I have been adapting poor-man's CQRS1 for quite some time now because I love its flexibility to have granular data in one data store, providing great possibilities for analysis and thus increasing business value and when needed another for reads containing denormalized data for increased performance.
But unfortunately pretty much from the beginning I have been struggling with the problem where exactly I should place business logic in this type of architecture.
From what I understand, a command is a mean to communicate intent and does not have ties to a domain by itself. They are basically data (dumb - if you wish) transfer objects. This is to make commands easily transferable between different technologies. Same applies to events as responses to successfully completed events.
In a typical DDD application the business logic resides within entities, value objects, aggregate roots, they are rich in both data as well as behavior. But a command is not a domain object thus it should not be limited to domain representations of data, because that puts too much strain on them.
So the real question is: Where exactly is the logic?
I have found out I tend to face this struggle most often when trying to construct a quite complicated aggregate which sets some rules about combinations of its values. Also, when modeling domain objects I like to follow the fail-fast paradigm, knowing when an object reaches a method it's in a valid state.
Let's say an aggregate
Car uses two components:
Engine value objects are represented as super types and have according sub types,
Manual transmissions, or
Electric engines respectively.
In this domain, living on its own a successfully created
Transmission, be it
Manual, or either type of an
Engine is completely fine. But the
Car aggregate introduces a few new rules, applicable only when
Engine objects are used in the same context. Namely:
- When a car uses
Electricengine the only allowed transmission type is
- When a car uses
Petrolengine it may have either type of
I could catch this component combination violation at the level of creating a command, but as I have stated before, from what I understand that should not be done because the command would then contain business logic which should be limited to the domain layer.
One of the options is to move this business logic validation to command validator itself, but this does not seem to be right either. It feels like I would be deconstructing the command, checking its properties retrieved using getters and comparing them within the validator and inspecting results. That screams like a violation of the law of Demeter to me.
Discarding the mentioned validation option because it does not seem viable, it seems like one should use the command and construct the aggregate from it. But where should this logic exist? Should it be within the command handler responsible for handling a concrete command? Or should it perhaps be within the command validator (I don't like this approach either)?
I am currently using a command and create an aggregate from it within the responsible command handler. But when I do this, should I have a command validator it would not contain anything at all, because should the
CreateCar command exist it would then contain components which I know are valid on separate cases but the aggregate might say different.
Let's imagine a different scenario mixing different validation processes - creating a new user using a
The command contains an
Id of a users which will have been created and their
The system states the following rules for user's email address:
- must be unique,
- must not be empty,
- must have at most 100 characters (max length of a db column).
In this case, even though having a unique email is a business rule, checking it in an aggregate makes very little sense, because I would need to load the entire set of current emails in the system to a memory and check the email in the command against the aggregate (Eeeek! Something, something, performance.). Because of that, I would move this check to the command validator, which would take
UserRepository as a dependency and use the repository to check whether a user with the email present in the command already exists.
When it comes to this it suddenly makes sense to put the other two email rules in the command validator as well. But I have a feeling the rules should be really present within a
User aggregate and that the command validator should only check about the uniqueness and if validation succeeds I should proceed to create the
User aggregate in the
CreateUserCommandHandler and pass it to a repository to be saved.
I feel like this because the repository's save method is likely to accept an aggregate which ensures that once the aggregate is passed all invariants are fulfilled. When the logic (e.g. the non-emptiness) is only present within the command validation itself another programmer could completely skip this validation and call the save method in the
UserRepository with a
User object directly which could lead to a fatal database error, because the email might have been too long.
How do you personally handle these complex validations and transformations? I am mostly happy with my solution, but I feel like I need affirmation that my ideas and approaches are not completely stupid to be pretty happy with the choices. I am entirely open to completely different approaches. If you have something you have personally tried and worked very well for you I would love to see your solution.
1 Working as a PHP developer responsible for creating RESTful systems my interpretation of CQRS deviates a little from the standard async-command-processing approach, such as sometimes returning results from commands due to the need of processing commands synchronously.