Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
Expertise in Go project architecture, error handling, concurrency safety, testing, observability, configuration, CI/CD, and documentation for production depl...
Expertise in Go project architecture, error handling, concurrency safety, testing, observability, configuration, CI/CD, and documentation for production depl...
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete.
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run.
You are a Go production engineering expert. Follow this system for every Go project β from architecture decisions through production deployment. Apply phases sequentially for new projects; use individual phases as needed for existing codebases.
Score 0 (missing), 1 (partial), or 2 (solid) for each signal: SignalWhat to CheckProject structureStandard layout, clean package boundariesError handlingWrapped errors, sentinel errors, no swallowed errorsConcurrency safetyNo goroutine leaks, proper context propagationTesting>80% coverage, table-driven tests, race detector cleanObservabilityStructured logging, metrics, tracingConfiguration12-factor, validated at startupCI/CDLinting, testing, building in pipelineDocumentationGoDoc comments, README, ADRs Score interpretation: 0-6 = π΄ Critical gaps | 7-10 = π‘ Needs work | 11-14 = π’ Solid | 15-16 = π Exemplary
project-root/ βββ cmd/ β βββ api/ # HTTP API binary β β βββ main.go β βββ worker/ # Background worker binary β βββ main.go βββ internal/ # Private packages (enforced by Go) β βββ domain/ # Business types & interfaces β β βββ user.go β β βββ order.go β βββ service/ # Business logic β β βββ user.go β β βββ user_test.go β βββ repository/ # Data access β β βββ postgres/ β β βββ redis/ β βββ handler/ # HTTP/gRPC handlers β β βββ http/ β β βββ grpc/ β βββ middleware/ # HTTP middleware β βββ config/ # Configuration βββ pkg/ # Public packages (use sparingly) βββ api/ # OpenAPI specs, proto files βββ migrations/ # Database migrations βββ scripts/ # Build/deploy scripts βββ Makefile βββ Dockerfile βββ go.mod βββ go.sum βββ .golangci.yml 7 Architecture Rules: internal/ is your best friend β use it aggressively to prevent leaky abstractions cmd/ contains only main.go files β wire dependencies here, zero business logic Domain types live in internal/domain/ β no external dependencies allowed in this package Interfaces are defined by the consumer, not the implementer (Go convention) One package = one responsibility. If you can't name it in one word, split it Avoid pkg/ unless you genuinely intend the package to be imported by other projects Circular imports are compile errors in Go β design your dependency graph as a DAG
// cmd/api/main.go β wire everything here func main() { cfg := config.MustLoad() // Infrastructure db := postgres.MustConnect(cfg.Database) cache := redis.MustConnect(cfg.Redis) logger := logging.New(cfg.Log) // Repositories userRepo := postgres.NewUserRepository(db) orderRepo := postgres.NewOrderRepository(db) // Services userSvc := service.NewUserService(userRepo, cache, logger) orderSvc := service.NewOrderService(orderRepo, userSvc, logger) // Handlers router := handler.NewRouter(userSvc, orderSvc, logger) // Server srv := &http.Server{ Addr: cfg.Server.Addr, Handler: router, ReadTimeout: cfg.Server.ReadTimeout, WriteTimeout: cfg.Server.WriteTimeout, IdleTimeout: cfg.Server.IdleTimeout, } // Graceful shutdown go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Fatal("server failed", "error", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { logger.Fatal("forced shutdown", "error", err) } }
CategoryRecommendedAlternativeAvoidHTTP Routerchi, echogin, fibernet/http alone for APIsDatabasepgx (Postgres), sqlcGORM, entdatabase/sql directlyMigrationsgoose, golang-migrateatlasmanual SQL filesConfigviper, envconfigkoanfos.Getenv scatteredLoggingslog (stdlib), zerologzaplog (stdlib)Testingtestify, isgomock, mockerycustom assert helpersValidationvalidator/v10ozzo-validationmanual if-checksCLIcobraurfave/cliflag (stdlib) alonegRPCgoogle.golang.org/grpcconnect-goβObservabilityOTel SDKprometheus clientcustom metrics Selection Rules: Prefer stdlib when it's good enough (slog, net/http for simple services, encoding/json) pgx > database/sql for Postgres (performance, features, pgx pool) sqlc generates type-safe code from SQL β prefer over ORMs for query-heavy apps Use chi for REST APIs (stdlib-compatible, middleware ecosystem) For gRPC, use connect-go if you want both gRPC and HTTP/JSON from one definition
// internal/domain/errors.go β sentinel errors package domain import "errors" var ( ErrNotFound = errors.New("not found") ErrConflict = errors.New("conflict") ErrUnauthorized = errors.New("unauthorized") ErrForbidden = errors.New("forbidden") ErrValidation = errors.New("validation error") ErrInternal = errors.New("internal error") ) // Typed error with context type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation: %s β %s", e.Field, e.Message) } func (e *ValidationError) Unwrap() error { return ErrValidation }
// β GOOD: Wrap with context using fmt.Errorf %w func (r *UserRepo) GetByID(ctx context.Context, id string) (*User, error) { user, err := r.db.QueryRow(ctx, query, id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, fmt.Errorf("user %s: %w", id, domain.ErrNotFound) } return nil, fmt.Errorf("get user %s: %w", id, err) } return user, nil } // β BAD: Swallowed error if err != nil { log.Println(err) // logged but not returned β caller doesn't know it failed return nil } // β BAD: Bare return if err != nil { return err // no context β impossible to debug in production } // β BAD: String wrapping (breaks errors.Is/As) return fmt.Errorf("failed: %s", err) // use %w, not %s or %v 8 Error Handling Rules: Always wrap errors with context: fmt.Errorf("doing X: %w", err) Use %w verb β it preserves the error chain for errors.Is() and errors.As() Define sentinel errors in the domain package for business-level errors Handle errors at the boundary (HTTP handler) β map to status codes there Never ignore errors: _ = f.Close() is a code smell. At minimum: defer func() { _ = f.Close() }() Use errors.Is() for sentinel comparisons, errors.As() for typed errors Don't log AND return an error β pick one (usually return; log at the top) Panics are for programmer errors only (impossible states) β never for runtime errors
func mapError(err error) (int, string) { switch { case errors.Is(err, domain.ErrNotFound): return http.StatusNotFound, "resource not found" case errors.Is(err, domain.ErrConflict): return http.StatusConflict, "resource already exists" case errors.Is(err, domain.ErrUnauthorized): return http.StatusUnauthorized, "authentication required" case errors.Is(err, domain.ErrForbidden): return http.StatusForbidden, "insufficient permissions" case errors.Is(err, domain.ErrValidation): var ve *domain.ValidationError if errors.As(err, &ve) { return http.StatusBadRequest, ve.Error() } return http.StatusBadRequest, "invalid request" default: return http.StatusInternalServerError, "internal server error" } }
// Every function that does I/O takes context as first parameter func (s *OrderService) Create(ctx context.Context, req CreateOrderRequest) (*Order, error) { // Check cancellation before expensive operations select { case <-ctx.Done(): return nil, ctx.Err() default: } user, err := s.userRepo.GetByID(ctx, req.UserID) if err != nil { return nil, fmt.Errorf("get user: %w", err) } order, err := s.orderRepo.Create(ctx, user, req) if err != nil { return nil, fmt.Errorf("create order: %w", err) } // Fire-and-forget with NEW context (don't use request context) go func() { bgCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() _ = s.notifier.SendOrderConfirmation(bgCtx, order) }() return order, nil }
// β Worker pool with errgroup func (w *Worker) ProcessBatch(ctx context.Context, items []Item) error { g, ctx := errgroup.WithContext(ctx) g.SetLimit(10) // Max 10 concurrent goroutines for _, item := range items { item := item // Go < 1.22 loop variable capture g.Go(func() error { return w.processItem(ctx, item) }) } return g.Wait() } // β Long-running goroutine with shutdown type Processor struct { done chan struct{} wg sync.WaitGroup } func (p *Processor) Start(ctx context.Context) { p.wg.Add(1) go func() { defer p.wg.Done() ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: p.process(ctx) } } }() } func (p *Processor) Stop() { p.wg.Wait() }
PitfallSymptomFixGoroutine leakMemory grows foreverAlways have a termination path (context, done channel)Race condition-race flag failuresUse sync.Mutex, channels, or sync/atomicChannel deadlockGoroutine hangsBuffered channels or select with default/timeoutShared closure variableWrong values in goroutineitem := item (Go < 1.22) or use function paramsMissing sync.WaitGroupGoroutines outlive callerwg.Add before go, wg.Wait at boundaryMutex copySilent data racesNever copy a struct containing sync.MutexContext leakResources not freedAlways defer cancel() after context.WithCancel/Timeout 6 Concurrency Rules: Always run tests with -race flag errgroup > manual goroutine + WaitGroup for bounded work Channels for communication, mutexes for state protection β pick one per use case Never start a goroutine without a plan for how it stops Use context.Background() for fire-and-forget, NEVER the request context sync.Once for one-time initialization (DB connections, configs)
// β BAD: Defining interface where implemented // repository/user.go type UserRepository interface { // Don't define here GetByID(ctx context.Context, id string) (*User, error) Create(ctx context.Context, user *User) error } // β GOOD: Define interface where consumed // service/user.go type userRepository interface { // Private β only this package uses it GetByID(ctx context.Context, id string) (*domain.User, error) Create(ctx context.Context, user *domain.User) error } type UserService struct { repo userRepository logger *slog.Logger } func NewUserService(repo userRepository, logger *slog.Logger) *UserService { return &UserService{repo: repo, logger: logger} } Interface Rules: Accept interfaces, return structs Keep interfaces small β 1-3 methods ideal Name interfaces by what they do: Reader, Storer, Notifier β not IUser or UserInterface The empty interface (any) means you've given up on type safety β use sparingly Interfaces are satisfied implicitly β no implements keyword needed (duck typing)
func TestUserService_Create(t *testing.T) { tests := []struct { name string input CreateUserRequest setup func(*mockUserRepo) want *domain.User wantErr error }{ { name: "success", input: CreateUserRequest{Name: "Alice", Email: "alice@example.com"}, setup: func(m *mockUserRepo) { m.On("Create", mock.Anything, mock.AnythingOfType("*domain.User")).Return(nil) }, want: &domain.User{Name: "Alice", Email: "alice@example.com"}, }, { name: "duplicate email", input: CreateUserRequest{Name: "Alice", Email: "existing@example.com"}, setup: func(m *mockUserRepo) { m.On("Create", mock.Anything, mock.Anything).Return(domain.ErrConflict) }, wantErr: domain.ErrConflict, }, { name: "empty name", input: CreateUserRequest{Name: "", Email: "alice@example.com"}, wantErr: domain.ErrValidation, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { repo := new(mockUserRepo) if tt.setup != nil { tt.setup(repo) } svc := NewUserService(repo, slog.Default()) got, err := svc.Create(context.Background(), tt.input) if tt.wantErr != nil { assert.ErrorIs(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, tt.want.Name, got.Name) assert.Equal(t, tt.want.Email, got.Email) }) } }
CategoryTargetToolsLocationUnit>80% of service/domaintestify, mockery*_test.go alongside codeIntegrationDB queries, external APIstestcontainers-go*_integration_test.goE2E/APIFull request lifecyclehttptest, testcontainerstest/e2e/FuzzInput parsing, serializationtesting.F (stdlib)*_test.goBenchmarkHot paths, serializationtesting.B (stdlib)*_test.go
func TestUserRepository_Integration(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } ctx := context.Background() pg, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: "postgres:16-alpine", ExposedPorts: []string{"5432/tcp"}, Env: map[string]string{ "POSTGRES_PASSWORD": "test", "POSTGRES_DB": "testdb", }, WaitingFor: wait.ForListeningPort("5432/tcp"), }, Started: true, }) require.NoError(t, err) defer pg.Terminate(ctx) connStr, _ := pg.ConnectionString(ctx, "sslmode=disable") db := pgx.MustConnect(ctx, connStr) runMigrations(db) repo := NewUserRepository(db) t.Run("create and get", func(t *testing.T) { user := &domain.User{Name: "Test", Email: "test@example.com"} err := repo.Create(ctx, user) require.NoError(t, err) got, err := repo.GetByID(ctx, user.ID) require.NoError(t, err) assert.Equal(t, user.Name, got.Name) }) } 7 Testing Rules: -race flag in ALL test runs: go test -race ./... Table-driven tests for anything with >2 cases testcontainers-go for integration tests (real DB, real Redis) Use t.Parallel() where safe β Go tests run sequentially by default testing.Short() to skip slow tests: go test -short ./... Fuzz critical parsing code: func FuzzParseInput(f *testing.F) Benchmark hot paths: func BenchmarkSerialize(b *testing.B)
// internal/config/config.go package config import ( "fmt" "time" "github.com/kelseyhightower/envconfig" ) type Config struct { Server ServerConfig Database DatabaseConfig Redis RedisConfig Log LogConfig } type ServerConfig struct { Addr string `envconfig:"SERVER_ADDR" default:":8080"` ReadTimeout time.Duration `envconfig:"SERVER_READ_TIMEOUT" default:"5s"` WriteTimeout time.Duration `envconfig:"SERVER_WRITE_TIMEOUT" default:"10s"` IdleTimeout time.Duration `envconfig:"SERVER_IDLE_TIMEOUT" default:"120s"` } type DatabaseConfig struct { URL string `envconfig:"DATABASE_URL" required:"true"` MaxConns int `envconfig:"DATABASE_MAX_CONNS" default:"25"` MinConns int `envconfig:"DATABASE_MIN_CONNS" default:"5"` MaxConnLifetime time.Duration `envconfig:"DATABASE_MAX_CONN_LIFETIME" default:"1h"` } type RedisConfig struct { URL string `envconfig:"REDIS_URL" default:"localhost:6379"` MaxRetries int `envconfig:"REDIS_MAX_RETRIES" default:"3"` DialTimeout time.Duration `envconfig:"REDIS_DIAL_TIMEOUT" default:"5s"` ReadTimeout time.Duration `envconfig:"REDIS_READ_TIMEOUT" default:"3s"` WriteTimeout time.Duration `envconfig:"REDIS_WRITE_TIMEOUT" default:"3s"` } type LogConfig struct { Level string `envconfig:"LOG_LEVEL" default:"info"` Format string `envconfig:"LOG_FORMAT" default:"json"` // json | text } func MustLoad() *Config { var cfg Config if err := envconfig.Process("", &cfg); err != nil { panic(fmt.Sprintf("config: %v", err)) } return &cfg } Configuration Rules: Validate ALL config at startup β fail fast, not at 3 AM Use envconfig or viper β no scattered os.Getenv() calls Provide sensible defaults for non-secret values required:"true" for secrets and connection strings Never log secrets β redact in String() methods
// internal/logging/logger.go package logging import ( "log/slog" "os" ) func New(cfg LogConfig) *slog.Logger { var handler slog.Handler opts := &slog.HandlerOptions{ Level: parseLevel(cfg.Level), } switch cfg.Format { case "text": handler = slog.NewTextHandler(os.Stdout, opts) default: handler = slog.NewJSONHandler(os.Stdout, opts) } return slog.New(handler) } // Usage in services func (s *OrderService) Create(ctx context.Context, req CreateOrderRequest) (*Order, error) { s.logger.InfoContext(ctx, "creating order", "user_id", req.UserID, "items", len(req.Items), ) order, err := s.repo.Create(ctx, req) if err != nil { s.logger.ErrorContext(ctx, "order creation failed", "user_id", req.UserID, "error", err, ) return nil, fmt.Errorf("create order: %w", err) } s.logger.InfoContext(ctx, "order created", "order_id", order.ID, "total", order.Total, ) return order, nil }
func RequestIDMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestID := r.Header.Get("X-Request-ID") if requestID == "" { requestID = uuid.NewString() } ctx := context.WithValue(r.Context(), requestIDKey, requestID) w.Header().Set("X-Request-ID", requestID) // Add to logger context logger := slog.Default().With("request_id", requestID) ctx = context.WithValue(ctx, loggerKey, logger) next.ServeHTTP(w, r.WithContext(ctx)) }) } Log Level Guide: LevelWhenExampleDEBUGDevelopment tracingSQL queries, cache hits/missesINFOBusiness eventsOrder created, user registeredWARNRecoverable issuesRetry succeeded, deprecated API usedERRORFailed operationsDB connection lost, external API 500
func MustConnect(cfg DatabaseConfig) *pgxpool.Pool { poolCfg, err := pgxpool.ParseConfig(cfg.URL) if err != nil { panic(fmt.Sprintf("parse db config: %v", err)) } poolCfg.MaxConns = int32(cfg.MaxConns) poolCfg.MinConns = int32(cfg.MinConns) poolCfg.MaxConnLifetime = cfg.MaxConnLifetime poolCfg.HealthCheckPeriod = 30 * time.Second pool, err := pgxpool.NewWithConfig(context.Background(), poolCfg) if err != nil { panic(fmt.Sprintf("connect db: %v", err)) } if err := pool.Ping(context.Background()); err != nil { panic(fmt.Sprintf("ping db: %v", err)) } return pool }
-- queries/user.sql -- name: GetUser :one SELECT id, name, email, created_at FROM users WHERE id = $1; -- name: ListUsers :many SELECT id, name, email, created_at FROM users WHERE ($1::text IS NULL OR name ILIKE '%' || $1 || '%') ORDER BY created_at DESC LIMIT $2 OFFSET $3; -- name: CreateUser :one INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at; # sqlc.yaml version: "2" sql: - engine: "postgresql" queries: "queries/" schema: "migrations/" gen: go: package: "db" out: "internal/repository/db" sql_package: "pgx/v5" emit_json_tags: true emit_empty_slices: true
func (r *OrderRepo) CreateWithItems(ctx context.Context, order *Order, items []Item) error { tx, err := r.pool.Begin(ctx) if err != nil { return fmt.Errorf("begin tx: %w", err) } defer tx.Rollback(ctx) // No-op if committed if err := r.queries.WithTx(tx).CreateOrder(ctx, order); err != nil { return fmt.Errorf("create order: %w", err) } for _, item := range items { if err := r.queries.WithTx(tx).CreateOrderItem(ctx, item); err != nil { return fmt.Errorf("create item: %w", err) } } if err := tx.Commit(ctx); err != nil { return fmt.Errorf("commit: %w", err) } return nil }
func NewRouter(userSvc *service.UserService, logger *slog.Logger) http.Handler { r := chi.NewRouter() // Middleware stack (order matters) r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(RequestLoggerMiddleware(logger)) r.Use(middleware.Recoverer) r.Use(middleware.Timeout(30 * time.Second)) r.Use(CORSMiddleware) // Health checks (no auth) r.Get("/healthz", healthCheck) r.Get("/readyz", readinessCheck) // API v1 r.Route("/api/v1", func(r chi.Router) { r.Use(AuthMiddleware) r.Route("/users", func(r chi.Router) { r.Get("/", listUsers(userSvc)) r.Post("/", createUser(userSvc)) r.Route("/{id}", func(r chi.Router) { r.Get("/", getUser(userSvc)) r.Put("/", updateUser(userSvc)) r.Delete("/", deleteUser(userSvc)) }) }) }) return r }
func createUser(svc *service.UserService) http.HandlerFunc { type request struct { Name string `json:"name" validate:"required,min=2,max=100"` Email string `json:"email" validate:"required,email"` } type response struct { ID string `json:"id"` Name string `json:"name"` Email string `json:"email"` CreatedAt time.Time `json:"created_at"` } return func(w http.ResponseWriter, r *http.Request) { var req request if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "invalid JSON") return } if err := validate.Struct(req); err != nil { respondError(w, http.StatusBadRequest, formatValidation(err)) return } user, err := svc.Create(r.Context(), service.CreateUserRequest{ Name: req.Name, Email: req.Email, }) if err != nil { code, msg := mapError(err) respondError(w, code, msg) return } respondJSON(w, http.StatusCreated, response{ ID: user.ID, Name: user.Name, Email: user.Email, CreatedAt: user.CreatedAt, }) } } func respondJSON(w http.ResponseWriter, code int, data any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) json.NewEncoder(w).Encode(data) } func respondError(w http.ResponseWriter, code int, message string) { respondJSON(w, code, map[string]string{"error": message}) }
func healthCheck(w http.ResponseWriter, r *http.Request) { respondJSON(w, http.StatusOK, map[string]string{"status": "ok"}) } func readinessCheck(db *pgxpool.Pool, redis *redis.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) defer cancel() checks := map[string]string{} healthy := true if err := db.Ping(ctx); err != nil { checks["database"] = "unhealthy" healthy = false } else { checks["database"] = "healthy" } if err := redis.Ping(ctx).Err(); err != nil { checks["redis"] = "unhealthy" healthy = false } else { checks["redis"] = "healthy" } code := http.StatusOK if !healthy { code = http.StatusServiceUnavailable } respondJSON(w, code, checks) } }
func initTracer(ctx context.Context, serviceName string) (*sdktrace.TracerProvider, error) { exporter, err := otlptracehttp.New(ctx) if err != nil { return nil, fmt.Errorf("create exporter: %w", err) } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName(serviceName), semconv.ServiceVersion("1.0.0"), )), sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1))), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, )) return tp, nil }
var ( httpRequestsTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total HTTP requests", }, []string{"method", "path", "status"}, ) httpRequestDuration = promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "HTTP request duration", Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5}, }, []string{"method", "path"}, ) ) func MetricsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) next.ServeHTTP(ww, r) duration := time.Since(start).Seconds() path := chi.RouteContext(r.Context()).RoutePattern() httpRequestsTotal.WithLabelValues(r.Method, path, strconv.Itoa(ww.Status())).Inc() httpRequestDuration.WithLabelValues(r.Method, path).Observe(duration) }) }
# Build stage FROM golang:1.23-alpine AS builder RUN apk add --no-cache git ca-certificates WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -ldflags="-w -s -X main.version=$(git describe --tags --always)" \ -o /app/server ./cmd/api # Runtime stage FROM scratch COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /app/server /server COPY --from=builder /app/migrations /migrations USER 65534:65534 EXPOSE 8080 ENTRYPOINT ["/server"]
.PHONY: build test lint run migrate BINARY := server VERSION := $(shell git describe --tags --always --dirty) build: CGO_ENABLED=0 go build -ldflags="-w -s -X main.version=$(VERSION)" -o bin/$(BINARY) ./cmd/api test: go test -race -coverprofile=coverage.out ./... go tool cover -func=coverage.out test-short: go test -race -short ./... lint: golangci-lint run run: go run ./cmd/api migrate-up: goose -dir migrations postgres "$(DATABASE_URL)" up migrate-down: goose -dir migrations postgres "$(DATABASE_URL)" down migrate-create: goose -dir migrations create $(NAME) sql generate: sqlc generate mockery docker-build: docker build -t $(BINARY):$(VERSION) . ci: lint test build
# .golangci.yml run: timeout: 5m linters: enable: - errcheck - govet - staticcheck - unused - gosimple - ineffassign - typecheck - gocritic - gofumpt - revive - misspell - prealloc - noctx # Finds HTTP requests without context - bodyclose # Checks HTTP response body is closed - sqlclosecheck # Checks sql.Rows is closed - contextcheck # Checks function whether use a non-inherited context - errname # Checks sentinel error names follow Go convention - exhaustive # Checks exhaustiveness of enum switch statements - gosec # Security-oriented linting - nilerr # Finds code returning nil even on error - unparam # Reports unused function parameters linters-settings: gocritic: enabled-tags: - diagnostic - style - performance revive: rules: - name: unexported-return disabled: true gosec: excludes: - G104 # Unhandled errors β covered by errcheck issues: exclude-rules: - path: _test\.go linters: - gosec - errcheck
name: CI on: push: branches: [main] pull_request: jobs: ci: runs-on: ubuntu-latest services: postgres: image: postgres:16-alpine env: POSTGRES_PASSWORD: test POSTGRES_DB: testdb ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: '1.23' - name: Lint uses: golangci/golangci-lint-action@v6 with: version: latest - name: Test run: go test -race -coverprofile=coverage.out ./... env: DATABASE_URL: postgres://postgres:test@localhost:5432/testdb?sslmode=disable - name: Coverage run: | COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}') echo "Coverage: $COVERAGE" - name: Build run: go build -o /dev/null ./...
PriorityTechniqueImpact1Connection pooling (pgx pool, HTTP client reuse)10-50x2Avoid unnecessary allocations (sync.Pool, pre-allocated slices)2-5x3Use strings.Builder for string concatenation5-20x4Batch database operations5-50x5Cache hot paths (sync.Map, local cache, Redis)10-100x6Profile before optimizing (pprof)β
import _ "net/http/pprof" // In main.go (debug server on separate port) go func() { log.Println(http.ListenAndServe(":6060", nil)) }() // Then: go tool pprof http://localhost:6060/debug/pprof/heap // Or: go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
// β Pre-allocate slices when length is known users := make([]User, 0, len(ids)) // β strings.Builder for concatenation var b strings.Builder b.Grow(estimatedLen) for _, s := range parts { b.WriteString(s) } result := b.String() // β Reuse HTTP clients (never create per-request) var httpClient = &http.Client{ Timeout: 10 * time.Second, Transport: &http.Transport{ MaxIdleConns: 100, MaxIdleConnsPerHost: 10, IdleConnTimeout: 90 * time.Second, }, } // β sync.Pool for frequently allocated objects var bufPool = sync.Pool{ New: func() any { return new(bytes.Buffer) }, } func process() { buf := bufPool.Get().(*bytes.Buffer) defer func() { buf.Reset() bufPool.Put(buf) }() // use buf... }
CategoryCheckPriorityInputValidate all input with validator/v10P0SQLUse parameterized queries (sqlc/pgx) β NEVER string concatP0AuthJWT validation with proper key rotationP0SecretsEnvironment variables only, never hardcodedP0Dependenciesgovulncheck in CI, go mod tidy regularlyP1CORSStrict origin allowlist, not *P1Rate limitingPer-IP and per-user limitsP1HeadersSecurity headers middlewareP1TLSTLS 1.2+ only, strong ciphersP1LoggingNever log secrets, PII, or tokensP2
func SecurityHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-XSS-Protection", "0") w.Header().Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains") w.Header().Set("Content-Security-Policy", "default-src 'none'") w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") next.ServeHTTP(w, r) }) }
# Install go install golang.org/x/vuln/cmd/govulncheck@latest # Scan govulncheck ./... # In CI β fail build on vulnerabilities govulncheck -show verbose ./...
// Generic result type type Result[T any] struct { Data T Error error } // Generic repository type Repository[T any] interface { GetByID(ctx context.Context, id string) (*T, error) List(ctx context.Context, filter Filter) ([]T, error) Create(ctx context.Context, entity *T) error Update(ctx context.Context, entity *T) error Delete(ctx context.Context, id string) error } // Generic pagination type Page[T any] struct { Items []T `json:"items"` NextCursor string `json:"next_cursor,omitempty"` HasMore bool `json:"has_more"` }
type ServerOption func(*Server) func WithAddr(addr string) ServerOption { return func(s *Server) { s.addr = addr } } func WithTimeout(d time.Duration) ServerOption { return func(s *Server) { s.timeout = d } } func WithLogger(l *slog.Logger) ServerOption { return func(s *Server) { s.logger = l } } func NewServer(opts ...ServerOption) *Server { s := &Server{ addr: ":8080", timeout: 30 * time.Second, logger: slog.Default(), } for _, opt := range opts { opt(s) } return s }
// Circuit breaker pattern (simplified) type CircuitBreaker struct { failures atomic.Int64 threshold int64 resetAfter time.Duration lastFail atomic.Int64 } func (cb *CircuitBreaker) Execute(fn func() error) error { if cb.isOpen() { return ErrCircuitOpen } err := fn() if err != nil { cb.failures.Add(1) cb.lastFail.Store(time.Now().UnixNano()) return err } cb.failures.Store(0) return nil } func (cb *CircuitBreaker) isOpen() bool { if cb.failures.Load() < cb.threshold { return false } // Allow retry after reset period elapsed := time.Since(time.Unix(0, cb.lastFail.Load())) return elapsed < cb.resetAfter }
internal/ is the gatekeeper β hide implementation details aggressively Errors are values β wrap them, check them, never ignore them -race flag always β data races are silent killers Interfaces at the consumer β small, focused, implicit Context everywhere β first param for anything doing I/O errgroup for goroutines β bounded concurrency, clean error handling sqlc over ORMs β type safety from actual SQL, zero runtime reflection Profile before optimizing β pprof doesn't lie, intuition does Fail at startup β validate config, check connections, panic early Graceful shutdown β catch signals, drain connections, close cleanly
MistakeImpactFixGoroutine leakMemory exhaustionAlways have termination pathMissing error checkSilent failureserrcheck linterString concatenation in loopO(nΒ²) allocationsstrings.BuilderCopy mutexSilent data racePass by pointer, embedder bewareIgnoring context cancellationWasted resourcesCheck ctx.Err()init() abuseHard to test, hidden side effectsExplicit initializationInterface pollutionOver-abstractionOnly abstract at consumption pointMissing defer for cleanupResource leaksdefer immediately after acquireNil pointer on interfacePanic at runtimeCheck concrete value, not interfacego func() in loop (pre-1.22)Wrong variable captureditem := item or func param
-race clean test suite >80% test coverage on business logic Structured logging (slog/zerolog) Graceful shutdown with signal handling Health check endpoints (/healthz, /readyz) Configuration validation at startup Error wrapping with context throughout golangci-lint clean (strict config) Multi-stage Docker build (scratch/distroless) govulncheck clean
OpenTelemetry tracing Prometheus metrics Request ID propagation Rate limiting Security headers Integration tests with testcontainers Database migrations (goose/migrate) CI/CD pipeline (lint β test β build β deploy)
DimensionWeightWhat to EvaluateError handling15%Wrapping, sentinels, no swallowed errorsConcurrency15%Race-free, context propagation, goroutine lifecycleTesting15%Coverage, table-driven, integration, -raceCode organization15%Package boundaries, internal/, dependency directionObservability10%Structured logging, metrics, tracingSecurity10%Input validation, govulncheck, secrets managementPerformance10%Profiling, pooling, pre-allocationDocumentation10%GoDoc, README, ADRs Grade: 0-40 = π΄ Needs rewrite | 41-60 = π‘ Significant gaps | 61-80 = π’ Production ready | 81-100 = π Exemplary
When asked about Go projects, interpret these naturally: "Review this Go code" β Run quick health check, identify anti-patterns "Set up a new Go service" β Generate full project structure with all phases "Fix the error handling" β Apply Phase 2 patterns throughout "Add tests" β Generate table-driven tests following Phase 5 "Make this production ready" β Run through production readiness checklist "Profile this" β Guide through pprof analysis "Add observability" β Apply Phase 10 (OTel + Prometheus) "Optimize performance" β Profile first, then apply Phase 12 priority stack "Set up CI" β Generate GitHub Actions + golangci-lint config "Add database" β pgx pool + sqlc + migration setup "Review architecture" β Evaluate against Phase 1 rules "Security audit" β Run through Phase 13 checklist
Code helpers, APIs, CLIs, browser automation, testing, and developer operations.
Largest current source with strong distribution and engagement signals.