A TypeScript implementation of the Clean Architecture specified by Robert C. Martin (Uncle Bob).
The Clean Architecture is an architectural pattern (a layered architecture) 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 not by layer 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 (architecture diagram with control flow following Clean architecture diagram (from the book)).
Used building blocks (including DDD tactical patterns):
- Entities
- Value Objects
- ...
- 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.
TODO: package diagram / choosen feature split (catalog, ...) with slice across the different layers.
See documentation.
- 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, ...).
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