24

I'm new to DDD and I'm stuck with many-to-many relationships. E.g. we have two aggregate roots - Tasks and Workers.

Contract is definitely not aggregate root, because it has no sense without Task and Worker. So, it should be part of some aggregate. But which aggregate should it belong to? We need to know both summary costs of all task contracts and summary costs of all worker contracts. And it's natural for me to have contracts collection both in Task and in Worker.

Well, I can move Costs calculation to domain service, but I afraid it's a step forward to anemic model. Is there common way to deal with many-to-many relationships and preserve reach domain model?

Thanks!

Class diagram

Lukasz
  • 7,572
  • 4
  • 41
  • 50
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459

2 Answers2

17

By following related questions linked in the sidebar, I found this interesting article:

DDD & Many to Many Object Relational Mapping

It seems to recommend what I was intuitively thinking anyway: that it is not in fact natural for worker and task to take on a dependence on contract. That is, the concept of a "worker" is still meaningful without the concept of a "contract" (and similarly for task), so the entity embodying that concept should not have a dependency on the contract entity.

To display the contracts assigned to a given task, or the contracts assigned to a given worker, you will need to run a domain query. This is in fact an appropriate use for a domain service, and better reflects the reality of your domain if you think about it.

I also note that you say "Contract is definitely not aggregate root, because it has no sense without Task and Worker." That is actually the precise reason that Contract is the aggregate root.

So, my suggestion, with arootbeer's insight from the comments incorporated: Proposed new class diagram

Domenic
  • 110,262
  • 41
  • 219
  • 271
  • 2
    I think placing `GetCosts()` on `Task` and `Worker` is muddling things - neither `Task` nor `Worker` embody an explicit cost; that is explicitly the domain of the `Contract`. A `Worker` has a `Rate`, and a `Task` has a `Period` or `Duration`; the combination of the two in a `Contract` specifies the `Cost`. `GetCosts()` would likely be a domain search aggregation function. – Matt Mills Apr 27 '11 at 15:35
  • @arootbeer: Probably right! I didn't really understand the costs part of the domain well enough so I just copied them over. I'll edit it to reflect your insight. – Domenic Apr 27 '11 at 15:37
  • Well, I was thinking about making Contract as Aggregate root also. But real business value is not Cost of contract - it's total contracts cost of Task and Worker. So, when both Task and Worker don't have collection of Contracts, model becomes anemic. We need to load all contracts by service and make a sum of costs. BTW there is no rate for worker - different contracts have different costs. – Sergey Berezovskiy Apr 28 '11 at 08:33
5

Contract appears to me to be a first-class object in your design. Your claim that it doesn't make sense outside of the context of both a worker and a task is certainly true, but that doesn't mean it isn't an aggregate root in its own right.

Presumably Contract has its own logic for calculating its cost, based on some attributes of the task and worker associated with it. Similarly, there is context that Task and Worker contain that are not relevant to Contract.

The gap you need to jump is moving the relevant context into the Contract object. Let it store the worker's rate, and the task's period (in addition to the respective IDs, which are only modeled implicitly above), and calculate cost dynamically.

--EDIT--

As Domenic states, your comment is a good candidate for a follow-up question. But I will say that once you get the Task and Worker IDs onto the Contract, reporting becomes a trivial task.

Matt Mills
  • 8,692
  • 6
  • 40
  • 64
  • 1
    that's just an example. So we can think of a Contract as a Entity without cost calculation logic - simple amount of money to be paid for work. Is there way to build reach model for total task/worker costs calculation? – Sergey Berezovskiy Apr 28 '11 at 08:40
  • @lazyberezovsky that's a good question---definitely ask it as a follow up though, since it strays a bit. When you clarify that the business value works that way, it does make me rethink things, and I'm not sure what I would do... – Domenic Apr 28 '11 at 13:31
  • Won't it be kind of anemic? No logic, just data, and all code in services. Need to think about this.. – Sergey Berezovskiy May 06 '11 at 13:00
  • @lazyberezovsky - It will have at least the basic logic of multiplying term by rate, but that is still logic. And it provides you with the ability to create more complex contracts in the future without changing your dependencies. – Matt Mills May 06 '11 at 14:13