A TypeScript implementation of the Clean Architecture specified by Robert C. Martin (Uncle Bob).
The Clean Architecture is an architectural pattern that enables the creation of loosely coupled and testable systems by enforcing a clear separation of concerns. This separation isolates the business rules from the details (such as frameworks and external dependencies like databases), making the system more maintainable and evolutive.
Following the "decompose by subdomain" pattern, a modular architecture has also been implemented to encapsulate and group all concerns from presentation to data per bounded context1. This modular monolith approach enables decision autonomy within a module boundary2 and the creation of self-contained systems centered around business capabilities3.
Furthermore, drawing inspiration from the Vertical Slice Architecture and the package by feature/component pattern, top-level directories within a module (excluding the shared
folder) are centered around business features3 and, optionally, around actors for entities coupled to gateways.
It allows not only to scream the application intent allowing better discoverability from the domain point of view but also to create cohesive and loosely-coupled components.
Second-level directories and the shared
directory are organized following the clean architecture layers to enforce/materialize the dependency rule and bring clarity about each layer scope.
TODO:
- Functional view (main modules and features).
- Architecture diagram with control flow following Clean architecture diagram (from the book:
TODO: package diagram / choosen feature split (catalog, ...) with slice across the different layers.
See documentation.
- Enterprise Business Rules (Entities): TODO.
- Application Business Rules (Use Cases): TODO.
- Interface Adapters: Concrete implementation of ports folder that can include entity gateways, interface for data source implemented framework side, other repository/service gateways and GUI design pattern implementation (MVC, MVVM, MVP, ...).
- Frameworks & Drivers: React views (and hooks), data source (including HttpDataSource to make fetch calls with error management, database client (Redis, SQL, MongoDB, ...), ...), ... TODO (include hosts (main component orchestrator)).
- Hosts: Act like the configurator instance in the Hexagonal Architecture. Under the Clean Architecture, the host layer is the outermost layer. It includes the initial entry point of the system called the main component in the Clean Architecture book (in the "Main Component" chapter). This layer is not depicted in the diagram shown above. The main component is on the driver side (for example, Web UI, CLI, Back-end server, ...) and is responsible to instantiate inner layers.
Used building blocks (including DDD tactical patterns):
- Entities
- Value Objects
- ...
- Hexagonal architecture by Alistair Cockburn in 2005: it introduces a kind of plugin architecture with a ports and adapters architecture so that external and internal hexagon are not tightly coupled.
- Onion architecture by Jeffrey Palermo in 2008: it defines several layers within and outside the hexagon with clear responsibilities and dependency direction.
- Clean architecture by Uncle Bob in 2012: it tries to mix the best of all previous architectures with some subtle semantic differences ("domain model" becomes "entity", …). It puts use cases in the center of the application making it screamingly clear what the application does.
Though these architectures all vary in their details, they're very similar: same objective (separation of concerns) by dividing the software into layers (each has at least one layer for the business logic and other for the externals).
They're means (architecture patterns) that can be used to design concretely domain-driven system (tactical domain-driven design).
Uncle Bob explains it well in his book. To quote him directly:
A simpler approach that some people follow for their ports and adapters code is to have just two source code trees:
- Domain code (the "inside")
- Infrastructure code (the "outside")
This maps on nicely to the diagram (Figure 34.9) that many people use to summarize the ports and adapters architecture, and there is a compile-time dependency from the infrastructure to the domain:
This approach to organizing source code will also work, but be aware of the potential trade-off.
It’s what I call the "Périphérique antipattern of ports and adapters". The city of Paris, France, has a ring road called the Boulevard Périphérique, which allows you to circumnavigate Paris without entering the complexities of the city.
Having all of your infrastructure code in a single source code tree means that it’s potentially possible for infrastructure code in one area of your application (e.g., a web controller) to directly call code in another area of your application (e.g., a database repository), without navigating through the domain. This is especially true if you’ve forgotten to apply appropriate access modifiers to that code.
- Clean Architecture original blog post.
- Clean Architecture talk (video).
- Clean Architecture book summary.
- Domain-Driven Design tactical design and Clean Architecture (video).
- Pull-based vs push-based approach managing use case output or why presenters (push-based) should be preferred to returned use case values (pull-based). TLDR; Communication from Controller to Presenter is meant to go through the application layer, making the Controller do part of the Presenters job is likely a domain/application leak.
- Single responsibility principle and mixing presenter/controller. TLDR; Not mixing them allows better flexibility later if some other ways of displaying data (i.e. presenters) should be supported (Web, CLI, JSON, ...).
- The "Real" Repository Pattern in Android.
We're open to new contributions, you can find more details here.
Footnotes
-
In this repository, a bounded context is implemented by one module, so a bounded context is equivalent to a module here. However, it's not always the case since a bounded context is not strictly equivalent to a module. Indeed, while a module is a technical-oriented concept that defines logical boundaries in the code, a bounded context is a business-oriented one (domain-driven design tactical pattern) that represents a cohesive area of the business domain. A module is a technical enabler to implement a bounded context, which can contain one or multiple modules. ↩
-
By its standalone nature, a module enables more easily local decisions such as which architecture makes the most sense depending on the nature of the business and its complexity. For example, a module A with no or little business logic can implement a non-layered architecture (e.g. MVC, transaction scripts, ...) while a module B with more extensive and complex business rules can use a layered-like architecture to allow better separation of concerns (e.g. Clean Architecture, Onion Architecture, ...). In the repository, we're focusing on the clean architecture. Consequently, each module implements this architecture. ↩
-
A feature represents solution functionality that delivers business value while a capability represents larger solution functionality (generally a subdomain) grouping multiple features together in a cohesive way. ↩ ↩2