The domain layer is where the software’s value resides and where most of the complexity lies. The domain layer is the home of your business logic rules.It is easier to sell a user interface than a domain layer since users connect to the domain through the presentation. However, it is important to remember that the domain is responsible for solving the problems and automating the solutions; the presentation layer only links users’ actions to the domain.We usually build the domain layer around a domain model. There are two macro points of view on this:
- Using a rich model.
- Using an anemic model.
You can leverage Domain-Driven Design (DDD) to build that model and the program around it. DDD goes hand in hand with rich models, and a well-crafted model should simplify the maintenance of the program. Doing DDD is not mandatory, and you can achieve the required level of correctness without it.
Another dilemma is persisting the domain model directly into the database or using an intermediate data model. We talk about that in more detail in the Data section.Meanwhile, we look at the two primary ways to think about the domain model, starting with the rich domain model.
Rich domain model
A rich domain model is more object-oriented, in the “purest” sense of the term, and encapsulates the domain logic as part of the model inside methods. For example, the following class represents the rich version of a minimal Product class that contains only a few properties:
public class Product
{
public Product(string name, int quantityInStock, int?
id = null)
{
Name = name ??
throw new ArgumentNullException(nameof(name));
QuantityInStock = quantityInStock;
Id = id;
}
public int?
Id { get; init; }
public string Name { get; init; }
public int QuantityInStock { get; private set; }
public void AddStock(int amount)
{
if (amount == 0) { return; }
if (amount < 0) {
throw new NegativeValueException(amount);
}
QuantityInStock += amount;
}
public void RemoveStock(int amount)
{
if (amount == 0) { return; }
if (amount < 0) {
throw new NegativeValueException(amount);
}
if (amount > QuantityInStock) {
throw new NotEnoughStockException(
QuantityInStock, amount);
}
QuantityInStock -= amount;
}
}
The AddStock and RemoveStock methods represent the domain logic of adding and removing stock for the product inventory. Of course, we only increment and decrement a property’s value in this case, but the concept would be the same in a more complex model.The biggest advantage of this approach is that most of the logic is built into the model, making this very domain-centric with operations programmed on model entities as methods. Moreover, it reaches the basic ideas behind object-oriented design, where behaviors should be part of the objects, making them a virtual representation of their real-life counterparts.The biggest drawback is the accumulation of responsibilities by a single class. Even if object-oriented design tells us to put logic into the objects, this does not mean it is always a good idea. If flexibility is important for your system, hardcoding logic into the domain model may hinder your ability to evolve business rules without changing the code itself (it can still be done). A rich model might be a good choice for your project if the domain is fixed and predefined.A relative drawback of this approach is that injecting dependencies into the domain model is harder than other objects, such as services. This drawback reduces flexibility and increases the complexity of creating the models.A rich domain model can be useful if you are building a stateful application where the domain model can live in memory longer than the time of an HTTP request. Other patterns can help you with that, such as Model-View-View-Model (MVVM), Model-View-Presenter (MVP), and Model-View-Update (MVU).If you believe your application would benefit from keeping the data and the logic together, then a rich domain model is most likely a good idea for your project. If you are practicing DDD, I probably don’t have to tell you that a rich model is the way to go. Without DDD notions, achieving a maintainable and flexible rich model is challenging.A rich model can be a good option if your program is built around a complex domain model and persists those classes directly to your database using an object-relational mapper (ORM). Using Cosmos DB, Firebase, MongoDB, or any other document database can make storing complex models as a single document easier than a collection of tables (this applies to anemic models too).As you may have noticed, there are a lot of “ifs” in this section because I don’t think there is an absolute answer to whether a rich model is better or not, and it is more a question of whether it is better for your specific case than better overall. You also need to take your personal preferences and skills into account.Experience is most likely your best ally here, so I’d recommend coding, coding, and coding more applications to acquire that experience.