Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CQRS: Class Redundancy and passing DTO to Domain

My CQRS application has some complex domain objects. When they are created, all properties of the entity are directly specified by the user, so the

CreateFooCommand has about 15 properties.

FooCreatedEvent thus also has 15 properties, because I need all entity properties on the read-side.

As the command parameters must be dispatched to the domain object and FooCreatedCommand should not be passed to the domain,

there is a manual mapping from CreateFooCommand to the domain.

As the domain should create the domain event,

That is another mapping from the domain Foo properties to FooCreatedEvent.

On the read side, I use a DTO to represent the structure of Foo as it is stored within my read-model.

So the event handler updating read-side introduces another mapping from event parameters to DTO.

To implement a simple business case, we have

  • Two redundant classes
  • Three mappings of basically the same properties

I thought about getting rid of command/event arguments and push the DTO object around, but that would imply that the domain can receive or create a DTO and assign it to the event.

Sequence:

REST Controller --Command+DTO--> Command Handler --DTO--> Domain --(Event+DTO)--> Event Handler

Any ideas about making CQRS less implementation pain?

like image 747
mbue Avatar asked Sep 12 '17 06:09

mbue


2 Answers

I see the following options:

  1. Create a immutable DTO class FooDetails that is used by both CreateFooCommand and FooCreatedEvent by injecting it in the constructor; type hint the aggregate method against FooDetails; for example new CreateFooCommand(new FooDetails(prop1, prop2, ...))

  2. Create a immutable base class FooDetails that is inherited by both the CreateFooCommand and FooCreatedEvent and type hint the aggregate method against FooDetails

  3. Completely change style and use the style promoted by cqrs.nu in which commands are sent directly to the aggregates; the aggregates have command methods like FooAggregate::handle(CreateFooCommand command); I personally use this style a lot.

like image 115
Constantin Galbenu Avatar answered Oct 30 '22 00:10

Constantin Galbenu


With CQRS + ES you opted for a more complex approach with more moving parts, knowing that it would allow you to achieve more. Live with it. The strength of this approach implies separating concerns. A Command is a command, an Event is an event, etc. Although many of them may look similar along the chain, there might be exceptions. Some may contain additional data, or slightly different aspects of the same data. A Command can have meta information about the applicative context (who initiated the command, when, is it a retry, etc.) that doesn't concern the Domain. Read models will often include information about related objects to be displayed in addition to their own info (think parent-child relationship).

There's only so much of the seemingly similar code you can cut off before you block yourself from modelling these exceptions. And introducing inheritance or composition between these data structures is often more complex than the original pain of having to write boilerplate mapping code.

like image 25
guillaume31 Avatar answered Oct 30 '22 01:10

guillaume31