Why this architecture exists
I don’t arrive at this architecture because it’s fashionable or “cloud-native”.
I arrive here because I keep seeing the same failure modes repeat themselves in enterprise platforms — regardless of tech stack, vendor, or organizational model:
- Failures propagate far beyond where they start
- Shared platforms slowly turn into bottlenecks
- Integrations leak straight into core logic
- Teams are nominally autonomous, but architecturally stuck
What these systems usually have in common isn’t a lack of layers or patterns.
It’s a lack of enforced boundaries.
This architecture combines client/server interaction, gateway-based edge control, cell-based decomposition, and adapter-driven design to make boundaries explicit, enforceable, and hard to bypass.
TL;DR
Layered architecture fails not because layers are wrong, but because boundaries are unenforced. Over time, shortcuts erode structure, business logic leaks outward, and systems become fragile and slow to change. Combining gateways, cell-based decomposition, and adapter-driven design makes boundaries explicit and painful to violate. Adapters protect the domain, cells limit blast radius, and together they turn architecture from a diagram into a system that actively resists decay.
High-level structure
At a system level, the architecture is structured as:
Clients
↓
Gateway (API Gateway / BFF)
↓
Cells (independent runtime units)
└─ Layered + adapter-based internal design
Each architectural pattern addresses a different concern:
- Client/Server – interaction model
- Gateway – edge control and protection
- Cells – fault isolation and scalability
- Adapters – decoupling from dependencies
Together, they form a coherent and evolvable platform architecture.
Client / server interaction
Clients (web, mobile, partner systems, internal tools) interact with the platform using stable, versioned APIs.
Key principles:
- Clients never access internal services or data directly
- Internal topology is hidden
- Contracts evolve independently of implementation
This keeps client change cheap and predictable.
Gateway architecture
The gateway acts as the single controlled entry point into the platform.
Typical responsibilities include:
- Authentication and authorization
- Rate limiting and throttling
- Request routing
- API versioning
- Protocol translation
- Observability (logging, metrics, tracing)
The gateway enforces policy, not business behavior.
No business logic belongs in the gateway.
In larger systems this is often implemented as:
- A shared API Gateway
- One or more BFFs (Backend for Frontend)
Cell-based architecture
The platform is decomposed into cells — autonomous runtime units that include:
- Application services
- Domain logic
- Data storage
- Integration adapters
- Capacity and failure boundaries
Each cell:
- Is deployed independently
- Scales independently
- Fails independently
Traffic is routed to cells based on criteria such as tenant, geography, customer segment, or partition key.
This design turns system-wide failures into contained, local incidents.
Internal structure of a cell
Inside each cell, a layered architecture is combined with ports and adapters.
Inbound Adapters
↓
Application Layer
↓
Domain Layer
↓
Outbound Adapters
This structure is intentional and enforced.
When layered architecture turns into a mess
I’ve seen layered architecture go wrong more times than I can count.
On paper, the boundaries look clean.
In reality, they often blur fast:
- Controllers start calling repositories directly
- Application layers accumulate business rules
- Domain logic leaks into adapters “just for convenience”
- Infrastructure concerns creep upward, one shortcut at a time
What started as a layered architecture quietly turns into a layered illusion.
Everyone still talks about “the domain layer”, but no one can really point to where it begins or ends.
Common architectural smells — and the rules they violate
When layered architecture starts to decay, the symptoms are rarely dramatic.
What I’ve learned is that every “small” shortcut almost always breaks a very specific architectural rule.
Making those rules explicit is what turns architecture from intention into constraint.
Smell: Controllers calling repositories directly
Violated rule: All business interactions must go through application use cases.
This bypasses orchestration, authorization, and consistency boundaries.
It turns the UI or API layer into an accidental application layer.
Smell: “Just this once” logic in adapters
Violated rule: Adapters translate — they do not decide.
Inbound and outbound adapters exist to isolate the core from the outside world.
Once business rules appear here, the direction of dependency is already broken.
Smell: An application layer that keeps growing
Violated rule: The application layer orchestrates behavior; it does not contain it.
When business rules accumulate here, the domain is being hollowed out and the model loses meaning.
Smell: Domain objects depending on frameworks or SDKs
Violated rule: The domain must be technology-agnostic.
Framework annotations, persistence concerns, or vendor SDKs in the domain are a direct breach of this rule — and they make change expensive later.
Smell: Shared utility packages used everywhere
Violated rule: Reuse must not create hidden coupling.
Utilities that “everyone depends on” quickly become informal shared infrastructure with no clear ownership or lifecycle.
Smell: Developers unsure where new logic belongs
Violated rule: Every change must have an obvious home.
When placement becomes a discussion rather than a decision, boundaries are no longer doing their job.
Smell: Architecture diagrams that look right but don’t match the code
Violated rule: Architecture must be enforced, not just documented.
When diagrams and reality diverge, the architecture has already lost authority — even if no one says it out loud.
None of these violations are dramatic on their own.
Together, they are a reliable signal that the system is no longer protecting its core.
The problem isn’t layers — it’s missing boundaries
The issue isn’t that layered architecture is flawed.
The issue is that layers without ownership and enforcement are just naming conventions.
Without clear rules:
- Layers become suggestions instead of constraints
- Violations feel harmless in the moment
- Short-term delivery pressure overrides structure
- The architecture erodes silently
By the time the pain is visible, the boundaries are already gone.
## Architecture principles I rely on
When I say “layers” or “adapters”, I’m not talking about boxes in a diagram.
I’m talking about constraints that make the right thing easy and the wrong thing uncomfortable.
These are the principles I use to keep boundaries real.
1. Use cases are the only entry point to business behavior
All business interactions must be expressed as application use cases (commands/queries).
No controller, UI, scheduler, or consumer should reach into repositories or domain objects directly.
Implication: request handlers should be thin; orchestration lives in the application layer.
2. Adapters translate — they do not decide
Adapters exist to isolate the core from protocols, vendors, and transport concerns.
They can validate, map, normalize, and enrich context — but they must not contain business rules.
Implication: if business logic appears in an adapter, the boundary is leaking.
3. The application layer orchestrates; the domain contains behavior
The application layer coordinates work: transactions, authorization, consistency, workflows.
Business rules and invariants live in the domain model.
Implication: if the application layer keeps growing, it’s usually absorbing domain behavior that should be modeled explicitly.
4. The domain is pure and technology-agnostic
The domain must not depend on frameworks, persistence, SDKs, or vendor APIs.
If the domain can’t be unit tested without infrastructure, it’s not isolated enough.
Implication: keep annotations, mappers, repositories, and clients outside the domain.
5. Reuse must not create hidden coupling
Shared libraries are allowed, but only when they do not become a “global dependency magnet”.
If everyone depends on a utility package, it needs ownership, versioning, and a lifecycle — just like a product.
Implication: prefer duplication over accidental coupling when ownership is unclear.
6. Every change must have an obvious home
A healthy architecture makes it obvious where new logic belongs.
If engineers debate placement, boundaries are unclear or principles aren’t enforced.
Implication: clarify responsibilities until “where does this go?” becomes a non-question.
7. Architecture is enforced, not just documented
Diagrams are useful, but they don’t create architecture — constraints do.
If the code and the diagram diverge, the code wins, and the architecture has already lost authority.
Implication: enforce boundaries via reviews, tests, and automation (where possible).
8. Own boundaries through cells
Cells exist to make ownership and failure boundaries explicit.
A cell owns its runtime, dependencies, and data. Crossing boundaries must be intentional and visible.
Implication: if teams can’t evolve independently, the cell boundaries are wrong (or not real).
Why I combine layers with adapters
This is why I don’t rely on layers alone.
Adapters make boundaries explicit and enforceable:
- Inbound adapters define how the system may be called
- Outbound adapters define how the system may depend on others
- Everything in between is forced to stay honest
It becomes obvious when something is in the wrong place — and just as obvious when someone tries to bypass a layer.
Cells make boundary violations visible
Cell-based architecture adds another level of pressure in the right direction.
When each cell owns its runtime, data, and dependencies:
- Boundary violations hurt faster
- Coupling becomes visible immediately
- “Quick fixes” stop being quick
You can’t casually reach across layers or cells without feeling the cost.
Inbound adapters
Inbound adapters translate external interaction models into internal use cases.
Examples include:
- REST controllers
- GraphQL resolvers
- Event consumers
- Batch job handlers
They handle protocols, validation, mapping, and context propagation — but contain no business logic.
Application layer
The application layer orchestrates use cases.
Its responsibilities include:
- Coordinating domain operations
- Managing transactions
- Enforcing authorization rules
- Handling consistency boundaries
The application layer is procedural and explicit, but deliberately thin.
Domain layer
The domain layer contains the core business logic:
- Entities and aggregates
- Value objects
- Domain services
- Business rules and invariants
The domain layer has no dependency on infrastructure, frameworks, or vendors.
This is the layer the architecture is designed to protect.
Outbound adapters
Outbound adapters connect the application and domain layers to external systems such as:
- Databases
- Legacy platforms
- External APIs
- Message brokers
They handle protocol translation, data mapping, retries, and resilience patterns.
The domain remains unaware of how integration and persistence are implemented.
Data ownership and integration
Each cell owns its data.
- No shared databases between cells
- No implicit coupling
- Explicit contracts for communication
Cross-cell interaction is:
- Event-driven where possible
- API-based where necessary
This enforces ownership, autonomy, and clarity.
Governance model
The architecture supports guardrail-based governance:
- Central architecture defines principles, standards, and boundaries
- Teams own cells, internal design decisions, and deployment cadence
Architecture becomes an enabler for change rather than a constraint.
The real lesson
Layered architecture is not enough by itself.
Without explicit boundaries, enforcement, and ownership, layers decay into convention — and convention decays under pressure.
Adapters make boundaries explicit.
Cells make boundary violations painful.
Together, they turn architecture from a diagram into a system that pushes back.
Good architecture should make the right thing the easy thing, and the wrong thing uncomfortable.
That’s the difference between drawing boxes and designing systems.