Check out my other repositories:
The main emphasis of this project is to provide recommendations on how to design software applications. This readme includes techniques, tools, best practices, architectural patterns and guidelines gathered from different sources.
Code examples are written using NodeJS, TypeScript, NestJS framework and Slonik for the database access.
Patterns and principles presented here are framework/language agnostic. Therefore, the above technologies can be easily replaced with any alternative. No matter what language or framework is used, any application can benefit from principles described below.
Note: code examples are adapted to TypeScript and frameworks mentioned above.
(Implementations in other languages will look differently)
Everything below is provided as a recommendation, not a rule. Different projects have different requirements, so any pattern mentioned in this readme should be adjusted to project needs or even skipped entirely if it doesn't fit. In real world production applications, you will most likely only need a fraction of those patterns depending on your use cases. More info in this section.
This is an attempt to combine multiple architectural patterns and styles together, such as:
And many others (more links below in every chapter).
Before we begin, here are the PROS and CONS of using a complete architecture like this:
This is a sophisticated architecture which requires a firm understanding of quality software principles, such as SOLID, Clean/Hexagonal Architecture, Domain-Driven Design, etc. Any team implementing such a solution will almost certainly require an expert to drive the solution and keep it from evolving the wrong way and accumulating technical debt.
Some practices presented here are not recommended for small-medium sized applications with not a lot of business logic. There is added up-front complexity to support all those building blocks and layers, boilerplate code, abstractions, data mapping etc. Thus, implementing a complete architecture like this is generally ill-suited to simple CRUD applications and could over-complicate such solutions. Some principles which are described below can be used in smaller sized applications, but must be implemented only after analyzing and understanding all pros and cons.
Diagram is mostly based on this one + others found online
In short, data flow looks like this (from left to right):
Each layer is in charge of its own logic and has building blocks that usually should follow a Single-responsibility principle when possible and when it makes sense (for example, using Repositories only for database access, using Entities for business logic, etc.).
Keep in mind that different projects can have more or less steps/layers/building blocks than described here. Add more if the application requires it, and skip some if the application is not that complex and doesn't need all that abstraction.
General recommendation for any project: analyze how big/complex the application will be, find a compromise and use as many layers/building blocks as needed for the project and skip ones that may over-complicate things.
More in details on each step below.
This project's code examples use separation by modules (also called components). Each module's name should reflect an important concept from the Domain and have its own folder with a dedicated codebase. Each business use case inside that module gets its own folder to store most of the things it needs (this is also called Vertical Slicing). It's easier to work on things that change together if those things are gathered relatively close to each other. Think of a module as a "box" that groups together related business logic.
Using modules is a great way to encapsulate parts of highly cohesive business domain rules.
Try to make every module independent and keep interactions between modules minimal. Think of each module as a mini application bounded by a single context. Consider module internals private and try to avoid direct imports between modules (like importing a class import SomeClass from '../SomeOtherModule') since this creates tight coupling and can turn your code into a spaghetti and application into a big ball of mud.
Few advices to avoid coupling:
This ensures loose coupling, refactoring of a module internals can be done easier because outside world only depends on module's public interface, and if bounded contexts are defined and designed properly each module can be easily separated into a microservice if needed without touching any domain logic or major refactoring.
Keep your modules small. You should be able to rewrite a module in a relatively short period of time. This applies not only to modules pattern, but to software development in general: objects, functions, microservices, processes, etc. Keep them small and composable. This is incredibly powerful in a constantly changing environments of software development, since when your requirements change, changing small modules is much easier than changing a big program. You can just delete a module and rewrite it from scratch in a matter of days. This idea is further described in this talk: Greg Young - The art of destroying software.
Code Examples:
Read more:
Each module consists of layers described below.
This is the core of the system which is built using DDD building blocks:
Domain layer:
Application layer:
Note: different implementations may have slightly different layer structures depending on applications needs. Also, more layers and building blocks may be added if needed.
Application Services (also called "Workflow Services", "Use Cases", "Interactors", etc.) are used to orchestrate the steps required to fulfill the commands imposed by the client.
Application services:
$ claude mcp add domain-driven-hexagon \
-- python -m otcore.mcp_server <graph>