I am becoming a big fan of DDD. So, I'm thinking of applying it correctly to my current system I develop.
Suppose we have two aggregate roots: Order
and User
. Order
has two properties, referencing the User
: owner
and contractor
. Owner created an Order
, contractor fulfilled it.
Owners can rate the quality with which the Order
was fulfilled by the contractor. So we have an Feedback
entity, connected to Order
, containing rating.
Now, User
object contains an average rating of each user across the orders he fulfilled. And this part confuses me a little.
User
rating is not just a static property, but actually an average of all ratings across Feedbacks
. And it is changed (needs to be recalculated) at the moment an Order
has Feedback
attached.
Currently I have some services encapsulating domain logic: OrderService
and UserService
(I know this is actually not in compliance with DDD, but I will refactor that later). When OrderService receives a command to attach Feedback to order, it emits OrderFeedbackAttachedEvent
which UserService listens for to recalculate user rating.
What doesn't satisfy me is that the knowledge about Order aggregate root is now leaked into UserService. And I don't see the way to escape it. I'm starting to think there should be some pattern to handle such cases.
Rating seem to be a perfect property of the user. But the fact that it is not a static, persistent value, but rather something, that is calculated based on other objects data, makes me doubt.
Rating isn't itself an entity either. Neither it is a value object. What is it, I wonder, in DDD? And how do I model ratings (or any other calculatable value) in my system without sacrificing performance and ease of use)?
Tactical DDD is when you define your domain models with more precision. The tactical patterns are applied within a single bounded context. In a microservices architecture, we are particularly interested in the entity and aggregate patterns.
In DDD, validation rules can be thought as invariants. The main responsibility of an aggregate is to enforce invariants across state changes for all the entities within that aggregate. Domain entities should always be valid entities. There are a certain number of invariants for an object that should always be true.
Thus, we have developed three strategies for extracting and formulating domain-specific design principles: (1) analyze the best hand-designed visualizations in the domain, (2) examine prior research on the perception and cognition of visualizations, and, when necessary, (3) conduct new user studies that investigate how ...
It appears you might have at least 2 separated bounded contexts: one for ordering, and another for feedbacks.
Aknowleging Bounded Contexts allows you to see different abstractions of the same physical thing: in the "orders" context, Order seems to be a legitimate Aggregate, but it could be a Value Object in the "feedback" BC, where it would hold an order id (which value comes from the order BC through an event).
Here is a proposal :
With this model, you would calculate the average rating of a contractor when handling the "FeedbackEmitted" event from the feedback aggregate in an event handler of the contractor aggregate
Hope this helps :)
To be fair, an event emitted from another AR or another BC is not leakage. There's no problem for a User
AR to handle an OrderFeedbackGiven
event in my opinion. If the Feedback
VO is part of the event then the client doesn't need to depend on anything else to process the event. Having a feedback bounded context might be cleaner, but I wouldn't implement a full blown BC for just that or you will have an explosion of micro-BCs...
I am worried by the fact, that after feedback was given, all feedbacks must be aggregated to calculate user rating. That creates a dependency from user to order (calling order repository to fetch rating from user service, when event was caught). Is this ok, what do you think?
I think it wouldn't matter if you did that. The application service layer is depending on the domain layer as a whole. However, there's no need to do this at all since you can calculate the moving cumulative average.
E.g. Where this.ordersFeedbackAvg
is a MovingAvg
value object that keeps track of the average and how many data points it was computed from.
when(OrderFeedbackGiven feedback) {
this.ordersFeedbackAvg = this.ordersFeedbackAvg.cumulate(feedback.mark);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With