Anemic Domain Model: A Detailed Review

[tr] Türkçe Oku

2023-06-15

The Anemic Domain Model is a software design pattern where most of the domain logic is found in services and data transfer objects, often obscuring functionality and behavior, and is typically used for data transmission only. This term was first defined by Martin Fowler as a negative example of a design pattern.

A typical example relies on the ability of objects to store data, but the business logic resides in services and other external resources. Therefore, this model usually deals with the functionality of the data access layer and often lacks business logic.

Structural Review

It usually consists of the following components:

  1. Entity/Model: Objects that store data. These objects typically only have simple get and set methods and do not have their own business logic.

  2. Service: The business logic is found here. Services are used to implement business rules and establish relationships between different models.

  3. DTO (Data Transfer Object): It is used to transfer data between layers or services.

Advantages and Disadvantages

It has some advantages:

  1. Code Readability: Since the domain model is very simple, it may be easier to understand the code initially.

  2. Development Speed: As the model only handles data storage and transmission operations outside of the business logic, rapid development can be achieved.

  3. Lower Technology Dependency: Since there is a clear distinction between the data access layer and the business layer, technology changes can become less complex.

However, there are some significant disadvantages to this model:

  1. Alienation from Business Logic: Since the business logic is separated from the model, it can be difficult to understand what the model represents.

  2. Low Flexibility: Changes made to the model often affect numerous services. This makes changes more complicated and the addition of new features more difficult.

  3. Code Complexity: When business logic is distributed across multiple services, the code can become more complex and difficult to manage.

An Anemic Domain Model can occur in software development due to various factors. Below are some of the most common reasons:

  1. Simplicity: An Anemic Domain Model can initially often be simpler and more understandable. For a novice developer, the simplicity and understandability of this model can facilitate rapid involvement in the development process.

  2. Rapid Prototyping: For teams wanting to develop and launch a product or application quickly, the Anemic Domain Model can be attractive. This model can aid in quickly adding features and creating the application’s prototype by separating business logic and data access.

  3. Technological Limitations: Some technologies or frameworks can make it difficult to implement more complex domain models. In this case, the Anemic Domain Model might be more applicable.

  4. Training and Knowledge Deficiency: Teams lacking in-depth knowledge in software engineering tend to use anemic domain models. This can stem from not fully understanding the complexity and business logic of more complex domain models.

Despite these reasons, it’s essential not to forget that the Anemic Domain Model has some significant disadvantages, often making the extension and maintenance of the application more difficult. This model can provide rapid development in the early stages of a project but can reduce the sustainability over the life of the project. Especially in large, complex projects or those that continually evolve, a more robust domain model may be required.

The notion of considering the Anemic Domain Model as an anti-pattern has not always been a universal view. There are many criticisms and comments arguing that the Anemic Domain Model can be valid and useful in some cases.

Some of these criticisms include:

  1. Task-Oriented Business Logic: Business logic is sometimes managed not by an object but by a process or sequence of operations. Applications in these types of situations can benefit from the Anemic Domain Model approach, as here domain objects don’t need to do much more than carry data.

  2. Configuration Simplicity: The Anemic Domain Model often avoids the complexity required by object-oriented programming languages and frameworks. The model provides a clear distinction between DTOs and services, which can help the code be more understandable in complex business applications.

  3. Scalability and Distribution: In large, distributed systems, business logic is often distributed across multiple services and microservices. In this case, the Anemic Domain Model can be useful because it ensures a clear separation of data and business logic.

  4. Encapsulation: Keeping the business logic in a service layer separates data and business logic and provides encapsulation. This allows different parts of an application to be changed and updated independently.

Whether to consider the Anemic Domain Model an anti-pattern entirely depends on the situation. While it may fall outside of generally accepted best practices, it can be beneficial and effective under specific circumstances and conditions. It’s important to remember that its use should be based on the project’s requirements and the team’s capabilities.

The Anemic Domain Model is seen as an anti-pattern by many software engineers and designers because it contradicts some important software design principles and practices. Here are the main reasons why the Anemic Domain Model is considered an anti-pattern:

  1. Contradicts Object-Oriented Programming (OOP) Principles: OOP is based on a paradigm where data and business logic are together. However, the Anemic Domain Model contradicts these principles by separating business logic and data. While business logic is usually stored in separate services, models often only store data. This violates the principle of providing a unified model, which is the fundamental aim of OOP.

  2. Low Encapsulation: Anemic Domain Model usually has low encapsulation due to the separation of business logic and data. When business logic is separate from the model, business rules are often spread across numerous services. This can require changes in different parts of the code when a model or service needs to be modified, which adds complexity to the code.

  3. Contradicts Single Responsibility Principle: It also contradicts the Single Responsibility Principle (SRP), which suggests that each class or module should have only one responsibility. In the Anemic Domain Model, business logic and data storage are usually managed by separate classes or modules, meaning each can have multiple responsibilities.

  4. Code Maintenance and Extension Become Difficult: Anemic Domain Model can make extending and maintaining the code more challenging. Changing a model often requires changing the services dependent on it. This can accumulate a large amount of technical debt over time.

For these reasons, the Anemic Domain Model is often considered an anti-pattern. However, it can still be useful in certain situations, especially under certain technological limitations. As always, the choice of the best practice model should be based on the requirements and context of the project.

Sure, I will give a more explanatory example of both anemic and rich (non-anemic) Domain Models. In this example, let’s have an object called Product and we will perform a Discount operation on this object.

Example in C# for Anemic and rich Domain Models:

Anemic Model (C#)

public class Product // Anemic model
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public decimal Discount { get; set; }
}

public class ProductService // Service
{
    public void ApplyDiscount(Product product, decimal discountPercentage)
    {
        product.Discount = product.Price * discountPercentage / 100;
    }
}

In this example, the Product object contains no business logic, and the ApplyDiscount operation is found in a service class named ProductService.

Rich Model (C#)

public class Product // Rich model
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public decimal Discount { get; set; }

    public void ApplyDiscount(decimal discountPercentage)
    {
        this.Discount = this.Price * discountPercentage / 100;
    }
}

In this example, the ApplyDiscount operation is added inside the Product class, thereby keeping the business logic and data together.

We can also demonstrate the same example with Java.

Anemic Model (Java)

public class Product { // Anemic model
    private String name;
    private BigDecimal price;
    private BigDecimal discount;

    // Getters and setters...
}

public class ProductService { // Service
    public void applyDiscount(Product product, BigDecimal discountPercentage) {
        product.setDiscount(product.getPrice().multiply(discountPercentage).divide(BigDecimal.valueOf(100)));
    }
}

Rich Model (Java)

public class Product { // Rich model
    private String name;
    private BigDecimal price;
    private BigDecimal discount;

    // Getters and setters...
    
    public void applyDiscount(BigDecimal discountPercentage) {
        this.setDiscount(this.getPrice().multiply(discountPercentage).divide(BigDecimal.valueOf(100)));
    }
}

In this example, Anemic and rich domain models are similarly demonstrated using Java. In the rich model, the applyDiscount operation is added inside the Product class, thereby keeping the business logic and data together.

Anemic and rich Domain Models offer different approaches to managing data and business logic in software engineering practices.

The Anemic Domain Model separates business logic and data, spreading functionality across service layers. This can initially seem simple and understandable, and may be appealing due to circumstances such as rapid prototyping or technological constraints. However, one of the main drawbacks of this model is that it contravenes the fundamental principles of Object-Oriented Programming (OOP). This can make the maintenance and extension of code more difficult, particularly in more complex or continuously evolving projects.

On the other hand, the rich Domain Model presents a unified model by keeping business logic and data together. This is more consistent with OOP principles and can facilitate the maintenance and expansion of code. However, this approach can also bring its own complexities and challenges, especially when managing business logic in large, distributed systems is necessary.

The usability and effectiveness of both models depend on the project’s requirements, the team’s capabilities, and the technology being used. It is important that the chosen model is the most suitable one for the long-term success of the project. Which model to choose should be determined by the team in a specific project context. In any case, how to most effectively manage and maintain business logic should be carefully considered.