agentic-team-templates 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,320 @@
1
+ # Go Production Patterns
2
+
3
+ Patterns for building Go services that run reliably in production.
4
+
5
+ ## Application Lifecycle
6
+
7
+ ### The run() Pattern
8
+
9
+ ```go
10
+ func main() {
11
+ ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
12
+ defer cancel()
13
+
14
+ if err := run(ctx, os.Stdout, os.Args[1:]); err != nil {
15
+ fmt.Fprintf(os.Stderr, "error: %v\n", err)
16
+ os.Exit(1)
17
+ }
18
+ }
19
+
20
+ func run(ctx context.Context, stdout io.Writer, args []string) error {
21
+ cfg, err := loadConfig()
22
+ if err != nil {
23
+ return fmt.Errorf("loading config: %w", err)
24
+ }
25
+
26
+ logger := slog.New(slog.NewJSONHandler(stdout, nil))
27
+ db, err := connectDB(ctx, cfg.DatabaseURL)
28
+ if err != nil {
29
+ return fmt.Errorf("connecting to database: %w", err)
30
+ }
31
+ defer db.Close()
32
+
33
+ srv := NewServer(logger, db)
34
+ httpSrv := &http.Server{
35
+ Addr: cfg.Addr,
36
+ Handler: srv,
37
+ }
38
+
39
+ // Start server in background
40
+ go func() {
41
+ logger.Info("server starting", "addr", cfg.Addr)
42
+ if err := httpSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
43
+ logger.Error("server error", "error", err)
44
+ }
45
+ }()
46
+
47
+ // Wait for shutdown signal
48
+ <-ctx.Done()
49
+ logger.Info("shutting down")
50
+
51
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
52
+ defer cancel()
53
+
54
+ return httpSrv.Shutdown(shutdownCtx)
55
+ }
56
+ ```
57
+
58
+ ### Dependency Injection
59
+
60
+ ```go
61
+ // Wire dependencies explicitly in main/run — no magic, no frameworks
62
+ func run(ctx context.Context) error {
63
+ // Infrastructure
64
+ db := connectDB(cfg.DatabaseURL)
65
+ cache := redis.New(cfg.RedisURL)
66
+
67
+ // Repositories
68
+ userRepo := postgres.NewUserRepo(db)
69
+ orderRepo := postgres.NewOrderRepo(db)
70
+
71
+ // Services
72
+ userSvc := service.NewUserService(userRepo, cache)
73
+ orderSvc := service.NewOrderService(orderRepo, userSvc)
74
+
75
+ // Handlers
76
+ handler := handler.New(userSvc, orderSvc)
77
+
78
+ // Server
79
+ srv := &http.Server{Handler: handler}
80
+ return srv.ListenAndServe()
81
+ }
82
+ ```
83
+
84
+ ## Configuration
85
+
86
+ ```go
87
+ // Validate all configuration at startup — fail fast
88
+ type Config struct {
89
+ Addr string `env:"ADDR" default:":8080"`
90
+ DatabaseURL string `env:"DATABASE_URL,required"`
91
+ LogLevel slog.Level `env:"LOG_LEVEL" default:"info"`
92
+ Timeout time.Duration `env:"TIMEOUT" default:"30s"`
93
+ }
94
+
95
+ func loadConfig() (*Config, error) {
96
+ cfg := &Config{}
97
+ // Parse from env...
98
+
99
+ // Validate
100
+ if cfg.DatabaseURL == "" {
101
+ return nil, errors.New("DATABASE_URL is required")
102
+ }
103
+ if cfg.Timeout <= 0 {
104
+ return nil, errors.New("TIMEOUT must be positive")
105
+ }
106
+ return cfg, nil
107
+ }
108
+ ```
109
+
110
+ ## Health Checks
111
+
112
+ ```go
113
+ func (s *Server) handleHealthz(w http.ResponseWriter, r *http.Request) {
114
+ ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
115
+ defer cancel()
116
+
117
+ checks := map[string]error{
118
+ "database": s.db.PingContext(ctx),
119
+ "cache": s.cache.Ping(ctx),
120
+ }
121
+
122
+ status := http.StatusOK
123
+ result := make(map[string]string, len(checks))
124
+ for name, err := range checks {
125
+ if err != nil {
126
+ status = http.StatusServiceUnavailable
127
+ result[name] = err.Error()
128
+ } else {
129
+ result[name] = "ok"
130
+ }
131
+ }
132
+
133
+ w.Header().Set("Content-Type", "application/json")
134
+ w.WriteHeader(status)
135
+ json.NewEncoder(w).Encode(result)
136
+ }
137
+ ```
138
+
139
+ ## Observability
140
+
141
+ ### Structured Logging
142
+
143
+ ```go
144
+ // Use slog with consistent field names
145
+ logger.Info("request completed",
146
+ "method", r.Method,
147
+ "path", r.URL.Path,
148
+ "status", status,
149
+ "duration_ms", time.Since(start).Milliseconds(),
150
+ "request_id", middleware.RequestID(r.Context()),
151
+ )
152
+
153
+ // Log levels have meaning:
154
+ // Debug: Development-only information
155
+ // Info: Normal operations worth recording
156
+ // Warn: Something unexpected but handled
157
+ // Error: Something failed and needs attention
158
+
159
+ // Never log sensitive data (passwords, tokens, PII)
160
+ // Never log at Error level for expected conditions (404, validation failures)
161
+ ```
162
+
163
+ ### Metrics
164
+
165
+ ```go
166
+ import "github.com/prometheus/client_golang/prometheus"
167
+
168
+ var (
169
+ httpRequestsTotal = prometheus.NewCounterVec(
170
+ prometheus.CounterOpts{
171
+ Name: "http_requests_total",
172
+ Help: "Total number of HTTP requests",
173
+ },
174
+ []string{"method", "path", "status"},
175
+ )
176
+
177
+ httpRequestDuration = prometheus.NewHistogramVec(
178
+ prometheus.HistogramOpts{
179
+ Name: "http_request_duration_seconds",
180
+ Help: "HTTP request duration in seconds",
181
+ Buckets: prometheus.DefBuckets,
182
+ },
183
+ []string{"method", "path"},
184
+ )
185
+ )
186
+
187
+ func init() {
188
+ prometheus.MustRegister(httpRequestsTotal, httpRequestDuration)
189
+ }
190
+ ```
191
+
192
+ ## Graceful Degradation
193
+
194
+ ```go
195
+ // Circuit breaker for external dependencies
196
+ type CircuitBreaker struct {
197
+ mu sync.Mutex
198
+ failures int
199
+ threshold int
200
+ resetAfter time.Duration
201
+ lastFailure time.Time
202
+ state string // "closed", "open", "half-open"
203
+ }
204
+
205
+ func (cb *CircuitBreaker) Execute(fn func() error) error {
206
+ cb.mu.Lock()
207
+ if cb.state == "open" {
208
+ if time.Since(cb.lastFailure) > cb.resetAfter {
209
+ cb.state = "half-open"
210
+ } else {
211
+ cb.mu.Unlock()
212
+ return ErrCircuitOpen
213
+ }
214
+ }
215
+ cb.mu.Unlock()
216
+
217
+ err := fn()
218
+
219
+ cb.mu.Lock()
220
+ defer cb.mu.Unlock()
221
+ if err != nil {
222
+ cb.failures++
223
+ cb.lastFailure = time.Now()
224
+ if cb.failures >= cb.threshold {
225
+ cb.state = "open"
226
+ }
227
+ return err
228
+ }
229
+
230
+ cb.failures = 0
231
+ cb.state = "closed"
232
+ return nil
233
+ }
234
+ ```
235
+
236
+ ## Retry with Backoff
237
+
238
+ ```go
239
+ func RetryWithBackoff(ctx context.Context, maxAttempts int, base time.Duration, fn func() error) error {
240
+ var err error
241
+ for attempt := range maxAttempts {
242
+ err = fn()
243
+ if err == nil {
244
+ return nil
245
+ }
246
+
247
+ if attempt == maxAttempts-1 {
248
+ break
249
+ }
250
+
251
+ // Exponential backoff with jitter
252
+ backoff := base * time.Duration(1<<uint(attempt))
253
+ jitter := time.Duration(rand.Int64N(int64(backoff) / 2))
254
+ wait := backoff + jitter
255
+
256
+ select {
257
+ case <-ctx.Done():
258
+ return ctx.Err()
259
+ case <-time.After(wait):
260
+ }
261
+ }
262
+ return fmt.Errorf("after %d attempts: %w", maxAttempts, err)
263
+ }
264
+ ```
265
+
266
+ ## Database Patterns
267
+
268
+ ```go
269
+ // Repository pattern with transactions
270
+ type TxFunc func(ctx context.Context, tx *sql.Tx) error
271
+
272
+ func WithTransaction(ctx context.Context, db *sql.DB, fn TxFunc) error {
273
+ tx, err := db.BeginTx(ctx, nil)
274
+ if err != nil {
275
+ return fmt.Errorf("beginning transaction: %w", err)
276
+ }
277
+
278
+ if err := fn(ctx, tx); err != nil {
279
+ if rbErr := tx.Rollback(); rbErr != nil {
280
+ return fmt.Errorf("rolling back: %w (original: %v)", rbErr, err)
281
+ }
282
+ return err
283
+ }
284
+
285
+ if err := tx.Commit(); err != nil {
286
+ return fmt.Errorf("committing transaction: %w", err)
287
+ }
288
+ return nil
289
+ }
290
+
291
+ // Usage
292
+ err := WithTransaction(ctx, db, func(ctx context.Context, tx *sql.Tx) error {
293
+ if err := createOrder(ctx, tx, order); err != nil {
294
+ return err
295
+ }
296
+ return updateInventory(ctx, tx, order.Items)
297
+ })
298
+ ```
299
+
300
+ ## Anti-Patterns
301
+
302
+ ```go
303
+ // Never: Global mutable state
304
+ var db *sql.DB // Package-level mutable state makes testing impossible
305
+
306
+ // Never: init() for complex initialization
307
+ func init() {
308
+ db, _ = sql.Open(...) // Errors swallowed, untestable, order-dependent
309
+ }
310
+
311
+ // Never: Shared context.Background() in request handlers
312
+ func handler(w http.ResponseWriter, r *http.Request) {
313
+ db.QueryContext(context.Background(), ...) // Ignores request cancellation!
314
+ // Use r.Context() instead
315
+ }
316
+
317
+ // Never: Unbounded work queues
318
+ ch := make(chan Job) // Unbuffered or unbounded — will block or OOM
319
+ // Use bounded channels and handle backpressure explicitly
320
+ ```
@@ -0,0 +1,276 @@
1
+ # Go Standard Library and Tooling
2
+
3
+ The standard library is Go's greatest asset. Know it deeply before reaching for third-party packages.
4
+
5
+ ## Essential stdlib Packages
6
+
7
+ ### net/http
8
+
9
+ ```go
10
+ // The stdlib HTTP server is production-ready. You rarely need a framework.
11
+ mux := http.NewServeMux()
12
+
13
+ mux.HandleFunc("GET /users/{id}", getUser)
14
+ mux.HandleFunc("POST /users", createUser)
15
+ mux.HandleFunc("GET /healthz", healthCheck)
16
+
17
+ srv := &http.Server{
18
+ Addr: ":8080",
19
+ Handler: mux,
20
+ ReadTimeout: 10 * time.Second,
21
+ WriteTimeout: 10 * time.Second,
22
+ IdleTimeout: 120 * time.Second,
23
+ }
24
+
25
+ // Always set timeouts — an http.Server with no timeouts will leak goroutines
26
+ ```
27
+
28
+ ### Middleware Pattern
29
+
30
+ ```go
31
+ // Middleware is just a function that wraps an http.Handler
32
+ func Logging(logger *slog.Logger) func(http.Handler) http.Handler {
33
+ return func(next http.Handler) http.Handler {
34
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
35
+ start := time.Now()
36
+ wrapped := &responseRecorder{ResponseWriter: w, statusCode: http.StatusOK}
37
+
38
+ next.ServeHTTP(wrapped, r)
39
+
40
+ logger.Info("request",
41
+ "method", r.Method,
42
+ "path", r.URL.Path,
43
+ "status", wrapped.statusCode,
44
+ "duration", time.Since(start),
45
+ )
46
+ })
47
+ }
48
+ }
49
+
50
+ type responseRecorder struct {
51
+ http.ResponseWriter
52
+ statusCode int
53
+ }
54
+
55
+ func (r *responseRecorder) WriteHeader(code int) {
56
+ r.statusCode = code
57
+ r.ResponseWriter.WriteHeader(code)
58
+ }
59
+
60
+ // Chain middleware
61
+ handler := Logging(logger)(Recovery()(Auth(secret)(mux)))
62
+ ```
63
+
64
+ ### encoding/json
65
+
66
+ ```go
67
+ // Struct tags control serialization
68
+ type User struct {
69
+ ID string `json:"id"`
70
+ Email string `json:"email"`
71
+ Name string `json:"name,omitempty"`
72
+ CreatedAt time.Time `json:"created_at"`
73
+ password string // unexported — never serialized
74
+ }
75
+
76
+ // Use json.NewDecoder for streams, json.Unmarshal for []byte
77
+ func decodeBody(r *http.Request, v any) error {
78
+ dec := json.NewDecoder(r.Body)
79
+ dec.DisallowUnknownFields() // Reject unexpected fields
80
+ if err := dec.Decode(v); err != nil {
81
+ return fmt.Errorf("decoding request body: %w", err)
82
+ }
83
+ return nil
84
+ }
85
+ ```
86
+
87
+ ### log/slog (Go 1.21+)
88
+
89
+ ```go
90
+ // slog is the standard structured logger — use it
91
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
92
+ Level: slog.LevelInfo,
93
+ }))
94
+
95
+ logger.Info("server starting", "addr", addr, "version", version)
96
+ logger.Error("query failed", "error", err, "query", query)
97
+
98
+ // Add context to a sub-logger
99
+ reqLogger := logger.With("request_id", requestID, "user_id", userID)
100
+ reqLogger.Info("processing request")
101
+ ```
102
+
103
+ ### context
104
+
105
+ ```go
106
+ // Context carries deadlines, cancellation signals, and request-scoped values
107
+ // across API boundaries and goroutines
108
+
109
+ // Timeouts
110
+ ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
111
+ defer cancel()
112
+
113
+ result, err := db.QueryContext(ctx, query)
114
+
115
+ // Values — sparingly, for request-scoped cross-cutting concerns only
116
+ type contextKey string
117
+ const requestIDKey contextKey = "request_id"
118
+
119
+ func WithRequestID(ctx context.Context, id string) context.Context {
120
+ return context.WithValue(ctx, requestIDKey, id)
121
+ }
122
+
123
+ func RequestID(ctx context.Context) string {
124
+ id, _ := ctx.Value(requestIDKey).(string)
125
+ return id
126
+ }
127
+ ```
128
+
129
+ ### io and io/fs
130
+
131
+ ```go
132
+ // io.Reader and io.Writer are the universal interfaces
133
+ // If your function accepts io.Reader, it works with:
134
+ // - files, HTTP bodies, buffers, strings, gzip streams, network connections...
135
+
136
+ func CountLines(r io.Reader) (int, error) {
137
+ scanner := bufio.NewScanner(r)
138
+ count := 0
139
+ for scanner.Scan() {
140
+ count++
141
+ }
142
+ return count, scanner.Err()
143
+ }
144
+
145
+ // Works with anything
146
+ CountLines(os.Stdin)
147
+ CountLines(strings.NewReader("hello\nworld"))
148
+ CountLines(resp.Body)
149
+
150
+ // io/fs for filesystem abstraction (Go 1.16+)
151
+ func LoadTemplates(fsys fs.FS) (*template.Template, error) {
152
+ return template.ParseFS(fsys, "templates/*.html")
153
+ }
154
+
155
+ // Production: os.DirFS("./templates")
156
+ // Tests: fstest.MapFS{"templates/index.html": {Data: []byte("...")}}
157
+ ```
158
+
159
+ ### time
160
+
161
+ ```go
162
+ // Always use time.Duration for durations, never raw int64
163
+ func Retry(attempts int, delay time.Duration, fn func() error) error { ... }
164
+
165
+ // Use time.NewTicker, not time.Tick (Tick leaks)
166
+ ticker := time.NewTicker(30 * time.Second)
167
+ defer ticker.Stop()
168
+
169
+ // Use monotonic clock for elapsed time (time.Since does this automatically)
170
+ start := time.Now()
171
+ doWork()
172
+ elapsed := time.Since(start) // Monotonic, not affected by clock adjustments
173
+
174
+ // Parse and format with the reference time: Mon Jan 2 15:04:05 MST 2006
175
+ t, err := time.Parse("2006-01-02", "2025-03-15")
176
+ s := t.Format(time.RFC3339)
177
+ ```
178
+
179
+ ## Toolchain
180
+
181
+ ### Essential Commands
182
+
183
+ ```bash
184
+ # Build and run
185
+ go build ./... # Compile all packages
186
+ go run ./cmd/myapp # Compile and execute
187
+ go install ./cmd/myapp # Install binary to $GOBIN
188
+
189
+ # Testing
190
+ go test ./... # Run all tests
191
+ go test -race ./... # Run with race detector (always in CI)
192
+ go test -count=1 ./... # Disable test caching
193
+ go test -short ./... # Skip long-running tests
194
+ go test -cover ./... # Show coverage percentage
195
+ go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out
196
+
197
+ # Vetting
198
+ go vet ./... # Official static analysis
199
+
200
+ # Modules
201
+ go mod tidy # Remove unused, add missing dependencies
202
+ go mod verify # Verify dependency checksums
203
+ go mod why <module> # Explain why a dependency is needed
204
+ ```
205
+
206
+ ### Linters
207
+
208
+ ```yaml
209
+ # .golangci-lint.yml — recommended configuration
210
+ linters:
211
+ enable:
212
+ - errcheck # Check that errors are handled
213
+ - govet # Official Go vet checks
214
+ - staticcheck # The gold standard static analyzer
215
+ - unused # Find unused code
216
+ - gosimple # Simplify code
217
+ - ineffassign # Detect ineffectual assignments
218
+ - gocritic # Opinionated style checks
219
+ - revive # Fast, configurable linter
220
+ - misspell # Find common spelling mistakes
221
+ - prealloc # Suggest preallocating slices
222
+ - unconvert # Remove unnecessary type conversions
223
+ - errname # Check error variable naming conventions
224
+ - errorlint # Check error handling idioms
225
+
226
+ linters-settings:
227
+ errcheck:
228
+ check-type-assertions: true
229
+ check-blank: true
230
+ gocritic:
231
+ enabled-tags:
232
+ - diagnostic
233
+ - style
234
+ - performance
235
+ ```
236
+
237
+ ### Makefile
238
+
239
+ ```makefile
240
+ .PHONY: build test lint vet check
241
+
242
+ build:
243
+ go build ./...
244
+
245
+ test:
246
+ go test -race -count=1 ./...
247
+
248
+ lint:
249
+ golangci-lint run
250
+
251
+ vet:
252
+ go vet ./...
253
+ staticcheck ./...
254
+
255
+ check: vet lint test
256
+
257
+ cover:
258
+ go test -coverprofile=coverage.out ./...
259
+ go tool cover -html=coverage.out -o coverage.html
260
+ ```
261
+
262
+ ## When to Use Third-Party Packages
263
+
264
+ Use the stdlib unless:
265
+ - The stdlib genuinely lacks the capability (e.g., database drivers, protocol buffers)
266
+ - A well-maintained package provides significantly better ergonomics for a complex domain (e.g., `sqlc` for SQL, `chi` for routing in complex APIs)
267
+ - The package is from the Go team's extended ecosystem (`golang.org/x/...`)
268
+
269
+ Respected packages in the ecosystem:
270
+ - **Router**: `net/http` (Go 1.22+ patterns), `chi` for complex routing
271
+ - **SQL**: `sqlc`, `pgx`, `database/sql` with driver
272
+ - **Testing**: `go-cmp`, `testify` (use sparingly — stdlib is usually enough)
273
+ - **CLI**: `cobra`, `kong`
274
+ - **Config**: `envconfig`, `viper`
275
+ - **Observability**: `prometheus/client_golang`, `opentelemetry-go`
276
+ - **Concurrency**: `golang.org/x/sync/errgroup`, `golang.org/x/sync/semaphore`