Various topics from software architecture part 5: aggregate roots
June 29, 2015 6 Comments
Introduction
In the previous post we looked at the second installment of the unit of work pattern. We designed a mock implementation of the unit of work and unit of work repository interfaces.
In this post we’ll look at a key concept from Domain Driven Design, the aggregate root. This topic was discussed in detail as part of the DDD series but it deserves a dedicated post.
Recall from the post on the Repository pattern that we marked a domain object with the interface IDomain:
public class Customer : IDomain { public int Id { get; set; } public string Name { get; set; } public Address Address { get; set; } }
IDomain is just an empty interface to mark domain objects:
public interface IDomain { }
It really should have been called IAggregateRoot but I didn’t want to introduce that concept right there. The scope of the post would have exploded.
Aggregates and their roots
Objects are seldom “independent”: they have other objects attached to them or are part of a larger object graph themselves. A Person object may have an Address object which in turn may have other Person objects if more than one person is registered there. It can be difficult to handle in software: if we delete a Person, then do we delete the Address as well? Then other Person objects registered there may be “homeless”. We can leave the Address in place, but then the DB may be full of empty, redundant addresses. Objects may therefore have a whole web of interdependency. Where does an object start and end? Where are its boundaries? There’s clearly a need to organise the domain objects into logical groups, so-called aggregates. An aggregate is a group of associated objects that are treated as a unit for the purpose of data changes. Every aggregate has a root and clear boundaries. The root is the only member of an aggregate that outside objects are allowed to hold references to.
Here comes an example:
Take a Car object: it has a unique ID, e.g. the registration number to distinguish it from other cars. Each tire must also be measured somehow, but it’s unlikely that they will be treated separately from the car they belong to. It’s also unlikely that we make a DB search for a tire to see which car it belongs to. Therefore the Car is an aggregate root whose boundary includes the tires. Therefore consumers of the Car object should not be able to directly change the tire settings of the car. You may go as far as hiding the tires entirely within the Car aggregate and only let outside callers “get in touch with them” through methods such as “MoveCarForward”. The internal logic of the Car object may well modify the tires’ position, but to the outside world that is not visible.
An engine will probably also have a serial number and may be tracked independently of the car. It may happen that the engine is the root of its own aggregate and doesn’t lie within the boundaries of the Car, it depends on the domain model of the application.
An important term in conjunction with Aggregates is invariants. Invariants are consistency rules that must be maintained whenever data changes, so they represent business rules.
In code
The simplest way to introduce aggregate roots in your domains is to mark them as such like in the case of IDomain above. As discused above aggregates are handled as one unit where the aggregate root is the entry point, the “boss” of the aggregate. This implies that the data access layer should only handle aggregate roots. It should not accept objects that lie somewhere within the aggregate. Here’s the marker interface for aggregate roots:
public interface IAggregateRoot { }
So it looks exactly like the IDomain interface. The Customer domain can now be marked as an aggregate root:
public class Customer : IAggregateRoot { public int Id { get; set; } public string Name { get; set; } public Address Address { get; set; } }
After refactoring the name of the interface you’ll see that the repositories we’ve seen in this series all work with aggregate roots, e.g.:
public interface IRepository<DomainType, IdType> where DomainType : IAggregateRoot { DomainType FindBy(IdType id); void Update(DomainType aggregate); void Insert(DomainType aggregate); void Delete(DomainType aggregate); }
This post finishes the series.
View the list of posts on Architecture and Patterns here.
Pingback: Architecture and patterns | Michael's Excerpts
Hi Andras,
Awesome article. Thanks for this.
I have a question to clarify;
If we consider implementing UnitOfWork pattern,
should we require implementing Update(…)/Save(…) in each Repository ? Shouldn’t it belongs to UnitOfWork implementation?
Within UnitOfWork implementation, each repository ( which represent aggregate root) behave like collection, and it should have a collection type interface ( methods like Add, Remove, Find and etc)
Appreciate your feedback in this regard.
Hi Ranga, for a full discussion with examples you can view two separate series:
Domain Driven Design
Original skeleton project
A model .NET web service based on Domain Driven Design Part 1: introduction
DDD revisited
Domain Driven Design with Web API revisited Part 1: introduction
You’ll find an example of using a unit of work there.
//Andras
Thanks @Andras,
I’m going throw all the posts you have published.
I will keep posting if anything unclear.
Ranga
throw = through, sorry for the typo
Just Awesome 🙂