Reputation: 55
In the application I am working on, the user can start an Order without Order Lines, but the order cannot be persisted without order lines and other properties where other aggregates are referenced (which in the database are foreign keys which do not accept null values, this is why). This order is kept in cache until the user confirms it, that is the moment to persist it.
I think creating an aggregate root via its constructor leaving it in an inconsistent state regarding its persistence should not be possible. Is there something wrong with my thinking? because if this is possible, how do I handle the rules for its persistence? In the application service? In your repository? via a function (maybe "IsValidToPersist") in the aggregate root?
I see sense in the fact that the Order can be created without all the properties it needs for its persistence, in this case. I usually check for these needs in the aggregate root's constructors, throwing exceptions if the aggregate root is not ready to persist, among other business rules. I guess if this can be so, then there must be application services like "CreateOrderService", "AddOrderLineService", and "SaveOrderService" / "AddOrderService" where it would persist. And not just an "AddOrderService" service where it is created and persisted at the same time.
To update an Order Line, should I create an "UpdateOrderLineService" service that will find the Order, update its order line through its functions? Or should it be all that it takes to update an Oder in a single service "UpdateOrderService"?. I think this second option complicates some things. For example, it is difficult for the service to know which order lines to add, update and delete. Although that could be solved with a function in Order like "ChangeOrderLines" and simply replace them. In the first option, should the Order be persisted with each update (or even addition or deletion) of order lines? I can think of other options such as: return the updated Order or return the updated (or added) Order Lines (although this is not such a good idea).
My question, really is: is it convenient that exists services to handle the child entities of the aggregate root?
Edit (specific information about app):
My response to @rascio contains more useful specific details for the application:
The application has a tour to "create" a Order. Screen after screen the order is "created". The first screen the user must write a number, without this number the order can't be created. Must be a valid number that it is persisted in db (like discount codes, for example). After that the user can chose the products and later, other things. I need check and save the data user write or choose for each screen. The last screen it's an abstract of order, where user can change things and confirm the order. It's a desktop application. Without side effects. Really not a order, but is very similar.
Edit 2 (more specific information about app):
There are two ways to create the order. One going through the tour: introducing the number, the products and the other things. Screen after screen. Once the tour is finished, the summary screen will be displayed where the user can modify and confirm it. Or you can start creating it on this summary screen from scratch. You can also go to this summary screen from any of the other tour screens. If the user had chosen only the number and the products in the tour, these will be the ones that appear in the summary and not the other things, which they can complete in the summary.
Upvotes: 0
Views: 623
Reputation: 9279
It seems to me that your domain rules want your order to not have order line in certain states.
From your latest update it seems that to be created, your Order aggregate, has only the invariant related to the number the user needs to put.
This is part of your domain logic.
This order is kept in cache until the user confirms it, that is the moment to persist it.
This is not a normal "software" cache, this is your domain want to maintain your order with relaxed invariants, until the user do something.
When you "update" an aggregate it can change its internal state, and on different internal states different invariants can be applied.
I see sense in the fact that the Order can be created without all the properties it needs for its persistence, in this case.
this is totally right to me.
I guess if this can be so, then there must be application services like "CreateOrderService", "AddOrderLineService", and "SaveOrderService" / "AddOrderService" where it would persist.
more than services I would speak in terms of your domain, these seems to be the commands your aggregate should support.
Looking at what your wrote, it seems to me that a valid sequence of commands can be:
CreateOrder(ANumber) -> AddProduct(Product) -> AddProduct(Product) -> AddOtherThings(OtherThings)
After each command the state of the aggregate is persisted.
Each command needs its own validation, so:
CreateOrder
needs a valid number (not empty, etc...)AddOrderLine
Valid product, and the order should be finalized (you didn't write about this, but I'm supposing after you AddOtherThings
or something else is done you cannot add any more products to the orderAddOtherThings
valid input and at least one product should be part of the order(This is an example, commands should be modeled also depending what you wat to offer to the user in terms of a "single update persisted atomically", so also an UpdateOrderLines(OrderLine...))
can make sense in certain cases.)
how do I handle the rules for its persistence? In the application service? In your repository?
What I want to stress with this response is the fact that an aggregate can have multiple states, and each state can have its own invariants. The role of the Entity/Aggregate in DDD is to maintain true these invariants, and disallow for updates that can move the data into an inconsistent state. So these rules should be handled where you are handling the logic to change the aggregate internal state.
For your last question:
My question, really is: is it convenient that exists services to handle the child entities of the aggregate root?
These depends on what you mean by "services", application services? Domain services? Something else services? I hate the "service" word as it doesn't have a precise meaning :)
By the way as software components to do, this depends on your generic architecture approach, but what DDD suggests is that all mutations to an aggregate "pass" for the aggregate root, because to enforce the invariants you could need the state of the entire aggregate, for example an OrderLine entity can be updated only if the Order entity (aggregate root) is in a certain state.
It doesn't matter what software components you do, the important thing is that they treat you entire aggregate as a single atomic unit of data, like it doesn't support concurrent modification, so that at each mutation invariants regarding the data of the aggregate can be checked.
Upvotes: 1
Reputation: 43728
There's two main ways to model this IMO:
An Order
always needs some order lines or else it's not an Order
. In that case you are making it an invariant of the Order
to even exist, so that thing "without order lines" is NOT an Order
. Perhaps it's a ShoppingCart
? Perhaps it's an OrderDraft
? It would be modeled as a concept "similar" to an Order
, from which the real Order
would most likely be created.
You make the invariant dependent not only on the mere Order
's existance, but it's state. For instance, you could see it as an accepted order must have line items. In that case perhaps the order starts as in progress and needs line items only to transition towards the accepted/submitted/etc. state. In that case the rules would apply upon calling order.accept()
for instance.
"then there must be application services like "CreateOrderService", "AddOrderLineService", and "SaveOrderService" / "AddOrderService"
I wouldn't call it "Service" if there's one per use case. I'd call it XXXUseCase
or XXXHandler
, but the Service
suffix is generally used for wider interfaces such as:
class OrderService {
void create(...) { }
void addLineItem(...) {}
//...
}
Make sure that the use case names aligns with the domain's language (the Ubiquitous Language) rather than technical jargon (e.g. saveOrder).
Upvotes: 3
Reputation: 20561
I would suggest not having foreign key constraints which cross aggregate boundaries. Enforcing such constraints in the DB infrastructure means that at least one of these two things is true:
It's also the case that if you have things like ON DELETE CASCADE
as part of that constraint, you're violating the principle that all access to the aggregate is through the root (because the DB definitely isn't going through the root), but this might not necessarily be a problem if there's no operations which could lead to that sort of "behind your back" modification of aggregates.
In this case, it's clear that there's a disagreement between your domain logic (which says "an order without lines is OK") and your DB infra (which says "nope"). If you can't drop the foreign key constraint, then you should duplicate the constraint in your domain logic and not allow an order to be constructed without lines. That may entail an OrderInProgress aggregate which might not have the same invariants as an Order.
Upvotes: 3
Reputation: 9247
I think your confusion stems from an assumption that your DDD entities can directly support a user workflow. This is not the case. The user workflow is a UI concern, not your aggregate's.
the user can start an Order without Order Lines, but the order cannot be persisted without order lines
The first part of that requirement is a User experience requirement. The second part is an invariant for your domain model.
"But this means that to support the UI I'd need to create similar objects as I already have in the domain model!?!"
Yup!
This order is kept in cache until the user confirms it, that is the moment to persist it.
Fine, but that cache does not belong in your domain model. It belongs on the client.
The services I might expect to see in your Bounded Context to support Order handling would include:
but it would NOT include:
All of the previous services would be persisting at the completion of each command.
is it convenient that exists services to handle the child entities of the aggregate root?
Yes, as I have outlined above. But that does not extend to helping the user through a 'work-in-progress' during which entities may violate your domain invariants. That's a task for the client.
Upvotes: 2