Holger Thiemann
Holger Thiemann

Reputation: 1082

Doesn't DDD and CQRS/ES break the persistence agnosticity of DDD?

The Domain model in DDD should be persistence agnostic.

CQRS dictates me to fire events for everything I wan't to have in my read model. (And by the way to split my model into a write model and at least one read model).

ES dictates me to fire events for everything that changes state and that my aggregate roots must handle the events itself.

This seems not to be very persistence agnostic to me.

So how could DDD and CQRS/ES be combined without heavy impact of this persistence technology to the domain model?

Is the read model also in the DDD domain model? Or outside of it?

Are the CQRS/ES events the same as DDD domain events?

Edit:

What I took out of the answers is the following:

Yes, for ORM the implementation of the domain model objecs will differ than that with using ES. The question is the false way around. First write the domain model objects, then decide how to persist (more event like => ES, more data like => ORM, ...).

But I doubt that you will ever be able to use ES (without big additions/changes to your domain objects) if you did not make this decision front up, and also to use ORM without decide it front up will cause very much pain. :-)

Upvotes: 4

Views: 1063

Answers (4)

David
David

Reputation: 560

I think you can still decouple your domain from the persistence mechanism by using a satellite POCO. Then you can implement your specific persistence mechanism around that POCO, and let your domain use it as a snapshot/memento/state.

Upvotes: 0

Mathieson
Mathieson

Reputation: 1948

Not sure how orthodox this is, but a current event sourced entity model I have does something like this, which might illustrate the difference . . . (C# example)

public interface IEventSourcedEntity<IEventTypeICanRespondTo>{
    void When(IEventTypeICanRespondTo event);
}

public interface IUser{
    bool IsLoggedIn{get;}
}

public class User : IUser, IEventSourcedEntity<IUserEvent>{
     public bool IsLoggedIn{get;private set;}

     public virtual void When(IUserEvent event){
           if(event is LoggedInEvent){
               IsLoggedIn = true;
           }
     }
}

Very simple example - but you can see here that how (or even IF) the event is persisted is outside the domain object. You can easily do that through a repository. Likewise CQRS is respected, because how I read the value is separate from how I set it. So for example, say I have multiple devices for a user, and only want them logged in once there's more than two?

public class MultiDeviceUser : IUser, IEventSourcedEntity<IUserEvent>{
     private IEnumerable<LoggedInEvent> _logInEvents = . . . 
     public bool IsLoggedIn{
         get{
              return _logInEvents.Count() > MIN_NUMBER_OF_LOGINS;
         }
     }

     public void When(IUserEvent ev){
          if(ev is LoggedInEvent){
             _logInEvents.Add(ev);
          }
     }
}

To the calling code, though, your actions are the same.

var ev = new LoggedInEvent();
user.When(ev);

if(user.IsLoggedIn) . . . . 

Upvotes: 0

Mark Seemann
Mark Seemann

Reputation: 233377

Commands

Boiled down to its essence, CQRS means that you should split your reads from your writes.

Typically, a command arrives in the system and is handled by some sort of function that then returns zero, one, or many events resulting from that command:

handle : cmd:Command -> Event list

Now you have a list of events. All you have to do with them is to persist them somewhere. A function to do that could look like this:

persist : evt:Event -> unit

However, such a persist function is purely an infrastructure concern. The client will typically only see a function that takes a Command as input and returns nothing:

attempt : cmd:Command -> unit

The rest (handle, followed by persist) is handled asynchronously, so the client never sees those functions.

Queries

Given a list of events, you can replay them in order to aggregate them into the desired result. Such a function essentially looks something like this:

query : target:'a -> events:Event list -> Result

Given a list of events and a target to look for (e.g. an ID), such a function can fold the events into a result.

Persistence ignorance

Does this force you to use a particular type of persistence?

None of these functions are defined in terms of any particular persistence technology. You can implement such a system with

  • In-memory lists
  • Actors
  • Event Stores
  • Files
  • Blobs
  • Databases, even
  • etc.

Conceptually, it does force you to think about persistence in terms of events, but that's no different than the approach with ORMs that force you to think about persistence in terms of Entities and relationships.

The point here is that it's quite easy to decouple a CQRS+ES architecture from most implementation details. That's usually persistent-ignorant enough.

Upvotes: 6

Phil Sandler
Phil Sandler

Reputation: 28046

A lot of the premises in your question present as very binary/black-and-white. I don't think DDD, CQRS, or Event Sourcing are that prescriptive--there are many possible interpretations and implementations.

That said, only one of your premises bother me (emphasis mine):

ES dictates me to fire events for everything that changes state and that my aggregate roots must handle the events itself.

Usually ARs emit events--they don't handle them.

In any case, CQRS and ES can be implemented to be completely persistence agnostic (and usually are). Events are stored as a stream, which can be stored in a relational database, a NoSQL database, the file system, in-memory, etc. The storing of events is usually implemented at the boundaries of the application (I think of this as infrastructure), and domain models have no knowledge of how their streams are stored.

Similarly, read models can be stored in any imaginable storage medium. You can have 10 different read models and projections, with each of them stored in a different database and different format. Projections just handle/read the event stream, and are otherwise completely decoupled from the domain.

It does not get any more persistence agnostic than that.

Upvotes: 4

Related Questions