Architecture overview
An overview of a layered, multi-repository architecture designed to separate concerns, ease evolution, and scale with greater control.
Goal of this architecture
The proposed architecture aims to solve a common problem in systems that grow:
- too many responsibilities mixed together
- difficulty deploying without risk
- external integrations polluting the domain
- growing complexity in both frontend and backend
- coupling between teams and components
To address this, the system is organized into layers and services with clearer boundaries.
The idea is not to add complexity for its own sake, but to distribute complexity to places where it is more manageable.
The 7 layers
1. Presentation
This is where the user interface lives. It renders screens, manages state, validates forms on the client side, and consumes APIs.
2. API Gateway
This is the main entry point. It handles authentication, routing, rate limiting, logging, and other cross-cutting concerns.
3. BFF
It aggregates and transforms data for the UI. It reduces frontend complexity and prevents the client from knowing too many internal details.
4. Microservices
They contain business logic organized by domains or bounded contexts. This is where the rules, use cases, domain events, and persistence live.
5. Event Bus
It enables asynchronous communication between services through events. It promotes temporal decoupling and the construction of distributed processes.
6. ACL
It protects the domain from external or legacy systems. It translates models and encapsulates adapters so the business core stays clean.
7. Data / External Systems
This includes databases, caches, legacy systems, and third-party providers.
Why separate into layers?
Separating into layers allows for:
Clarity of responsibilities
Each part of the system has a more specific focus.
Lower coupling
The frontend doesn’t need to know the internal details of each microservice. Domain services shouldn’t depend directly on the model of a legacy ERP.
Technical scalability
You can scale different components according to their real needs.
Organizational scalability
Teams can work on clearer areas, with well-defined ownership.
Controlled evolution
Changing one piece has less impact on the others when boundaries are well defined.
Communication between layers
In this architecture, two major forms of communication coexist:
Synchronous communication
It is used when a layer needs an immediate response. For example:
- frontend → gateway
- gateway → BFF
- BFF → microservices
It usually happens over HTTP or GraphQL/REST.
Asynchronous communication
It is used when a service publishes a fact and others react without needing to respond immediately.
For example:
ms-orderspublishesOrderPlaced- inventory reacts
- notifications react
- analytics reacts
Both coexist because they solve different needs.
Multi-repo: why separate repositories
The architecture is designed around multiple repositories, one per layer or per main service.
This allows for:
- deployment independence
- clearer ownership
- smaller size per repository
- better isolation by domain
But it also requires:
- shared standards
- versioning agreements
- discipline with contracts
- control over the shared kernel
Multi-repo is not automatically better; it is a decision that should accompany a real need for separation.
Principles that support the architecture
Separation of concerns
Each layer solves a different problem.
Protected domain
Business rules must live in the right place and not be polluted by external models.
Explicit contracts
Communication between parts of the system must be defined and understandable.
Independent evolution
Each part should be able to change with as little friction as possible.
Observability and security by design
They shouldn’t be added at the end as a patch.
What this architecture does not mean
It’s important to avoid some misinterpretations.
It doesn’t mean everything must be a microservice
Separate when there is a clear boundary and a real need.
It doesn’t mean more layers is always better
Each layer must justify the value it provides.
It doesn’t mean complexity disappears
Complexity doesn’t disappear; it is organized better.
It doesn’t mean the shared kernel can grow without control
Sharing too much can recreate strong coupling.
How to work through this section
We recommend moving forward like this:
- Frontend
- API Gateway
- BFF
- Microservices
- Event Bus
- ACL
- Data / External Systems
- Shared Kernel and contracts
- Repositories
Summary
This architecture organizes the system into layers with distinct responsibilities to promote clarity, scalability, and controlled evolution. It doesn’t seek to impose complexity, but to place each type of complexity where it can be handled best.