Clean code: production contracts · 3 MIN READ
Package APIs should reveal their change boundary
Use narrow consumer contracts and tests that make accidental coupling visible.
A repository with packages named controllers, managers and utils can still have no useful boundaries. Ask which behavior changes together and which caller is allowed to depend on it. Go import cycles are prohibited, but a cycle-free graph can still expose every storage struct and force unrelated features to change together. Test the public behavior of a capability, not its current arrangement of private helper calls.
clean-code-production · A MAP OF THE IDEA
- Name capability
- Keep implementation private
- Test public contract
- Integrate real adapter
Useful when
- Use narrow consumer contracts and tests that make accidental coupling visible.
- Makes the owner and the failure boundary explicit.
Watch out
- Creating an interface for every struct increases maintenance without adding a boundary.
- Run native service and failure tests in your own development environment.
Implementation & operation cost: Additional state and tests must be justified by the demonstrated failure; this lesson is not a requirement for every Go program.
The contract and the failure
A repository with packages named controllers, managers and utils can still have no useful boundaries. Ask which behavior changes together and which caller is allowed to depend on it. Go import cycles are prohibited, but a cycle-free graph can still expose every storage struct and force unrelated features to change together. Test the public behavior of a capability, not its current arrangement of private helper calls.
Work through the example
Return domain values rather than leaking driver-specific result types across every layer. Export the smallest useful API, document error and ownership contracts, and use external package tests where appropriate to exercise what users can actually call. Use integration tests for serialization, SQL and network semantics that a fake cannot reproduce.
package main
import (
"context"
"fmt"
)
type Order struct{ ID string }
// The consumer needs this capability, not every method of a database client.
type OrderReader interface {
Find(context.Context, string) (Order, error)
}
type Memory struct{ orders map[string]Order }
func (m Memory) Find(_ context.Context, id string) (Order, error) {
value, ok := m.orders[id]
if !ok {
return Order{}, fmt.Errorf("order absent")
}
return value, nil
}
func describe(ctx context.Context, reader OrderReader) {
o, err := reader.Find(ctx, "7")
fmt.Println(o.ID, err)
}
func main() { describe(context.Background(), Memory{map[string]Order{"7": {ID: "7"}}}) }The reader contract is declared near its consumer and contains one capability. The adapter satisfies it implicitly. In a real module, put private implementation under internal where the import rules fit the intended boundary. The tiny memory implementation is a test seam, not proof that a database adapter follows the same transaction and cancellation behavior.
Production decisions
Creating an interface for every struct increases maintenance without adding a boundary. A mock that asserts a private call order can reject a harmless refactor and miss a broken transaction. Avoid a shared models package that makes every subsystem depend on every field. Test contract cases against both a fake and the real adapter when substitutability matters.
Explain it in an interview
Should a consumer depend on a full database client when it only reads one order? Prefer the narrow capability the consumer needs; keep driver and transaction details in adapters and verify those with real integration tests. Describe the state before the operation, its owner, the failure between steps and the evidence you would test. Distinguish what this example demonstrates from the infrastructure a production service still needs.
TAKE THIS WITH YOU
Use narrow consumer contracts and tests that make accidental coupling visible. Prefer the narrow capability the consumer needs; keep driver and transaction details in adapters and verify those with real integration tests.
Check your understanding
Should a consumer depend on a full database client when it only reads one order?
Go to the source
Progress stays in this browser. No account needed.