I feel like you were really close, but just missed it
order = orderRepository.Get(123)
OrderService.AddOrderLine(order: order, product: "Chocolate Cake", amount: 3))
orderRepository.Save(order)
What you were looking for is more like this:
order = orderRepository.Get(123)
order.AddOrderLine(orderService: orderService, product: "Chocolate Cake", amount: 3))
orderRepository.Save(order)
Domain services support queries within the domain model - they don't write changes to the model; the aggregates change and protect the domain state. So as a rule, you want to pass the domain service to the aggregate, then let the aggregate pass current state to the service, as needed. For instance
// Order.AddOrderLine()
orderLine = orderService.createOrderLine(this.id, product, amount);
this.lines.add(orderLine);
Note the (implied) separation of responsibilities
- The domain service creates an instance of the orderLine, using only the context provided to it. But there's no persistence here, it's just transient data in memory at this point.
- The aggregate then evaluates the orderLine to determine whether or not it satisfies the invariant. If it does, the orderLine will become part of the graph of entities reachable via the aggregate root.
However, having a Save method on repositories that concern non-root aggregates, allows for saving changes made to them directly
That's not a good thing?
orderLine = orderLineRepository.Get(31923)
orderLine.Amount = 5
OrderLineRepository.Save(orderLine)
But if this orderLine is part of an order aggregate, then presumably the Order is expected to check that the state of the order is consistent. It can't do that if you insist on being able to mutate the subordinate entities directly.
Part of the point to aggregates is that all changes to the domain model must go through paths that force the model to remain consistent. Are all of the OrderLine methods going to walk back up the graph to the Order to ensure that the invariant is still satisfied?
Of course, this might actually be a hint that the aggregate boundaries are in the wrong place. If you should be able to modify OrderLine without the entire Order, then perhaps OrderLines are aggregate roots, and not merely subordinate entities. Your domain experts might tell you that discrepancies between the Order and OrderLines aren't actually particularly expensive; ensuring that they are rare (rather than eliminating them entirely) may suffice. Horses for courses.
We have the same issue.
And I se no way to solve this problem but with transactions or with consistency check at the DB to get an exception in the worst case.
You can also use pessimistic lock, blocking aggregate root or only its parts for other clients until business transaction is completed what is to some extent equivalent to serializable transaction.
The way you go heavily depends on your system and business logic.
Concurrency is not an easy task at all.
Even if you can detect a problem how will you resolve it? Just cancel the operation or allow user to 'merge' changes?
We use Entity Framework and EF6 uses read_commited_snapshot by default, so two consecutive reads from repository can give us inconsistent data. We just keep that in mind for the future when business processes will be more clearly outlined and we can make infromed decision. And yes we still check consistency at model level as you do. This at least allows to test BL separately from DB.
I also suggest you to think over repository consistency in case you have 'long' business transactions. It is quite tricky as it turned out.
Best Answer
I believe Eric Evans wrote, "Models are neither right nor wrong; they are simply more or less useful". The question is, which model helps you solve the problem in a clean way. By just referring to the names of a number of classes, it is impossible for others to determine which model would help you solve your problem.
But as to the why should you choose one model over the other:
If one entity (A) does not make sense to load outside the context of a different entity (B), then entity A should probably be aggregated inside entity B. Also, nobody should reference A in this case.
Just because there is a relationship between your entities doesn't necessarily mean that they should be aggregated.
As an example on an aggregate, I once worked at writing an online job site. One of the features was that a person to create a CV, and companies could search for CVs. A CV would contain a list of competencies (A competency in this context mean something like, "I am higly skilled in .NET. I have used it for 12 years, last in 2012). It does not make sense to load a competency in itself. In only makes sense in the context of a CV. Though you could search for CVs matching certain competency requirements, the result would not be a list of competencies, but a list of CVs meeting the required competencies. So in this example, the CV was an aggregate root.
But are terms like 'Application Group', 'Application', and 'Configuration' really part of your domain, or is it part of your infrastructure? I'm not saying that they couldn't. Depend of course entirely on the nature of the application you are working on. But it does sound a lot like system infrastructure in my ears.