Search code examples
.netcqrsmediatr

Why do CQRS command handlers exclude saving UnitOfWork?


I've been looking through different CQRS samples and most of them use command handlers that do not save UnitOfWork (i. e. DataContext in case of Entity Framework). Something like this:

    public void Handle(Command message)
    {
        var course = Mapper.Map<Command, Course>(message);

        _db.Courses.Add(course);
    }

Saving (and transaction commit) usually happens in background when request is processed.

I've seen this approach from many leading CQRS guys but I've never heard the reasoning of it.

The biggest problem of this approach are the cases when you need to get an entity Id right after the handler call is returned (which happens quite a lot). There's obviously ways to workaround it (i. e. using Guid, prerequesting unique Id from your database, etc.) but seem clumsy.

But what are the advantages of this approach? Theoretically it could help not to make several database roundtrips in case we have several handlers per request. But it doesn't happen a lot. Another advantage that comes to my mind is that we don't have to type that routine Save call and let it happen automatically. It's kinda nice but does it overweight the Id generation problems?


Solution

  • Why do CQRS command handlers exclude saving UnitOfWork?

    I think it starts from Evans, Domain Driven Design, Chapter 6 The Life Cycle of a Domain Object.

    There is a raft of techniques for dealing with the technical challenges of database access....

    But even so, take note of what has been lost. We are no longer thinking about concepts in our domain model. Our code will no be communicating about the business, it will be manipulating the technology of data retrieval.

    The idea being that at this point in the code, we are working with the domain model rather than looking at the data model.

    A REPOSITORY lifts a huge burden from the client, which can now talk to a simple, intention-revealing interface, and ask for what it needs in terms of the model

    Evans is very emphatic about this, in the context of transactions

    Leave transaction control to the client. Although the REPOSITORY will insert into and delete from the database, it will ordinarily not commit anything. It is tempting to commit after saving, for example, but the client presumably has the context to correctly initiate and commit units of work. Transaction management will be simpler if the REPOSITORY keeps its hands off.

    Some additional notes:

    The biggest problem of this approach are the cases when you need to get an entity Id right after the handler call is returned (which happens quite a lot).

    That's usually an indication that you are fighting the wrong problem. See Marc de Graauw, Nobody Needs Reliable Messaging.