I am working on a microservice architecture using CQS and with domain objects built using DDD aggregates. I am just beginning to learn about CQS, CQRS, Domain Driven Design, so excuse me if what I post is completely wrong from either perspective. Currently, the architecture within a microservice is as follows:
Write side: RestApi situated at the controller level deserializes json bodies to commands (Controller layer) => (Service layer) commands are handled in command handlers where business logic is invoked on the aggregates, or handled within the handler itself => (Repository layer) domain objects are saved into (and hydrated from) the database via a Write repository (Database is shared between read and write layer, so there is no separate DB for projections as in CQRS)
Read side: Rest api (Controller layer) => communicates directly with read repositories which return DTOs. Have introduced also query handlers for aggregating data from multiple repositories (and services), when this is necessary.
Also, the aggregates are attempting to follow concepts described in Vaughn Vernon's Efficient aggregate design, whereas if an aggregate root needs to hold a reference to a different aggregate root, it does so by only storing the id and not the whole aggregate.
For the purpose of this questions, I will introduce a basic aggregate:
class Order {
CustomerId;
...
CalculateDiscount();
}
class Customer {
Id;
Name;
Age;
IsStudent();
}
There are times within the application logic (on the write side), where a dependent aggregate needs to be loaded so that various items are checked. For example, even though the Order aggregate root is loosely coupled from the Customer and can be acted upon (persisted, invariants being maintained) individually, there are cases where business related to the customer needs to be checked. For example, when creating an order, the existence of the Customer must be first validated. Furthermore, as described in the small example, for calculating a discount, loading the customer and checking if they are a student would be a necessary step.
In order to do this, I have two solutions, but both have their pros and cons and I would like some opinions as to what is best (or if there are other alternatives).
Injecting the Customer write repository within the order command handler:
Pro
Seems to be more object oriented and enables business logic to be encapsulated within domain models. In the example above, the CalculateDiscount
method can become CalculateDiscount(lCustomerWriteRepository)
which encapsulates this logic within the aggregate.
Cons
Apart from being able to retrieve the CustomerDomainModel
the interface leaks methods to persist a CustomerDomainModel, which is a non-readonly operation that should only be done through handlers of CustomerCommands and not OrderCommands
Refactoring the customer aggregate to a different service would lead to the logic being moved at a handler level
Injecting the Customer read repository within the command handler:
Pro The interface allows only readonly access to customers Read repository can be easily switched with proxy to other microservice if aggregate is separated to other microservice (As both read repository and communication over HTTP results in DTOs rather than domain model representations) Cons Logic involving calls to other aggregates is achieved at handler level. I prefer pushing as much logic towards the aggregates as possible. Changing UI requirements might affect DTOs being used in write operations and more and more unnecessary data might be fetched Aggregates are more anemic in business logic as opposed to the alternate solution.
I am hoping to find a solution to my above problem and perhaps understand what I am missing. Also, as a bonus question, I am failing to understand how Domain Services would come into play in such an architecture :)