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

  1. Outer adapter implements a port
  2. Use case depends on the port
  3. 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

Small tool or CLI2/5
Domain-rich application5/5
Independent distributed services4/5

Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.

01

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.

02

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.

go
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
Read the code

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.

03

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.

04

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.

05

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.

06

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.