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
// 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
# 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/[email protected]
# Update all dependencies
go get -u ./...
# Remove unused dependencies
go mod tidy
Internal Packages
// 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
// 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
// +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
.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
# 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
// 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
// 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
// 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 |