Go architectures · 3 MIN READ
Clean and Onion: dependencies point toward policy
Keep domain policy independent of framework and database details.
Keep domain policy independent of framework and database details. Clean and Onion designs place domain rules near the center and technology at the edge. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.
Architecture · A MAP OF THE IDEA
- Outer adapter implements a port
- Use case depends on the port
- Domain computes the price
Useful when
- Business policy outlives transport and storage choices
- Use cases need isolated tests
Watch out
- More boundaries require deliberate mapping
- A diagram cannot enforce business invariants
Implementation & operation cost: Medium to high
Fit depends on the task
Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.
The design decision
Clean and Onion designs place domain rules near the center and technology at the edge. Source imports point toward stable policy even when a runtime call goes outward through an interface. These related styles are not identical diagrams, and neither prescribes a Go folder template.
A complete Go example
Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.
package main
import (
"fmt"
)
type Product struct{ Price int }
func (p Product) Discount(percent int) int { return p.Price * (100 - percent) / 100 }
type Prices interface{ Find(string) Product }
type Quote struct{ prices Prices }
func (q Quote) Total(id string) int { return q.prices.Find(id).Discount(10) }
type MemoryPrices map[string]Product
func (m MemoryPrices) Find(id string) Product { return m[id] }
func main() {
q := Quote{prices: MemoryPrices{"book": {Price: 200}}}
fmt.Println(q.Total("book"))
}
// Output:
// 180
Product performs policy without importing storage. Quote depends on its own Prices contract. The outer MemoryPrices implementation satisfies it implicitly, and main wires the concrete choice. The inward source dependency differs from the runtime lookup direction.
Tradeoffs and the simpler alternative
Model missing products and invalid discounts before using this mechanism in production. Do not create a DTO, interface and mapper for every struct automatically. For a tiny CRUD endpoint, a focused handler and explicit SQL may be easier to change.
Distinguish imports from runtime calls
The dependency rule concerns source imports: policy types do not import a web framework or database driver. At runtime a use case can call an adapter through a consumer-owned interface even though the adapter imports the policy package. Wire concrete adapters in the composition root. Entities express invariants; application services coordinate a use case. Request validation and serialization stay at the outside, while domain validation remains enforceable when the same use case is called by a job or CLI.
Avoid an interface for every concrete type
A constructor returning a concrete type is often appropriate. Introduce an interface where a consumer needs a replaceable capability, especially at IO and clock boundaries. Do not create a BaseRepository or service factory hierarchy just to imitate a class-oriented diagram. A pure calculation needs no repository. Repeated DTO conversions should protect a real boundary; when two layers use an identical stable value, copying every field solely to preserve a diagram adds cost without independence.
Prove independence with a focused test
Construct the application service with an in-memory contract implementation and a controlled clock. Check the invariant, failure result and absence of unwanted side effects. Then verify the production adapter separately against its real storage contract. If starting a router, database and message broker is required to test a price rule, policy still depends on infrastructure somewhere. Clean architecture suits long-lived business rules, while a short one-purpose tool may be clearer with fewer boundaries.
TAKE THIS WITH YOU
Source dependencies point inward toward policy. Model missing products and invalid discounts before using this mechanism in production. Do not create a DTO, interface and mapper for every struct automatically. For a tiny CRUD endpoint, a focused handler and explicit SQL may be easier to change.
Check your understanding
Which statement best describes Clean and Onion: dependencies point toward policy?
Go to the source
Progress stays in this browser. No account needed.