Properly organizing the code and specially the domain logic is crucial for the maintainability of a project.
Personally, it reminds me of a book store or library: If we put a book in the wrong shelve, the people looking for the book will not find it, it will be lost, another copy of the book will be ordered, catalogued and stored in some other place, hopefully in the correct place where people will find it and therefore use it.
It happens the same with our code, if we don’t have a clear place to put a specific code unit, the developers (our colleagues or even one self) will not know of its existence, they will think it does not exist, they will create it again (wasting time, wasting resources, duplicating code, …), they will create it slightly differently, introducing inconsistency throughout the code base.
The folders we use in the project structure should correspond to bounded contexts, which correspond to business concepts, and in them there should be a clear functional separation, a separation per code unit role.
Domain logic organizational patterns
There are, mainly, 3 patterns to organize domain logic:
- Transaction script
We use Procedural Programming and we have a single procedure for each action a user can do. This doesn’t mean, however, that the procedure has all logic end to end.
This is a simple way to organize the code and the possible actions, but it becomes insufficient as complexity grows, as it is not good in preventing code duplication and can, therefore, lead to duplicated logic.
- Domain Model
We use Object Oriented Programming and we build a model of our domain which is organized around the nouns in our domain. Thus, a noun corresponds to an object, its characteristics are the object properties and what it can do its the object API (public methods).
The big advantage of this strategy is that allows us to handle increasingly complex logic in a well organized and maintainable way.
- Table Module
This organization type is a middle ground between Transaction Script and Domain Model. It organizes the domain logic around tables instead of domain objects or straight procedures.
It provides better structure than a Transaction Script, and allows for better prevention of code duplication, but it doesn’t provide the mechanisms for the patterns, techniques and strategies that a domain model provides through OOP.
An option to further organize the domain, is to split the domain layer in two:
- Service layer
The UI layer (ie. Controllers) interacts with the domain only through the service layer, which provides a clear API to interact with the domain.
The services in this layer can be just façades, simply forwarding the calls to the domain objects, or they can hold most of the business logic, making the domain objects very simple, maybe even anaemic.
An intermediate commitment might be to put the logic specific to a use case in the service layer and the logic common to more than one use case in the domain objects (entities).
- Model Layer
In this layer we place the objects that reflect something relevant in the domain (entities), and eventually the accessory functional classes (ie. value objects, enum objects that reflect the state of an entity property, …)