Why I'm Trying Clean Architecture in 2022
TL;DR — Clean Architecture buys testability and replaceability at the cost of indirection. Worth it on services that outlive multiple frameworks; overkill on a 6-month CRUD app. June walks through implementing it in Go and Laravel 9 — same patterns, different ergonomics.
After May’s workflow automation theme, back to product code. June’s theme is Clean Architecture — a layering pattern that’s been argued about for 10 years. I’m going to walk through implementing it in Go and Laravel 9, not because either language “needs” it, but because the patterns translate and the differences are instructive.
This post is the why before the how.
What Clean Architecture is
Robert Martin’s 2012 pitch. Concentric layers: Entities at the core, Use Cases around them, Adapters (HTTP, gRPC, DB drivers) around that, Frameworks at the edge. Dependencies point inward only.
Practically:
- Entities: the domain. Plain types with business rules. No imports of framework code.
- Use Cases: orchestrate entities. Pure functions where possible. Inject dependencies through interfaces.
- Adapters: implementations of those interfaces. HTTP handler, Postgres repository, Kafka consumer.
- Frameworks: Gin, Echo, Laravel — the outer ring. Replaceable.
The rule: inner layers don’t import outer ones. Entities never import sqlx. Use cases never import gin. The framework can be swapped without touching the domain.
What it actually buys
Three real wins:
Testability. Use cases are unit-testable without a database, HTTP server, or framework. Pass in fake repositories that implement the interface. Tests run in milliseconds.
Replaceability. Want to swap Gin for chi? Adapters change; use cases don’t. Want to migrate Postgres → CockroachDB? Repository implementation changes; use cases don’t. Real change is rare; when it happens, it’s contained.
Clarity. Business rules live in one place. New engineers find “what does this app do?” by reading entities + use cases, not by tracing controllers through framework magic.
What it costs
Honesty:
- Indirection. Three or four files where a Rails app has one. Reading “create a user” goes through entity → use case → repository interface → repository impl → HTTP adapter.
- Boilerplate. Interfaces, DTO conversions, dependency wiring. Some of this is necessary friction; some is over-engineering.
- Onboarding friction. New hires from a non-architected codebase will ask “why so many files?” Worth answering once well.
- Diminishing returns at small scale. A 6-month internal admin tool doesn’t benefit. A 5-year payment service does.
When it’s worth it
Concrete heuristics:
- Service expected to outlive the current framework choice (>3 years)
- Multiple developers, possibly rotating
- Business rules complex enough to be worth isolating
- Tests are a priority
- Multiple input/output adapters (HTTP + worker + CLI)
When it’s NOT worth it:
- Throwaway scripts
- Tiny CRUD apps where the framework IS the architecture
- Prototypes
- Solo developer projects under a year long
- “We might split this later” — you won’t
What June covers
Going to implement the same domain twice — a small subscription billing service — in two languages:
- Jun 3: Layers explained
- Jun 6: Go project layout
- Jun 8: DI in Go without a framework
- Jun 10: Domain entities in Go
- Jun 13: Repositories in Go
- Jun 15: Use cases in Go
- Jun 17: Adapters in Go
- Jun 20: Laravel 9 project layout
- Jun 22: Laravel service container for DI
- Jun 24: Eloquent vs domain models
- Jun 27: Testing Clean Architecture
- Jun 29: Month retro
The honest framing
Clean Architecture is over-evangelized. It’s also a useful set of habits. June’s posts are how I actually apply it in 2022 — selectively, pragmatically, with explicit cost/benefit per pattern. Don’t read it as a manifesto.
The patterns translate across languages. Go has interfaces baked in; Laravel has a service container that does the wiring; the layering ideas are the same.
Common Pitfalls
Implementing every pattern in the book on a small service. Pick what helps; skip what doesn’t.
Treating it as “the right way” instead of one option. It’s a tool. So is “just call the database from the handler.”
Layer-pure code that’s hard to debug. When every line goes through three interfaces, debugging becomes a maze. Pragmatic exceptions are fine.
Use cases that are 1:1 with handlers. If your “create user” use case does nothing the handler couldn’t do, you’ve added boilerplate for nothing.
Wrapping Up
Clean Architecture has real wins on long-lived services. Implementation cost is real too. June walks the trade-offs. Friday: the four layers and what each is responsible for.