stevenacoffman/go-craft
Go application design: packages, dependency injection, errors, HTTP services, and CLIs.
Invoke when a single Go struct is being used across multiple layers (API response, database persistence, domain entity, CQRS read model) or when a PR adds a field to a shared struct that spans concerns.
Use when deciding how a CLI should accept a setting — flag, environment variable, project or user config file, or system-wide config — and their precedence, or when designing a new CLI tool's configuration system. Covers .env files vs real config files and users retyping flags.
Use when designing or reviewing confirmation UX for a CLI command that deletes, destroys, resets, or purges data or remote resources: whether it needs a prompt, whether --force or --yes is enough, staying scriptable, and catching destruction as a side effect of innocuous changes.
Use when renaming, removing, or deprecating a CLI flag, subcommand, env var, config key, or exit code without breaking scripts, designing a subcommand namespace, or changing output format and machine-readable output. Not for library, RPC, or HTTP API versioning, or purely cosmetic CLI UX.
Use when a CLI must accept a secret or credentials such as a password, API key, or token: choosing between flags, env vars, files, and stdin, reviewing a --password or --token flag, or judging whether shell substitution like $(cat file) is safe. Keeps secrets out of process lists and logs.
Use when you need to know why a Go context was canceled, beyond 'context canceled' or 'deadline exceeded': context.WithCancelCause, context.Cause, WithTimeoutCause. Covers the gotcha where defer cancel() discards the timeout cause, and the time.AfterFunc fix.
Use when storing values with context.WithValue, choosing context key types, or debugging context values that are missing or wrong at runtime. Prevents cross-package key collisions with unexported struct key types or pointer keys, wrapped in WithX and XFromContext accessors.
Invoke when two Go packages must share a type or contract without importing each other's concrete types: where a shared struct should live, or whether to extract a shared package. Chooses among function-typed fields, a local interface, a shared package, or duplicate types plus an adapter.
Invoke when injecting dependencies in Go without an interface per external call, or when tests are heavy with generated mocks: a Dependencies struct of function-typed fields, a New(deps) factory, real wiring in main.go, function literals in tests. Not for many-method dependencies.
Use when structuring a new Go project, replacing a models/controllers/handlers layout, fixing import cycles between layers, or adding a business domain. Gives each domain its own package (order, user, payment); technology packages like http and postgres import domain packages, never the reverse.
Apply when defining a repository interface in a Go DDD project — specifically when deciding where the interface declaration lives in the package tree, how mutation methods must be shaped to prevent transaction leakage, or when reviewing a two-call fetch+save pattern.
Apply when structuring a domain service that depends on external capabilities (payment gateways, third-party APIs, other domain services) — specifically when deciding how to inject dependencies into a domain service and how to separate coordination logic from domain logic.
Apply when classifying a new domain type during modeling or code review — specifically when a developer is uncertain whether a struct should have an identity field (UUID), pointer semantics, and mutable state (entity) or value-type semantics, unexported fields, and immutability (value object).
Use when designing or debugging Go error handling across layers: handlers checking sql.ErrNoRows, pgx, or redis.Nil errors, HTTP and gRPC handlers duplicating storage error checks, (bool, error) returns, driver errors leaking via %w, or choosing %w vs %v in fmt.Errorf.
Invoke when organizing Go packages for a new or existing application: directory structure, where models and types belong, whether to use models/, handlers/, controllers/ directories, or fixing import cycle errors between functional-grouping packages. Not for go.mod setup or formatting.
Invoke when someone is confused about why Go forbids circular imports, or brings a Ruby, Java, Python, or JavaScript module mental model to Go package design and everything depends on everything. Explains the packages-as-layers mental model behind import graph problems, not just the fix.
Invoke when deciding where a database transaction belongs in a Go service — handler, unit-of-work wrapper, repository, or service method — making multiple service operations atomic, or untangling *sql.Tx passed through the call stack. Not for distributed transactions, sagas, or connection pooling.
Invoke when designing Go error types and how errors flow: what fields to include, showing errors to users vs logging for operators, branching on not-found vs validation errors, error codes, wrapping, mapping service errors to HTTP status, or stopping database messages leaking to users.
Invoke when wiring a Go HTTP service's dependency injection from the entrypoint down to handlers — specifically when choosing how to make startup logic testable, how to inject shared handler dependencies, and how to compose both concerns without globals.
Apply when implementing a DDD value object in Go — specifically when deciding on field visibility, constructor shape, method receiver types, and return semantics for any type classified as a value object (immutable, value-comparable, describes a domain concept).
Apply when an exported Go type must implement an interface and you want compile-time conformance checks with var _ Interface = (*T)(nil). Covers when a guard is worth adding and when it is redundant because the type is already used as the interface.
Apply when structuring a Go DDD project and needing compile-time enforcement that one bounded context cannot import another's domain types directly — specifically when organising a monorepo, a multi-service project, or any Go module where domain leakage across context boundaries is a risk.
Invoke when designing the model layer of a Go web app — specifically when handlers need to distinguish "not found" from "server error", or when database driver errors must not leak into handler code.
Invoke when implementing HTML form handling in Go — specifically when validation errors and original input values must travel together through a handler's re-render path.
Invoke when reading form data from an HTTP request in Go, or when deciding which packages belong under internal/ vs at the module root.
Invoke when implementing login or logout handlers in a Go web app, or when auditing session management for session fixation vulnerabilities.
Invoke when writing the render helper for a Go HTML template service — specifically to prevent the partial-200 failure mode where a template error produces corrupted HTML with an incorrect 200 status.
Activate when a developer asks whether to use a DI framework (dig, wire, FX) or how to structure dependency wiring in Go, or when they describe a complex dependency graph and wonder if they need a container.
Use when Go HTTP handlers repeat decode-then-validate boilerplate. Combines JSON decoding and validation in one generic decodeValid[T Validator] helper, with a Valid(ctx) method returning a problems map ready to use as an API error response.
Invoke when Go service or CLI tests use t.Setenv and so cannot call t.Parallel(). Inject a getenv func(string) string into run() instead of calling os.Getenv: pass os.Getenv from main() and a closure in tests. Not when there is no run() function pattern.
Apply when wiring Go HTTP handler dependencies such as a db and logger, choosing between methods on a server struct and maker functions that take explicit dependencies and return http.Handler. Not for middleware construction, NewServer wiring, or request-scoped data.
Apply when making a Go program's startup testable: testing main, calling the program from tests with controlled args, env, and stdio, avoiding flag.Parse and os.Getenv global state that blocks t.Parallel(), and graceful shutdown from tests. Not for handler, middleware, or decoding logic.
Apply when separate mutex-protected Get() and Set() methods lose updates under concurrency, or go test -race passes but shared state is wrong. Get-mutate-Set is a logical race; fix it with closure inversion, Set(func(*T)), holding the lock for the whole read-check-write.
Use when choosing how a Go constructor takes options: functional options, an exposed config struct, or method chaining on a returned *Config. Picks by option count, API stability, and whether callers must store or compose options; functional options are rarely the right choice.
Use when Go database writes across multiple tables or stores must be atomic, or transaction management leaks into service logic: a service holding *sql.DB or sqlc Queries, calling Begin, Commit, or Rollback directly, or nested store transactions that end up as two transactions.
Use when writing, reviewing, or debugging Go code that spawns goroutines: goroutine leaks, bare go func() without a WaitGroup or errgroup, senders blocked on channels, early returns, unbounded fan-out, worker pools, semaphores, bounded concurrency, or backpressure.
Use when a Go service exposes the same operation over HTTP and gRPC, or handlers repeat decode, validate, call, encode per endpoint. Uses transport-free service functions with a generic Wrap adapter; wraps gRPC clients so gRPC status codes become domain errors and protobuf types don't leak.