Question

my question is straightforward - in normal DDD domain objects should not be exposed to the presentation layer (except some people acctually prefer it - naked objects how it's called I believe). But what about CQRS + ES? Writes are delegated to encapsulated algorithms (Events + Sagas) which use repositories I believe and then updates the object calling accept on it (with the changed field/fields). So there are some transactional logic and anything related to consistency-enforcing... am I right?

Was it helpful?

Solution

Commands and queries sit on top of your domain. Your presentation layer talks to this layer.

From an earlier blogpost of mine:

Commands help you in supporting the ubiquitous language by explicitly capturing user intent at the boundaries of your system - think use cases. You can look at them as messages that are being sent to your domain. In this regard, they also serve as a layer over your domain - decoupling the inside from the outside, allowing you to gradually introduce concepts on the inside, without breaking the outside. The command executor gives you a nice pipeline you can take advantage of to centralize security, performance metrics, logging, session management and so on.

Commands represent a use case.

public class WithdrawMoneyCommand 
{
     public WithDrawMoneyCommand(string account, decimal amount) 
     {
         // Guard and assign here..
     }

     public string Account { get; private set; }

     public decimal Amount { get; private set; }
}

Commands are executed by command handlers. This is where you do something with your aggregates. Be it invoking a method and changing state or playing and recording a new event. The transactional boundary is at the command handler level.

public class WithdrawMoneyCommandHandler : IHandle<WithdrawMoneyCommand> 
{
     public void Handle(WithdrawMoneyCommand command) 
     {
         // Get your account here, and do something with it..
     }
}

This is the write side.

On the read side, someone sends you a query, and you reply to it. As simple as that.

public class AccountBalanceReadModel
{
    public string AccountNumber { get; set; }

    public decimal Value { get; set; }
}  

The read model can be composed on the fly querying one or multiple aggregates.

public class AccountBalanceQuery 
{
     public string AccountNumber { get; set; }
}

public class AccountReads : IHandle<AccountBalanceQuery, AccountBalanceReadModel>
{
    public AccountBalanceReadModel Handle(AccountBalanceQuery query) 
    {
          // Do query or queries and assemble an AccountBalanceReadModel 
    }
}

Or (and this is where events come into play) the read model can already be in the format you want because it was created by handling events (raised by aggregates).

public class AccountReads : IHandle<AccountBalanceQuery, AccountBalanceReadModel>
{
    public AccountBalanceReadModel Handle(AccountBalanceQuery query) 
    {
          // Do simple query because your read model is already there in the way you like it
    }
}

Looking back at how to create your read model with events, you would have something handling your events, and creating the read model.

public class AcocuntBalanceReadModelDenormalizer : IHandle<AmountWithdrawn>
{
    public void Handle(AmountWithdrawn @event) 
    {
          // Update your specialized read model here (AccountBalanceReadModel)
    }
}

Do note that events are often handled in an eventual consistent fashion. The advantages of this are performance and stability of your system.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top