Structuring Your Domain Model

Tags: DomainDrivenDesign;

This article is a continuation of a series on building the Person Service. Here is the list of articles in the series.

In this article I am going to define the domain model for the Person Service. Our primary entity in this system will be a person. Now before we get too deep into the code let’s define some of the terms we will be using related to DDD. Domain Driven Design (DDD) is an approach to software development defined in a book by the same name written by Eric Evans back in 2003. In it he describes an approach to software development where you break down the problem into smaller individual pieces focused around the business domain. The concept of a domain is the setting in which a word or statement appears that determines its meaning. For example in the banking industry the word credit may have a completely different meaning than in a university setting regarding scholarly articles. The high-level concepts of DDD include:

Entity

An object that is not defined by its attributes, but rather by a thread of continuity and its identity. An example of an entity in our system is the person. Every person will be given a unique Id and even if two persons have the same attribute values if their Id’s don’t match then they are not the same person.

Value Object

“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.” [Eric Evans]

An example of a value object in our system is an address. The address consist of a collection of attributes and when two addresses have the exact same attributes they are viewed as equal.

I found a great list of attributes on Enterprise Craftsmanship a blog by Vladimir Khorikov.

  • No identity. A corollary of value objects’ identity-less nature is, obviously, not having an Id property. Unlike entities, value objects should be compared by value, not by identity field.
  • Immutability. As any value object can be replaced by another value object with the same property set, it’s a good idea to make them immutable to simplify working with them, especially in multi-threaded scenarios. Instead of changing an existing value object, just create a new one.
  • Lifetime shortening. It’s another corollary of the value objects’ identity-less nature. They can’t exist without a parent entity owning them. In other words, there always must be a composition relationship between a Value Object class and an Entity class. Without it, value objects don’t make any sense.
  • Lifetime shortening leads to another consequence: Value Objects should not have separate tables in database. This one is an attribute programmers find the most controversial.

Aggregate

A collection of objects that are bound together by a root entity, otherwise known as an aggregate root. The aggregate root guarantees the consistency of changes being made within the aggregate by forbidding external objects from holding references to its members. An example of an aggregate in our system is the person as it is a collection of objects. The person object also servers as our aggregate root as it controls access to the system’s aggregates from external systems.

Services

When an operation does not conceptually belong to any object you can implement these operations in services. If Entities and Value Objects are the “things” in your domain, the services are a way of dealing with actions, operations and activities. An example in our system may be an event subscription and notification service which allows external systems to subscribe to change events related to people in our system.

Repository

Repository mediates between the domain and data mapping using a collection-like interface for accessing domain objects. It is more like a facade to your data store that pretend like a collection of your domain. This allows your domain to be ignorant of the data persistence being used and storage implementations may be easily interchanged.

Equality

Another concept that we already hit on when describing Entity objects and Value objects is equality. I want to define the three types of equality.

  • Reference equality means that two references refer to the same object in memory.
  • Identifier equality means that two different objects in memory have the same Id attribute. They also refer to the same row in the data database.
  • Logical equality means that two different objects and two different rows in the database are actually equal. It might happen when an object doesn’t have its own identity (such objects called value objects), and thus we can treat two different objects with identical fields as logically equal.

Now that we have some of the concepts defined lets begin defining our entities and value objects. I don’t like re-inventing the wheel I would rather stand on the shoulders of giants and build upon their great work. There can be a great many lessons learned in re-writing existing systems or designs, but for this effort I am not going to do that. As you may or may not know naming things is hard. So I wanted to take advantage of the great work done by the Schema.org W3C community group. The group includes members from Google, Microsoft, Yahoo, Yandex, and many others. Their mission is to create, maintain, and promote schemas for structured data on the internet. Basically some of the largest companies in the world came together and said that there needed to be a universal schema and naming standard for common types so that developers didn’t have to think about it on every new project and so that systems used a shared vocabulary making it easier to integrate. The core vocabulary contains 583 types, 846 properties, and 114 enumerations. The primary one that I was interested in was the Person type.

Our Person object is an aggregate of primitives and other objects in our domain. These include primitives such as first name and last name and value objects such as home address and contact points such as their cellphone. You can see the full implementation of the Person domain model on GitHub in the PersonService repository.

public class Person : Entity
{
    public string FirstName { get; set; }
    public string MiddleName { get;  set; }
    public string LastName { get;  set; }
    public string HonorificPrefix { get;  set; }
    public string HonorificSuffix { get;  set; }
    public string Email { get;  set; }
    public PostalAddress BusinessAddress { get;  set; }
    public PostalAddress HomeAddress { get;  set; }
    public DateTime BirthDate { get;  set; }
    public ICollection<ContactPoint> ContactPoints { get;  set; }
    public DateTime DeathDate { get;  set; }
    public int Height { get;  set; }
    public int Weight { get;  set; }
    public GenderType Gender { get;  set; }
    public string JobTitle { get;  set; }
    public Organization WorksFor { get;  set; }
    public Person Manager { get;  set; }
    public ICollection<Person> DirectReports { get;  set; }
    public string Nationality { get;  set; }
    public Person Spouse { get;  set; }
    public Person EmergencyContact { get;  set; }
    public string AlternateName { get;  set; }
    public string Description { get;  set; }
    public string Id { get;  set; }
    public ImageObject Image { get;  set; }
    public string Name { get;  set; }
}

One interesting object I wanted to point out is organization. I wavered back and forth as to whether it was a value object or entity. In the end the question that helped me determine that it was a value object was; did it need to exist outside the context of a person? And for our system the answer was no. It was just a collection of attributes that only mattered in the context of a person. In many other systems organization would be an entity that had it’s own identity, but I needed to stay focused on our domain.

One other object I wanted to point out for our domain is the Employee entity. This entity contains all of the attributes specific to an employee at our generic company such as hourly rate, job code, and employee status. The Employee entity implements the IPerson interface.

Another common practice with designs that are following DDD is to define abstract implementations of Entity and ValueObject that contain the proper equality logic. This saves you from having to implement it in every class.

 public abstract class Entity
    {
        public virtual long Id { get; protected set; }

        public override bool Equals(object obj)
        {
            var compareTo = obj as Entity;

            if (ReferenceEquals(compareTo, null))
                return false;

            if (ReferenceEquals(this, compareTo))
                return true;

            if (GetType() != compareTo.GetType())
                return false;

            if (!IsTransient() && !compareTo.IsTransient() && Id == compareTo.Id)
                return true;

            return false;
        }

        public static bool operator ==(Entity a, Entity b)
        {
            if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
                return true;

            if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
                return false;

            return a.Equals(b);
        }

        public static bool operator !=(Entity a, Entity b)
        {
            return !(a == b);
        }

        public override int GetHashCode()
        {
            return (GetType().ToString() + Id).GetHashCode();
        }

        public virtual bool IsTransient()
        {
            return Id == 0;
        }
    }
public abstract class ValueObject<T> : IEquatable<T>
    where T : ValueObject<T>
    {
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            T other = obj as T;

            return Equals(other);
        }

        public override int GetHashCode()
        {
            IEnumerable<FieldInfo> fields = GetFields();

            int startValue = 17;
            int multiplier = 59;

            int hashCode = startValue;

            foreach (FieldInfo field in fields)
            {
                object value = field.GetValue(this);

                if (value != null)
                    hashCode = hashCode * multiplier + value.GetHashCode();
            }

            return hashCode;
        }

        public virtual bool Equals(T other)
        {
            if (other == null)
                return false;

            Type t = GetType();
            Type otherType = other.GetType();

            if (t != otherType)
                return false;

            FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

            foreach (FieldInfo field in fields)
            {
                object value1 = field.GetValue(other);
                object value2 = field.GetValue(this);

                if (value1 == null)
                {
                    if (value2 != null)
                        return false;
                }
                else if (!value1.Equals(value2))
                    return false;
            }

            return true;
        }

        private IEnumerable<FieldInfo> GetFields()
        {
            Type t = GetType();

            List<FieldInfo> fields = new List<FieldInfo>();

            while (t != typeof(object))
            {
                fields.AddRange(t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));

                t = t.GetTypeInfo().BaseType;
            }

            return fields;
        }

        public static bool operator ==(ValueObject<T> x, ValueObject<T> y)
        {
            return x.Equals(y);
        }

        public static bool operator !=(ValueObject<T> x, ValueObject<T> y)
        {
            return !(x == y);
        }
    }

Summary

You can see the full implementation of the Person domain model on GitHub in the PersonService repository. In this article we:

  • Defined the concepts in Domain Driven Design
  • Defined our entities, value objects, and aggregate root
  • Implemented our equality logic

This is our first pass at the domain model and as Eric Evans talks about in DDD the domain model should be iterated on throughout the development process to make it clearer as to each parts meaning and purpose.

Related Links

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s