11

~TLDR: I'm implementing a CQRS + DDD solution for one of my larger projects, and, I'm wondering if there is any real reason that my command handlers can't directly dispatch the command objects to my aggregates, in a small handful of cases, where the command object is data rich? I can't find any specific reason why this would be any kind of an anti-pattern, and I can't find any opinions that go into great detail about this type of design.

Background: I have implemented CQRS systems before, and I have implemented DDD applications, but never CQRS + DDD in a proper Eric Evans style domain driven application. So I ask because I don't want to abuse my Aggregates, and hurt my application in the long term.

An example of my command object having quite a bit of data would be a registration command that takes in 8+ fields (firstname, lastname, preferred name, dob, title, username, password, department etc). It feels very awkward creating a method on my Aggregate that has 8 params, and the alternative solution of using some sort of dto, and having my handler map the command to the dto - either automagically using automapper, or inline - seems like an unnecessary and non value adding abstraction.

I can also see future use cases where commands might be data rich (it wouldn't be a large percentage of commands, but there would still be a few), so I'd like to get this seemingly trivial aspect correct from the start.

AaronHS
  • 1,334
  • 12
  • 29
  • As far as I remember, DDD does not say exactly how to implement the Domain Model but CQRS does. No conflict here. Regarding simple command objects, how did you pass them to Aggregates? I guess you have an abstraction/interface that both depend on. – neleus May 25 '15 at 10:21
  • I pass the command data (not the actual command object) to the aggregate via parameters. The command is received by a handler which loads up the aggregate, and simply passes it what it needs via an appropriately designed method. Normally it's just one or a few parameters, which is the way i like it, as i believe a well designed aggregate should have methods that require very few parameters. Otherwise it's probably violating SRP somehow. But, there are *some* edge cases where lots of params may be required. – AaronHS May 25 '15 at 12:13
  • Did you consider passing the entire command object to the Aggregate? It looks simple, no cons here. – neleus May 25 '15 at 15:55
  • This was actually what my question is: *I'm wondering if there is any real reason that my command handlers can't directly dispatch the command objects to my aggregates* – AaronHS May 25 '15 at 17:39
  • @neleus CQRS is an implementation detail, so I would rather avoid poluting the domain with this concept. – Michał Chilczuk Jan 29 '18 at 17:10

2 Answers2

15

Command objects are usually expressed in primitive types while aggregate method signatures will be expressed in domain concepts.

The fact that you didn't immediately recognized that probably means that you missed a lot of opportunities to make implicit concepts explicit in your domain.

"a registration command that takes in 8+ fields (firstname, lastname, preferred name, dob, title, username, password, department etc)"

What should strike you is that firstname and lastname could definitely form a meaningful whole, such as new FullName(firstname, lastname) and I'm sure there's a lot of other cases where Value Objects (VO) could or should be used in your domain... Username, Password, etc. ? Using VOs to model things that changes together will better describe your model as well as reduce the number of arguments you have to pass around.

Therefore, that makes command objects poor candidates as aggregate method arguments. If you go down that road, you will definitely miss modeling opportunities.

plalx
  • 42,889
  • 6
  • 74
  • 90
  • That makes perfect sense. Thanks for the answer, as well as a clear explanation as to the *why* – AaronHS May 26 '15 at 12:11
  • But why wouldn't concepts like fullname be a part of your command? Wouldn't the business tell you they would like to tell the application to add an entity which contains a fullname? – Thomas Heijtink Mar 29 '22 at 17:15
  • @ThomasHeijtink Not sure to understand the question. The business wouldn't tell you about entities or value objects, but they would most likely tell you that users need to provide their full name upon registering. The `FullName` VO wouldn't be in the command DTO here, but rather translated **from** the command's payload before reaching the domain. – plalx Mar 29 '22 at 17:41
  • @plalx I know that’s what you said. But the OP asked why the AR shouldn’t directly handle the command. Your argument was that that the command dto wouldn’t have VO’s. What I often do is specify an interface as argument for an AR method ‘IUpdatedUserDetails’. The command ‘UpdateUserDetails’ would explicitly implement that interface acting as a mapping interface returning a VO. Now the command can be directly passed to the AR as it satisfies the contract. What downsides do you see with such an approach? – Thomas Heijtink Mar 31 '22 at 04:04
  • @ThomasHeijtink Well from the domain's perspective it does not depend on the command so that's fine. As long as the interface is defined in the domain I don't really see a problem with it. The only thing is that you can't easily enrich the values with data that's not in the command if the command itself does the mapping. The approach should be fine for most cases, but you might need a dedicated mapper or map in the services for some cases. That becomes an implementation detail of the application service layer. – plalx Mar 31 '22 at 13:00
  • @plalx absolutely. And I have found many of such cases. But for the easy cases it does save extra models and code in the handler making the handlers more readable. Also for those that don't like tools like AutoMapper (like me) it gives a better mapping experience inside the dto instead of a separate mapping profile somewhere else. In cases it needs to be enriched I will need extra code anyway which need to explicitly be visible in the handler itself. – Thomas Heijtink Apr 01 '22 at 08:27
2

Agree with @plalx.

Take commands as aggregate method arguments may lead to having too many mapping codes inside aggregates: Mapping primitive types to domain objects, which is better to be placed out of the domain objects.

But for simpler projects, I think it is good starter.

In registration case, the bounded context is usually a supporting domain and the complexity usually comes from external integration(email notification, register with social accounts, etc). In this case, I think bounded context integration is more important than the models inside. So take commands as aggregate method arguments may be a quick start to get things done and saves your time to focus on your core domain.

Yugang Zhou
  • 7,123
  • 6
  • 32
  • 60