Go backend development standards and architecture patterns. Use this skill whenever writing, reviewing, or scaffolding Go backend code including services, APIs, operators, or any Go project. Triggers on Go file creation, code review requests, architecture discussions, or when the user mentions Go, Golang, backend services, REST APIs, gRPC, or Kubernetes operators. Always apply these standards when generating Go code.
These are opinionated standards for Go backend development. Apply them to all Go code generation, review, and architecture decisions.
Before writing Go code, ensure:
fmt.Errorf("...: %w", err)golangci-lint runUse cmd/, internal/, pkg/ layout:
cmd/ - Entry points (main packages), one subdirectory per service/toolinternal/ - Private application code, organized by domain (not by layer)pkg/ - Reusable libraries safe for external import (use sparingly)Organize by domain, not by technical layer:
internal/
├── orders/ # Good: domain-focused
├── payments/
└── users/
Not:
internal/
├── handlers/ # Bad: layer-focused
├── models/
└── repositories/
Business logic depends only on interfaces it defines. Infrastructure implements those interfaces.
// In internal/orders/service.go (business logic)
type OrderRepository interface {
Save(ctx context.Context, order *Order) error
FindByID(ctx context.Context, id string) (*Order, error)
}
type Service struct {
repo OrderRepository // Depends on interface, not concrete type
}
Business logic should never import transport, database, or instrumentation packages.
Context is a first-class architectural concern:
context.Context as the first parametercontext.Background() only at program entry pointscontext.WithValue sparingly and only for request-scoped dataWrap errors with context using fmt.Errorf:
if err != nil {
return fmt.Errorf("failed to save order %s: %w", order.ID, err)
}
Handle errors at appropriate boundaries. Don't log and return the same error.
service.go → service_test.gofunc TestService_CreateOrder(t *testing.T) {
tests := []struct {
name string
input CreateOrderInput
setup func(*mocks.MockRepository)
wantErr bool
}{
// test cases...
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// ...
})
}
}
//go:build integrationApply instrumentation via middleware/decorators at boundaries, not in business logic:
// Decorator pattern for business logic instrumentation
type InstrumentedService struct {
next Service
metrics Metrics
tracer trace.Tracer
}
func (s *InstrumentedService) CreateOrder(ctx context.Context, input CreateOrderInput) (*Order, error) {
ctx, span := s.tracer.Start(ctx, "CreateOrder")
defer span.End()
start := time.Now()
order, err := s.next.CreateOrder(ctx, input)
s.metrics.RecordLatency("create_order", time.Since(start))
return order, err
}
Follow:
Key principles:
Run golangci-lint run before committing. Enable standard linters:
gofmt, goimports, govet, errcheckstaticcheck, gosimple, ineffassign, unusedPrefer the Go standard library. Before introducing any external library:
google.rpc.Status with error detailsBased on OWASP Go Secure Coding Practices:
github.com/go-playground/validator/v10 for struct validation$1, MySQL: ?, Oracle: :param1golang.org/x/crypto/bcrypt - never roll your owncrypto/rand for all security-sensitive values (tokens, keys)math/rand for security purposeshtml/template (not text/template) for HTML outputContent-Type: application/json for JSON APIsFor extended guidance, read these files from this skill's directory:
references/architecture.md - Detailed architecture patterns, DI, boundary separationreferences/testing.md - Comprehensive testing strategies, test containers, coveragereferences/observability.md - Full OTel implementation, trace-enriched loggingreferences/security.md - OWASP-based security practices, input validation, cryptography