loading...
Angry Nerds

Avoiding Anemic Domain Model

angrynerds_soft profile image Angry Nerds Originally published at angrynerds.co on ・7 min read

In this post, we are going to discuss the concept of Anemic Domain Model and its influence on designing software systems. It probably goes without saying but everything that is discussed here is in the context of object-oriented programming. Let’s start with defining the notion of Anemic Domain Model.

Anemic Domain Model is a software pattern where classes representing domain objects contain only data and little or no logic in methods. Does it sound familiar? Maybe it is a consequence of using one of the most popular ways of organizing application logic into layers.

You see the data layer and it may be quite natural to think of this as a table in a relational database. It is true that the objects from the data layer will be often used to preserve some data in a relational database but additional work is required to send data from those objects. The bottom line is that objects are not tables.

Using Anemic Domain Models is a direct violation of one of the core concepts in object-oriented programming. Having Anemic Domain Models leads to code duplication and a lack of encapsulation. Check out this excellent blog post by Martin Fowler. Let’s see some examples.

All examples in this post are in C#.

public class Company 
{ 
    public int Id { get; set; } 
    public DateTime CreatedAt { get; set; } 
    public string Name { get; set; } 
    public string Description { get; set; } 
    public decimal ShareCapital { get; set; } 
}

Company class is used by a service layer to create/update new companies, and that service is injected in some API controller. CompanyService class contains the necessary logic - here is a draft of that service. Let's omit the controller class as it is straightforward.

public class CompanyService 
{ 
    public void Create(CreateCompanyModel model) 
    { 
        if (!string.IsNullOrWhiteSpace(model.Name)) 
        { 
            throw new Exception("name is required"); 
        } 

        Company company = new Company
        { 
            Id = ... //generate Id 
            CreatedAt = DateTime.UtcNow, 
            Name = model.Name 
        }; 

        // save company in database 
    } 

    public void Update(UpdateCompanyModel model) 
    { 
        // validate model if necessary 

        Company company = ... // get company from database      
        company.Description = model.Description; 
        company.ShareCapital = model.ShareCapital; 

        // update company in database 
    } 
}

Potentially, there could be a couple of scenarios where new companies will be created or updated. Maybe we want to create a Business Corporation by following a different path than the path for General Partnership or a Limited Liability Company. This is something that will be defined by business needs.

Right now, there is nothing in the Company class that helps you understand what are the required properties and what are the optional properties. There are no validation rules included. Probably the Name and CreatedAt should be required - and it may be obvious to someone but when you have a model with 20 properties it may be harder to choose a subset of required properties.

Moreover, sometimes properties’ values could be automatically deduced when a new instance is created, e.g. CreatedAt seems like something that could be set behind the scenes. If only we defined a constructor, we would make creating a company easier. We could replace the above code with:

public class Company 
{ 
    public int Id { get; set; }
    public DateTime CreatedAt { get; set; } 
    public string Name { get; set; } 
    public string Description { get; set; } 
    public decimal ShareCapital { get; set; } 

    public Company(string name) 
    { 
        if (!string.IsNullOrWhiteSpace(name)) 
        { 
            throw new Exception("name is required"); 
        } 

        Id = ... //generate Id 
        Name = name; 
        CreatedAt = DateTime.UtcNow; 
    } 
}

Now our client can’t create a new company using default constructor without Name or CreatedAt values. This is good because it is a business rule that we want to enforce in our domain. However, he can still update Name or CreatedAt date. Does it make sense? As always, it depends. Let's assume that business rule is that we should change neither CreatedAt nor Name values. To do that, we will replace public setters with private setters in Company class.

public class Company 
{ 
    public int Id { get; private set; } 
    public DateTime CreatedAt { get; private set; } 
    public string Name { get; private set; } 
    public string Description { get; private set; } 
    public decimal ShareCapital { get; private set; } 

    public Company(string name) 
    { 
        if (!string.IsNullOrWhiteSpace(name)) 
        { 
            throw new Exception("name is required"); 
        } 

        Id = ... //generate Id 
        Name = name; 
        CreatedAt = DateTime.UtcNow; 
    } 
}

A new problem arose that has to be addressed. In this version, we cannot set value for optional properties Description and ShareCapital. To fix that, we will add new methods: UpdateDescription, UpdateShareCapital. The idea is that for specific scenarios we want to have separate methods that will act accordingly. In more complex scenarios such a method would do more than setting value for one property, e.g. set more values, like the last update date.

public class Company 
{ 
    public int Id { get; private set; } 
    public DateTime CreatedAt { get; private set; } 
    public string Name { get; private set; } 
    public string Description { get; private set; } 
    public decimal ShareCapital { get; private set; } 

    public Company(string name) 
    { 
        if (!string.IsNullOrWhiteSpace(name)) 
        { 
            throw new Exception("name is required"); 
        } 

        Id = ... //generate Id 
        Name = name; 
        CreatedAt = DateTime.UtcNow; 
    } 

    public void UpdateDescription(string description) 
    { 
        Description = description; 
    } 

    public void UpdateShareCapital(decimal shareCapital) 
    { 
        ShareCapital = shareCapital; 
    } 
}

Another advantage of this approach is discoverability — i.e. when you look at the object, you discover what you can do with that object. With an Anemic Domain Model, this is harder and potentially more confusing.

Value objects

We will define the concept of value objects that are useful building blocks in creating rich domain models. Eric Evans defines value objects this way:

An object that represents a descriptive aspect of the domain with no conceptual identity is called a VALUE OBJECT. VALUE OBJECTS are instantiated to represent elements of the design that we care about only for what they are, not who or which they are.

[Excerpt from “Domain-Driven Design: Tackling Complexity in the Heart of Software”, p. 59]

Examples of value objects are numbers, strings, money, addresses, colors, etc. When you think about it, there is quite a natural distinction between entities and value objects. For example, if you have a product that costs $100, it doesn’t matter which $100 it is. When you design a value object, you have to make them immutable. Fulfilling the immutability requirement can be a little tedious. We will use value object implementation proposed by msdn (check here).

public abstract class ValueObject
{ 
    protected static bool EqualOperator(ValueObject left, ValueObject right)
    { 
        if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null)) 
        { 
            return false; 
        } 
        return ReferenceEquals(left, null) || left.Equals(right); 
    } 

    protected static bool NotEqualOperator(ValueObject left, ValueObject right) 
    { 
        return !(EqualOperator(left, right)); 
    } 

    protected abstract IEnumerable<object> GetAtomicValues(); 

    public override bool Equals(object obj) 
    { 
        if (obj == null || obj.GetType() != GetType()) 
        { 
            return false; 
        }

        ValueObject other = (ValueObject)obj; 
        IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator();
        IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator(); 
        while (thisValues.MoveNext() && otherValues.MoveNext()) 
        { 
            if (ReferenceEquals(thisValues.Current, null) ^    
                ReferenceEquals(otherValues.Current, null)) 
            { 
                return false; 
            } 

            if (thisValues.Current != null &&             
                !thisValues.Current.Equals(otherValues.Current)) 
            { 
                return false; 
            } 
        } 
        return !thisValues.MoveNext() && !otherValues.MoveNext(); 
    } 

    public override int GetHashCode() 
    { 
        return GetAtomicValues() 
         .Select(x => x != null ? x.GetHashCode() : 0) 
         .Aggregate((x, y) => x ^ y); 
    } 
    // Other utility methods 
}

We can inherit from ValueObject class to define Money value object.

public class Money : ValueObject 
{ 
    public string Currency { get; private set; } 
    public decimal Value { get; private set; } 

    private Money() { } 

    public Money(string currency, string value) 
    { 
        Currency = currency; 
        Value = value; 
    } 

    protected override IEnumerable<object> GetAtomicValues() 
    { 
        yield return Currency; 
        yield return Value; 
    } 
}

A word of caution must be mentioned here. Initially, you may be tempted to use struct as a value object. Even though it may seem like a good idea, it has major disadvantages. Read this blog post to learn why. We can use Money class in ShareCapital property of Company class. With that change, we completed transforming an anemic model into a rich model. This is a new version of Company class:

public class Company 
{ 
    public int Id { get; private set; } 
    public DateTime CreatedAt { get; private set; } 
    public string Name { get; private set; } 
    public string Description { get; private set; } 
    public Money ShareCapital { get; private set; } 

    public Company(string name) 
    { 
        if (!string.IsNullOrWhiteSpace(name)) 
        { 
            throw new Exception("name is required"); 
        } 

        Id = ... //generate Id 
        Name = name; 
        CreatedAt = DateTime.UtcNow; 
    } 

    public void UpdateDescription(string description) 
    { 
        Description = description; 
    } 

    public void UpdateShareCapital(Money shareCapital) 
    { 
        ShareCapital = shareCapital; 
    } 
}

We could benefit from using value objects instead of primitive types. The code will be well-organized and understandable, and it will help us avoid code duplication. There is even a term for the situation when your domain code relies heavily on primitive types — we call it a primitive obsession.

Refactor CompanyService

We can simplify CompanyService by using a new version of Company class and by removing company logic that is encapsulated inside Company class. Here is the result of that change:

public class CompanyService
{ 
    public void Create(CreateCompanyModel model) 
    { 
        Company company = new Company(model.Name); 

        // save company in database 
    } 

    public void UpdateDescription(string description, int companyId)
    { 
        Company company = ...// get company from database 
        company.UpdateDescription(description); 

        // update company in database 
    } 

    public void UpdateShareCapital(Money money, int companyId) 
    { 
        Company company = ...// get company from database 
        company.UpdateShareCapital(money); 

        // update company in database 
    } 
}

This code is much cleaner than the original version, and more object-oriented. It is also more expressive.

Potential problems

▶ When you develop a small application, transforming it into rich models may not be worth it.

▶ Additional work has to be done to make it work with popular ORM frameworks, e.g. NHibernate or Entity Framework. It is possible but it is not out of the box support.

▶ The setup of your tests may become more complex. It can also be a good thing because your tests are closer to real-world scenarios. When you create your domain models in test, then you are forced to comply with all business rules that are implemented in your application.

In general, I believe it is worth going the extra mile if you know that a project will be developed in a longer perspective.

Where to go next?

I highly recommend you a Pluralsight course Refactoring from Anemic Domain Model Towards a Rich One by Vladimir Khorikov. You should also read Kamil Grzybek’s blog, especially posts related to Domain-Driven Design. If you want to learn more about Domain-Driven Design, you can start by reading the book Implementing Domain-driven Design by Vaughn Vernon.

If you have any questions, feel free to leave us a comment!

Article by Marcin Zarębski. Originally published at https://angrynerds.co.


Discussion

pic
Editor guide