Architecture is the set of boundaries that are expensive to change. Consolidate by making the domain explicit and keeping I/O at the edge — not by copying a layered diagram into every CRUD.
Before imposing a style: project-discovery. The code and the teams beat the book.
Expected outcome
- Named boundaries (module, context, process) with dependency rules.
- Changing a business rule does not require rewriting HTTP/DB/UI together without need.
- Complexity accidental drops; the essential complexity stays in the domain.
- Chosen style fits the team (not the other way around).
Diagnosis
- Is the domain the center of risk, or is the risk I/O/ops?
- One team or several with different cadences?
- One deploy or many?
- Do reads and writes have very distinct models?
- Does a ubiquitous language and contexts already exist, or is it stable CRUD?
If the domain is uncertain and the team is small: modularize the monolith. Do not start with microservices.
Styles (when to use)
| Style | Use when | Skill |
|---|---|---|
| Simple layers | CRUD, little domain, one deploy | (honest default; don't force DDD) |
| MVVM | Rich UI, screen state, mobile/desktop | mvvm-architecture |
| Clean / Hexagonal | I/O and frameworks should be pluggable; use case tests without DB | clean-architecture |
| DDD | Complex rules, business language, multiple contexts | ddd |
| Modular monolith | One deploy, multiple domain boundaries | modular-monolith |
| Vertical slice | End-to-end features without global horizontal layers | modular-monolith |
| CQRS | Reads and writes genuinely diverge | cqrs |
| Event-driven | Decouple time and reaction owners | event-driven-architecture |
| Microservices | Team/deploy/scale autonomy with stable domain | microservices-architecture |
Combine: DDD defines what to isolate; Clean/hexagonal how the arrows point; module/service where that runs.
Consolidation (rules)
- Cohesion by reason for change (context), not by technical type (
controllers/,services/,repositories/global ones as the only axis). - Coupling visible: one module imports another's public API — never the internal schema.
- Dependencies point to the domain, not the other way around (DB does not dictate the business entity).
- Cycles between modules are debt. Break them with events, ACL, or a third module.
- Extract microservice afterwards if the boundary exists in the process (same DB, same PR) —
modular-monolith.
ruim: ui → service → db (tudo conhece tudo)
bom: ui → application → domain ← adapters (db, http)
What is not architecture
- Folder
CleanArchitecture.Domainempty with anemic DTOs - MediatR/use case per button without invariant
- “Let's go micro because it scales” without SLO
- Event bus for disguised RPC
Anti-patterns
- Big ball of mud with “let's refactor later”
- Applying DDD + CQRS + ES + micro to the same CRUD
- Mandatory layers in an 80-line feature
- Shared kernel that becomes a dump of everything
- Architecture copied from another product without the same domain
Flow when deciding
project-discovery— what the repo already is.- Name contexts and data owners (
dddif the domain calls for it). - Choose deployment: one process (
modular-monolith) vs several (microservices-architecture). - Adjust dependency arrows (
clean-architecture) only where I/O hinders testing/evolution. - CQRS/events only with concrete pain in reading or decoupling.
Integration with other skills
- DDD:
ddd - Clean/hexagonal:
clean-architecture - Modules in one deployment:
modular-monolith - CQRS:
cqrs - Events:
event-driven-architecture - Services:
microservices-architecture - UI:
mvvm-architecture - HTTP:
api-design
Completion criteria
- Chosen style written in 5 lines (why, what is left out)
- Boundaries and dependency direction observable in the code
- No extra style without corresponding pain
- Aligned with what the repository already does, unless a change is requested