Skip to content

🏗️ A clean architecture example to implement testable and evolutive systems

License

Notifications You must be signed in to change notification settings

adbayb/clean-architecture

Repository files navigation


🏗️ Clean Architecture

A clean architecture example to implement testable and evolutive systems


✨ Features

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.


🏗️ Architecture

Overview

TODO:

  • Functional view (main modules and features).
  • Architecture diagram with control flow following Clean architecture diagram (from the book:

Component diagram

Modules

TODO: package diagram / choosen feature split (catalog, ...) with slice across the different layers.

A special module: the Shared Kernel.

See documentation.

Layers

Clean Architecture Layers

  • 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.

Components

Used building blocks (including DDD tactical patterns):

  • Entities
  • Value Objects
  • ...

💬 Miscellaneous

Onion, hexagonal, clean architecture and domain-driven design… what're the differences?

  • 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).

How the number of layers can impact accidental coupling?

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:

Hexagonal layers

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.


📚 Resources


✍️ Contribution

We're open to new contributions, you can find more details here.


📖 License

MIT


Footnotes

  1. 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.

  2. 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.

  3. 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

About

🏗️ A clean architecture example to implement testable and evolutive systems

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published