Let’s talk about crossing aggregate boundaries in business operations.

1. Preamble

In my Pluralsight course about EF Core and encapsulation, I made the recommendation to work with both, the DbContext (also known as Unit of Work) and repositories that work on top of that DbContext, in controllers:

public class StudentController : ControllerBase
{
    private readonly StudentRepository _repository;
    private readonly SchoolContext _context;

    public StudentController(
        StudentRepository repository,
        SchoolContext context)
    {
        _repository = repository;
        _context = context;
    }
}

The idea is that you should use repositories to update data in the database and you should use the DbContext to decide whether to keep those updates or roll them back entirely:

[Enable Images]

Here’s how it may look in code:

public void Register()
{
    // Assign the student data from the incoming DTO
    var student = new Student(/* ... */);

    _repository.Save(student);
    _context.SaveChanges();
}

Notice how we are using the repository to add the student to our database, and the DbContext (the Unit of Work) to commit the changes.

In theory, we could make the SaveChanges() call inside the _repository.Save() method, but that’s only because this example is quite simple. In real-world projects, you will almost always have scenarios where you need to update more than one aggregate at once. For example, not just the Student class, but also the College to increment the number of students.

Therefore, you can’t delegate this SaveChanges() responsibility to any particular repository, it should be performed separately.

2. Crossing aggregate boundaries

And this is where the following question comes (I received it twice during the last month):

Vaughn Vernon in his book "Implementing Domain Design" (page 360) gives the following advice:

"Both the referencing aggregate and the referenced aggregate must not be modified in the same transaction. Only one or the other may be modified in a single transaction."

The general guidance I’ve read / heard is to avoid modifying more than one aggregate in a single transaction but according to your course it’s quite a common practice. Could you share your thoughts on this?

I hear this guideline a lot as well.

I’m not 100% sure where that guideline comes from but I think it stems from limitations of non-relational and event sourced systems where you can’t have transactions span more than one document/aggregate and have to come with complicated pub-sub mechanisms between aggregates to ensure consistency.

In relational DBs, this is unnecessary. It’s easier to just allow your application code to mutate more than one aggregate n a single database transaction. The result is going to be the same as with pub-sub but with much less friction.

What is that friction I’m talking about, exactly?

Well, consider the example I mentioned above where, when registering a student, we also need to increment the number of students in the corresponding college.

How would you implement this functionality given that you can create (or modify) only one aggregate instance at a time? And let’s say that both the Student and College are aggregates.

You would have to at least do this:

  • Publish a message to a message bus at the time of student registration.

  • Have a receiver that would listen for said messages and process them by updating the College aggregate.

Here’s this process on an image:

And mind you, this is the simplest workflow.

If you want to, let’s say, implement the transactional outbox pattern, you would need another step in-between the above two to persist the domain event before dispatching it.

Now compare it to this code:

public void Register()
{
    // Create a student and update the corresponding college

    _studentRepository.Save(student);
    _collegeRepository.Save(college);

    _context.SaveChanges();
}

This code provides the same consistency guarantees (thanks to the transactional nature of relational databases) without all the friction of implementing the publisher-subscriber boilerplate.

I think the choice is obvious.

--Vlad

https://enterprisecraftsmanship.com


Enjoy this message? Here are more things you might like:

Workshops — I offer a 2-day workshop for organizations on Domain-Driven Design and Unit Testing. Reply to this email to discuss.

Unit Testing Principles, Patterns and Practices — A book for people who already have some experience with unit testing and want to bring their skills to the next level.
Learn more »

My Pluralsight courses — The topics include Unit Testing, Domain-Driven Design, and more.
Learn more »