478 lines
11 KiB
Markdown
478 lines
11 KiB
Markdown
|
|
# Project Structure and Module Management
|
||
|
|
|
||
|
|
## Standard Project Layout
|
||
|
|
|
||
|
|
```
|
||
|
|
myproject/
|
||
|
|
├── cmd/ # Main applications
|
||
|
|
│ ├── server/
|
||
|
|
│ │ └── main.go # Entry point for server
|
||
|
|
│ └── cli/
|
||
|
|
│ └── main.go # Entry point for CLI tool
|
||
|
|
├── internal/ # Private application code
|
||
|
|
│ ├── api/ # API handlers
|
||
|
|
│ ├── service/ # Business logic
|
||
|
|
│ └── repository/ # Data access layer
|
||
|
|
├── pkg/ # Public library code
|
||
|
|
│ └── models/ # Shared models
|
||
|
|
├── api/ # API definitions
|
||
|
|
│ ├── openapi.yaml # OpenAPI spec
|
||
|
|
│ └── proto/ # Protocol buffers
|
||
|
|
├── web/ # Web assets
|
||
|
|
│ ├── static/
|
||
|
|
│ └── templates/
|
||
|
|
├── scripts/ # Build and install scripts
|
||
|
|
├── configs/ # Configuration files
|
||
|
|
├── deployments/ # Docker, K8s configs
|
||
|
|
├── test/ # Additional test data
|
||
|
|
├── docs/ # Documentation
|
||
|
|
├── go.mod # Module definition
|
||
|
|
├── go.sum # Dependency checksums
|
||
|
|
├── Makefile # Build automation
|
||
|
|
└── README.md
|
||
|
|
```
|
||
|
|
|
||
|
|
## go.mod Basics
|
||
|
|
|
||
|
|
```go
|
||
|
|
// Initialize module
|
||
|
|
// go mod init github.com/user/project
|
||
|
|
|
||
|
|
module github.com/user/myproject
|
||
|
|
|
||
|
|
go 1.21
|
||
|
|
|
||
|
|
require (
|
||
|
|
github.com/gin-gonic/gin v1.9.1
|
||
|
|
github.com/lib/pq v1.10.9
|
||
|
|
go.uber.org/zap v1.26.0
|
||
|
|
)
|
||
|
|
|
||
|
|
require (
|
||
|
|
// Indirect dependencies (automatically managed)
|
||
|
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||
|
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||
|
|
)
|
||
|
|
|
||
|
|
// Replace directive for local development
|
||
|
|
replace github.com/user/mylib => ../mylib
|
||
|
|
|
||
|
|
// Retract directive to mark bad versions
|
||
|
|
retract v1.0.1 // Contains critical bug
|
||
|
|
```
|
||
|
|
|
||
|
|
## Module Commands
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Initialize module
|
||
|
|
go mod init github.com/user/project
|
||
|
|
|
||
|
|
# Add missing dependencies
|
||
|
|
go mod tidy
|
||
|
|
|
||
|
|
# Download dependencies
|
||
|
|
go mod download
|
||
|
|
|
||
|
|
# Verify dependencies
|
||
|
|
go mod verify
|
||
|
|
|
||
|
|
# Show module graph
|
||
|
|
go mod graph
|
||
|
|
|
||
|
|
# Show why package is needed
|
||
|
|
go mod why github.com/user/package
|
||
|
|
|
||
|
|
# Vendor dependencies (copy to vendor/)
|
||
|
|
go mod vendor
|
||
|
|
|
||
|
|
# Update dependency
|
||
|
|
go get -u github.com/user/package
|
||
|
|
|
||
|
|
# Update to specific version
|
||
|
|
go get github.com/user/package@v1.2.3
|
||
|
|
|
||
|
|
# Update all dependencies
|
||
|
|
go get -u ./...
|
||
|
|
|
||
|
|
# Remove unused dependencies
|
||
|
|
go mod tidy
|
||
|
|
```
|
||
|
|
|
||
|
|
## Internal Packages
|
||
|
|
|
||
|
|
```go
|
||
|
|
// internal/ packages can only be imported by code in the parent tree
|
||
|
|
|
||
|
|
myproject/
|
||
|
|
├── internal/
|
||
|
|
│ ├── auth/ # Can only be imported by myproject
|
||
|
|
│ │ └── jwt.go
|
||
|
|
│ └── database/
|
||
|
|
│ └── postgres.go
|
||
|
|
└── pkg/
|
||
|
|
└── models/ # Can be imported by anyone
|
||
|
|
└── user.go
|
||
|
|
|
||
|
|
// This works (same project):
|
||
|
|
import "github.com/user/myproject/internal/auth"
|
||
|
|
|
||
|
|
// This fails (different project):
|
||
|
|
import "github.com/other/project/internal/auth" // Error!
|
||
|
|
|
||
|
|
// Internal subdirectories
|
||
|
|
myproject/
|
||
|
|
└── api/
|
||
|
|
└── internal/ # Can only be imported by code in api/
|
||
|
|
└── helpers.go
|
||
|
|
```
|
||
|
|
|
||
|
|
## Package Organization
|
||
|
|
|
||
|
|
```go
|
||
|
|
// user/user.go - Domain package
|
||
|
|
package user
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// User represents a user entity
|
||
|
|
type User struct {
|
||
|
|
ID string
|
||
|
|
Email string
|
||
|
|
CreatedAt time.Time
|
||
|
|
}
|
||
|
|
|
||
|
|
// Repository defines data access interface
|
||
|
|
type Repository interface {
|
||
|
|
Create(ctx context.Context, user *User) error
|
||
|
|
GetByID(ctx context.Context, id string) (*User, error)
|
||
|
|
Update(ctx context.Context, user *User) error
|
||
|
|
Delete(ctx context.Context, id string) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// Service handles business logic
|
||
|
|
type Service struct {
|
||
|
|
repo Repository
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewService creates a new user service
|
||
|
|
func NewService(repo Repository) *Service {
|
||
|
|
return &Service{repo: repo}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Service) RegisterUser(ctx context.Context, email string) (*User, error) {
|
||
|
|
user := &User{
|
||
|
|
ID: generateID(),
|
||
|
|
Email: email,
|
||
|
|
CreatedAt: time.Now(),
|
||
|
|
}
|
||
|
|
return user, s.repo.Create(ctx, user)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Multi-Module Repository (Monorepo)
|
||
|
|
|
||
|
|
```
|
||
|
|
monorepo/
|
||
|
|
├── go.work # Workspace file
|
||
|
|
├── services/
|
||
|
|
│ ├── api/
|
||
|
|
│ │ ├── go.mod
|
||
|
|
│ │ └── main.go
|
||
|
|
│ └── worker/
|
||
|
|
│ ├── go.mod
|
||
|
|
│ └── main.go
|
||
|
|
└── shared/
|
||
|
|
└── models/
|
||
|
|
├── go.mod
|
||
|
|
└── user.go
|
||
|
|
|
||
|
|
// go.work
|
||
|
|
go 1.21
|
||
|
|
|
||
|
|
use (
|
||
|
|
./services/api
|
||
|
|
./services/worker
|
||
|
|
./shared/models
|
||
|
|
)
|
||
|
|
|
||
|
|
// Commands:
|
||
|
|
// go work init ./services/api ./services/worker
|
||
|
|
// go work use ./shared/models
|
||
|
|
// go work sync
|
||
|
|
```
|
||
|
|
|
||
|
|
## Build Tags and Constraints
|
||
|
|
|
||
|
|
```go
|
||
|
|
// +build integration
|
||
|
|
// integration_test.go
|
||
|
|
|
||
|
|
package myapp
|
||
|
|
|
||
|
|
import "testing"
|
||
|
|
|
||
|
|
func TestIntegration(t *testing.T) {
|
||
|
|
// Integration test code
|
||
|
|
}
|
||
|
|
|
||
|
|
// Build: go test -tags=integration
|
||
|
|
|
||
|
|
// File-level build constraints (Go 1.17+)
|
||
|
|
//go:build linux && amd64
|
||
|
|
|
||
|
|
package myapp
|
||
|
|
|
||
|
|
// Multiple constraints
|
||
|
|
//go:build linux || darwin
|
||
|
|
//go:build amd64
|
||
|
|
|
||
|
|
// Negation
|
||
|
|
//go:build !windows
|
||
|
|
|
||
|
|
// Common tags:
|
||
|
|
// linux, darwin, windows, freebsd
|
||
|
|
// amd64, arm64, 386, arm
|
||
|
|
// cgo, !cgo
|
||
|
|
```
|
||
|
|
|
||
|
|
## Makefile Example
|
||
|
|
|
||
|
|
```makefile
|
||
|
|
# Makefile
|
||
|
|
.PHONY: build test lint clean run
|
||
|
|
|
||
|
|
# Variables
|
||
|
|
BINARY_NAME=myapp
|
||
|
|
BUILD_DIR=bin
|
||
|
|
GO=go
|
||
|
|
GOFLAGS=-v
|
||
|
|
|
||
|
|
# Build the application
|
||
|
|
build:
|
||
|
|
$(GO) build $(GOFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/server
|
||
|
|
|
||
|
|
# Run tests
|
||
|
|
test:
|
||
|
|
$(GO) test -v -race -coverprofile=coverage.out ./...
|
||
|
|
|
||
|
|
# Run tests with coverage report
|
||
|
|
test-coverage: test
|
||
|
|
$(GO) tool cover -html=coverage.out
|
||
|
|
|
||
|
|
# Run linters
|
||
|
|
lint:
|
||
|
|
golangci-lint run ./...
|
||
|
|
|
||
|
|
# Format code
|
||
|
|
fmt:
|
||
|
|
$(GO) fmt ./...
|
||
|
|
goimports -w .
|
||
|
|
|
||
|
|
# Run the application
|
||
|
|
run:
|
||
|
|
$(GO) run ./cmd/server
|
||
|
|
|
||
|
|
# Clean build artifacts
|
||
|
|
clean:
|
||
|
|
rm -rf $(BUILD_DIR)
|
||
|
|
rm -f coverage.out
|
||
|
|
|
||
|
|
# Install dependencies
|
||
|
|
deps:
|
||
|
|
$(GO) mod download
|
||
|
|
$(GO) mod tidy
|
||
|
|
|
||
|
|
# Build for multiple platforms
|
||
|
|
build-all:
|
||
|
|
GOOS=linux GOARCH=amd64 $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./cmd/server
|
||
|
|
GOOS=darwin GOARCH=amd64 $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64 ./cmd/server
|
||
|
|
GOOS=windows GOARCH=amd64 $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe ./cmd/server
|
||
|
|
|
||
|
|
# Run with race detector
|
||
|
|
run-race:
|
||
|
|
$(GO) run -race ./cmd/server
|
||
|
|
|
||
|
|
# Generate code
|
||
|
|
generate:
|
||
|
|
$(GO) generate ./...
|
||
|
|
|
||
|
|
# Docker build
|
||
|
|
docker-build:
|
||
|
|
docker build -t $(BINARY_NAME):latest .
|
||
|
|
|
||
|
|
# Help
|
||
|
|
help:
|
||
|
|
@echo "Available targets:"
|
||
|
|
@echo " build - Build the application"
|
||
|
|
@echo " test - Run tests"
|
||
|
|
@echo " test-coverage - Run tests with coverage report"
|
||
|
|
@echo " lint - Run linters"
|
||
|
|
@echo " fmt - Format code"
|
||
|
|
@echo " run - Run the application"
|
||
|
|
@echo " clean - Clean build artifacts"
|
||
|
|
@echo " deps - Install dependencies"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Dockerfile Multi-Stage Build
|
||
|
|
|
||
|
|
```dockerfile
|
||
|
|
# Build stage
|
||
|
|
FROM golang:1.21-alpine AS builder
|
||
|
|
|
||
|
|
WORKDIR /app
|
||
|
|
|
||
|
|
# Copy go mod files
|
||
|
|
COPY go.mod go.sum ./
|
||
|
|
RUN go mod download
|
||
|
|
|
||
|
|
# Copy source code
|
||
|
|
COPY . .
|
||
|
|
|
||
|
|
# Build binary
|
||
|
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server ./cmd/server
|
||
|
|
|
||
|
|
# Final stage
|
||
|
|
FROM alpine:latest
|
||
|
|
|
||
|
|
RUN apk --no-cache add ca-certificates
|
||
|
|
|
||
|
|
WORKDIR /root/
|
||
|
|
|
||
|
|
# Copy binary from builder
|
||
|
|
COPY --from=builder /app/server .
|
||
|
|
|
||
|
|
# Copy config files if needed
|
||
|
|
COPY --from=builder /app/configs ./configs
|
||
|
|
|
||
|
|
EXPOSE 8080
|
||
|
|
|
||
|
|
CMD ["./server"]
|
||
|
|
```
|
||
|
|
|
||
|
|
## Version Information
|
||
|
|
|
||
|
|
```go
|
||
|
|
// version/version.go
|
||
|
|
package version
|
||
|
|
|
||
|
|
import "runtime"
|
||
|
|
|
||
|
|
var (
|
||
|
|
// Set via ldflags during build
|
||
|
|
Version = "dev"
|
||
|
|
GitCommit = "none"
|
||
|
|
BuildTime = "unknown"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Info returns version information
|
||
|
|
func Info() map[string]string {
|
||
|
|
return map[string]string{
|
||
|
|
"version": Version,
|
||
|
|
"git_commit": GitCommit,
|
||
|
|
"build_time": BuildTime,
|
||
|
|
"go_version": runtime.Version(),
|
||
|
|
"os": runtime.GOOS,
|
||
|
|
"arch": runtime.GOARCH,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Build with version info:
|
||
|
|
// go build -ldflags "-X github.com/user/project/version.Version=1.0.0 \
|
||
|
|
// -X github.com/user/project/version.GitCommit=$(git rev-parse HEAD) \
|
||
|
|
// -X github.com/user/project/version.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Go Generate
|
||
|
|
|
||
|
|
```go
|
||
|
|
// models/user.go
|
||
|
|
//go:generate mockgen -source=user.go -destination=../mocks/user_mock.go -package=mocks
|
||
|
|
|
||
|
|
package models
|
||
|
|
|
||
|
|
type UserRepository interface {
|
||
|
|
GetUser(id string) (*User, error)
|
||
|
|
SaveUser(user *User) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// tools.go - Track tool dependencies
|
||
|
|
//go:build tools
|
||
|
|
|
||
|
|
package tools
|
||
|
|
|
||
|
|
import (
|
||
|
|
_ "github.com/golang/mock/mockgen"
|
||
|
|
_ "golang.org/x/tools/cmd/stringer"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Install tools:
|
||
|
|
// go install github.com/golang/mock/mockgen@latest
|
||
|
|
|
||
|
|
// Run generate:
|
||
|
|
// go generate ./...
|
||
|
|
```
|
||
|
|
|
||
|
|
## Configuration Management
|
||
|
|
|
||
|
|
```go
|
||
|
|
// config/config.go
|
||
|
|
package config
|
||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/kelseyhightower/envconfig"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Config struct {
|
||
|
|
Server ServerConfig
|
||
|
|
Database DatabaseConfig
|
||
|
|
Redis RedisConfig
|
||
|
|
}
|
||
|
|
|
||
|
|
type ServerConfig struct {
|
||
|
|
Host string `envconfig:"SERVER_HOST" default:"0.0.0.0"`
|
||
|
|
Port int `envconfig:"SERVER_PORT" default:"8080"`
|
||
|
|
ReadTimeout time.Duration `envconfig:"SERVER_READ_TIMEOUT" default:"10s"`
|
||
|
|
WriteTimeout time.Duration `envconfig:"SERVER_WRITE_TIMEOUT" default:"10s"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type DatabaseConfig struct {
|
||
|
|
URL string `envconfig:"DATABASE_URL" required:"true"`
|
||
|
|
MaxOpenConns int `envconfig:"DB_MAX_OPEN_CONNS" default:"25"`
|
||
|
|
MaxIdleConns int `envconfig:"DB_MAX_IDLE_CONNS" default:"5"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type RedisConfig struct {
|
||
|
|
Addr string `envconfig:"REDIS_ADDR" default:"localhost:6379"`
|
||
|
|
Password string `envconfig:"REDIS_PASSWORD"`
|
||
|
|
DB int `envconfig:"REDIS_DB" default:"0"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Load loads configuration from environment
|
||
|
|
func Load() (*Config, error) {
|
||
|
|
var cfg Config
|
||
|
|
if err := envconfig.Process("", &cfg); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &cfg, nil
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Quick Reference
|
||
|
|
|
||
|
|
| Command | Description |
|
||
|
|
|---------|-------------|
|
||
|
|
| `go mod init` | Initialize module |
|
||
|
|
| `go mod tidy` | Add/remove dependencies |
|
||
|
|
| `go mod download` | Download dependencies |
|
||
|
|
| `go get package@version` | Add/update dependency |
|
||
|
|
| `go build -ldflags "-X ..."` | Set version info |
|
||
|
|
| `go generate ./...` | Run code generation |
|
||
|
|
| `GOOS=linux go build` | Cross-compile |
|
||
|
|
| `go work init` | Initialize workspace |
|