The best way to implement a Main Something property

In this post, we will explore a common design problem: implementing a Main Something property. There’s an equally common solution to this problem which is sub-optimal in most cases.

The best way to implement a Main Something property

Let me explain the design problem first. Imagine you develop a CRM system. It has an entity Customer and each customer keeps a list of contacts, like this:

public class Customer
{
    public string Name { get; private set; }
    public IReadOnlyList<Contact> Contacts { get; private set; }
}
 
public class Contact
{
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
}

Now let’s say there’s a "go to" contact in every client company - the main person you maintain the relationship with within that company. How would you implement it?

The most common solution I’ve seen to this date is to introduce a flag, like so:

public class Contact
{
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public bool IsMain { get; set; }
}

Now you can add a calculated property to Customer which would return the main contact and some method that would update it:

public class Customer
{
    public string Name { get; private set; }
    public IReadOnlyList<Contact> Contacts { get; private set; }
 
    public Maybe<Contact> MainContact
    {
        get { return Contacts.SingleOrDefault(x => x.IsMain); }
    }
 
    public void UpdateMainContact(Contact contact)
    {
        if (!Contacts.Contains(contact))
            throw new InvalidOperationException();
 
        Maybe<Contact> oldMainContact = MainContact;
        if (oldMainContact.HasValue)
        {
            if (oldMainContact.Value == contact)
                return;
 
            oldMainContact.Value.IsMain = false;
        }
 
        contact.IsMain = true;
    }
}

At this point, you probably recall domains in which you needed to do something alike. It wasn’t necessarily "Main Something". It could also be "Default Something" or similar: the default image in a photo album, the favorite song in a song collection, etc. Every such requirement I encountered had the implementation I brought above: a collection of items, each with a boolean flag.

Alright, so what is wrong with this implementation? Well, nothing, except that there’s a more straightforward alternative.

Instead of relying on the IsMain flag in the Contact class, we can make the MainContact property in Customer non-calculated:

public class Customer
{
    public string Name { get; private set; }
    public IReadOnlyList<Contact> Contacts { get; private set; } 
    public Maybe<Contact> MainContact { get; private set; }
}

The UpdateMainContact then can be reduced to this version:

public void UpdateMainContact(Contact contact)
{
    if (!Contacts.Contains(contact))
        throw new InvalidOperationException();
 
    // Maybe supports proper == operator
    if (MainContact == contact)
        return;
 
    MainContact = contact;
}

And we can remove the IsMain property:

public class Contact
{
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
}

Okay, let’s break this alternative down.

At first, it looks like we introduced a redundancy to our Customer. Instead of N contacts, we now store N+1: N in the Contacts collection and another one in MainContact. However, it is not the case. We don’t keep an excessive number of physical contacts. MainContact is just a reference to one of the entities which already exists under the collection of contacts.

What we did here is we traded the old Contact.IsMain property for the new Customer.MainContact one. There are two corollaries from this refactoring.

First, it’s become harder to remove a contact from the collection of contacts as we now need to check whether it also referenced by the new property:

public void RemoveContact(Contact contact)
{
    if (!Contacts.Contains(contact))
        return;
 
    Contacts.Remove(contact);
    if (MainContact == contact)
    {
        MainContact = null;
    }
}

Theoretically, we could run into an inconsistency where MainContact refers to a contact which no longer exists. That could take place because of a race condition in a multi-threaded scenario or if we just forget to add the if statement like the one above. In the old implementation, we just remove the contact from the collection of contacts and MainContact updates itself automatically due to its calculated nature.

On the other hand, it’s now easier to change the main contact. The UpdateMainContact method has become simpler as we don’t need to chase down the existing contacts with IsMain set to true and reset them anymore:

public void UpdateMainContact(Contact contact)
{
    if (!Contacts.Contains(contact))
        throw new InvalidOperationException();
 
    // Maybe supports proper == operator
    if (MainContact == contact)
        return;
 
    MainContact = contact;
}

We just update the MainContact property itself and that’s it. It’s now also impossible to run into another inconsistency that could potentially show up in the old implementation: having more than one main contact. The absence of the IsMain property protects us from that.

So, in terms of the ease of maintaining invariants, both solutions pretty much break even. We trade the boolean flag for the additional reference and, as a corollary, one potential inconsistency for the other.

Why am I claiming the latter alternative is more straightforward then? Because of its explicitness. Think about the domain model we have here. Each of our customers needs to maintain a list of contacts and somehow distinguish one of them. This very act of differentiating the main contact is a direct responsibility of the Customer entity, Contact itself has nothing to do with it. Pushing this responsibility to Contact in the form of the IsMain property messes up the domain model. It inverts the responsibility graph. As a result, the model no longer accurately represents the actual domain, and that adds additional mental overhead when translating between the two.

One of the biggest goals of Domain-Driven Design is to build models that reflect the problem domain as closely as possible, with attention to relevant details and disregard for irrelevant ones. The closer the model represents the problem, the easier it is to reason about it. (Another DDD concept that helps with that is Ubiquitous Language).

Pushing an improper responsibility where it doesn’t belong doesn’t look like a big deal but such things add up. It’s a good idea to eliminate disparities between the domain and its model when you can, and this concrete design problem is a good example of one. Also, it’s just a good practice overall to be explicit in your code.

By the way, it’s not only the application code that you can refactor in the above way, it’s also the database structure. Instead of this:

Implementation with IsMain
Implementation with IsMain

you can implement the relationship like this:

Explicit alternative
Explicit alternative

Conclusion

Having clearly defined responsibilities is preferable in most cases. The Main Something problem is a good example where you can practice it. A non-calculated property instead of IsMain flag puts domain knowledge to the right place and makes it explicit.

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