Entity Base Class

If you follow DDD principles, you eventually end up creating a base class for all the domain entities. It’s a good idea as it allows you to gather common logic in one place. When you decide to do that, you inevitably face the question what exactly should be included in that base entity and how it should be presented.

Interface as a base entity

I often see developers using interfaces as a base entity. Their code might look like this:

public interface IEntity
{
    int Id { get; }
}

While this approach guarantees that all domain entities have some minimum functionality (Id property in this example), in most cases having an interface as a base entity is a bad idea.

First of all, interfaces don’t allow you to gather any logic in them, you need to implement this logic yourself which leads to a massive code duplication. Even in the example above you need to create the Id property in every single entity, which itself is a heavy violation of the DRY principle.

Secondly, using an interface doesn’t show the appropriate relationship between the domain entities. When a class implements an interface it makes a promise about some functionality, and that’s it. Two classes implementing the same interface don’t give any promises about their relationship, they can belong to entirely unconnected hierarchies. In other words, IEntity interface introduces a “can do” relation (according to Bertrand Meyer’s classification), whereas domain entities should be connected to the base entity by “is a” relation. Every domain class not only has the Id property, but itself is an entity. It is important to remove possible misunderstandings, using an interface instead of the base class can lead to one.

How you shouldn’t implement base classes

Okay, but what logic do we need in the base domain class?

Obviously, it should have an Id field, which is mapped to a table’s primary key. All tables in the database must have ids with the same type so we could factor the Id property out to the base class. Here is how you should not do it:

public class Entity<T>
{
    public T Id { get; protected set; }
}

Motivation for such code it pretty clear: you have a base class that can be reused across multiple projects. For instance, if there is a web application with GUID Id columns in the database and a desktop app with integer Ids, it might seem a good idea to have the same class for both of them. In fact, this approach introduces accidental complexity because of premature generalization. There is no need in using a single base entity class for more than one project or bounded context. Each domain has its unique path, so let it grow independently. Just copy and paste the base entity class to a new project and specify the exact type that will be used for the Id property.

There’s a case I want to mention, though. Although you should create tables with primary keys of the same type only, you may have a legacy application, created by different developers, where they have database tables with primary keys of different types. Or there might be composite primary keys. In these cases, there is no way to inherit your domain classes from the same base class. The best solution here might be refactoring your database, although you should consider pros and cons of such decision. For a new project, you should always create database tables with a single Id column of the same type.

Entity base class: the code

So, what should the base domain class look like? Here is the code I use in production, let’s step through it.

public abstract class Entity
{
    public virtual long Id { get; protected set; }
    protected virtual object Actual => this;

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

        if (other is null)
            return false;

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

        if (Actual.GetType() != other.Actual.GetType())
            return false;

        if (Id == 0 || other.Id == 0)
            return false;

        return Id == other.Id;
    }

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

        if (a is null || b is null)
            return false;

        return a.Equals(b);
    }

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

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

By default the Id property is of long type, which excludes the possibility of sequence exhausting. Also, all members are made virtual, because I use NHibernate where virtual members are required to create runtime proxies. Setter is made protected and not private because of NHibernate as well.

The most interesting part is equality members, which is generally the rest part of the class. To go further, we should step back and recall what it means to be equal.

There are three types of equality in enterprise software:

  • Reference equality means that two references refer to the same object in memory.

  • Identifier equality means that two different objects in memory refer to the same row in the 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.

The Entity class covers the first two situations: when two objects either equal by reference or by identifier. Logical equality pertains to Value Objects I will cover later. Note, that Actual.GetType() method is called in order to get the type of the objects. It was added to the base class in order to overcome the issue where ORMs return the type of a runtime proxy if you just call GetType() on an object.

The second if statement in Equals checks reference equality and the last one checks identifier equality. The IsTransient method refers to the notion of objects’ states. They can be either saved in DB (this state is called persistent) or not saved (this state is called transient). It doesn’t matter why an object can be not saved: either because it was deleted or because insert wasn’t done in DB yet, this state is called transient regardless of the reason.

We must check the objects’ state because transient objects have Id property set to zero by default. We cannot check identifier equality if one of the objects doesn’t have the identifier yet. But if you assign the id by yourself, using non-native id generation strategy in NHibernate, then you can skip this.

The GetHashCode method must be overridden together with the Equals method as well. They always come together because of the internal .NET logic. This logic can be illustrated in the code below. When you call Contains() method it loops through the objects in the list and calls GetHashCode() on each object. If the codes coincide it also calls Equals(). And only if the both checks, GetHashCode and Equals, are passed the two objects are considered equal.

Entity obj = GetObjectSomehow();
List<Entity> objects = GetObjectsSomehow();
objects.Contains(obj);

It is a good idea to declare == and != operators as well, because by default == operator checks reference equality only.

Summary

I use this code in most of my projects, sometimes adding some functionality if it appears in all project’s domain classes. Such functionality might be version property (byte[] or int) or any other useful stuff. But don’t be carried away by this, add functionality to the base domain class if it belongs to all domain classes only. Otherwise, you may end up building God Object instead of a thin base entity.

Update 4/1/2019

The Entity base class no longer contains the GetRealType method and doesn’t rely on NHibernate anymore. Thanks to Anders Baumann for suggesting the workaround.

Subscribe


I don't post everything on my blog. Don't miss smaller tips and updates. Sign up to my mailing list below.

Comments


comments powered by Disqus