1

Let's assume a project presenting those specifications:

  • Each Employee can organize a Meeting inviting other Employees.
  • Each Employee can accept the invitation to participate to the Meeting, while the number of max Participations isn't exceeded.
  • Any Manager of the Employee creator can cancel the Meeting at any time.

IMO, the invariants are:

  • A Meeting exists as long as the creator (Employee) exists (not deleted or flag as deleted).
  • There shouldn't be at any time a Meeting containing a number of participants superior to the limite intended.
  • Any Employee that cancels its created Meeting should have also this Participations canceled /deleted.
  • When a Manager cancels an Employee's Meeting, both the Meeting and the Participations should be deleted.

Should I make:

  • the Employee an Aggregate Root, containing the collection of its created Meetings.
  • Meeting being thus an inner entity of Employee and containing a collection of Participations.
  • Manager as the other Aggregate Root, containing a collection of its Employees.

Thus there would be just to Aggregate Roots: Employee and Manager.
Indeed, a Manager could be fired and then is not part of an invariant with the Employee, and vice versa.

In details:
_ Employee would provide a method createParticipation, encapsulating checks for the important rules like whether the number max of participants is exceeded.
_ Employee would provide also a Factory to create Meeting, allowing to always assign the correct EmployeeId to the Meeting.
_ Only two repositories would be created: EmployeeRepository and ManagerRepository, avoiding direct access to respective inner parts.
(that deals only with creations, deletions would be similar)

Therefore, in order to create a Meeting's Participation, my entry point would be the creator(Employee) that I retrieve through the EmployeeRepository.

Does it make sense in order to follow strict DDD practice?

Mik378
  • 21,881
  • 15
  • 82
  • 180

1 Answers1

3

Your Employee aggregate is way too big. This creates concurrency issues. What would happen if two employees accept the invitation in the same time? One transaction would be rolled back because they try to modify the same aggregate. Unless you designed some fancy conflict resolution logic.

Instead, consider Participation and Meeting to be separate aggregates.

Bartłomiej Szypelow
  • 2,121
  • 15
  • 17
  • Indeed, I agree. Currently, that is what I have: Participation and Meeting being Aggregate Roots, as you promote. However, what about the invariant: "if an Employee is deleted, all its created meetings + participations concerning it should also be destroyed". How to handle that atomically? Should I go with eventual consistency in this case? – Mik378 Feb 04 '14 at 11:45
  • 1
    I see no benefit of transactional consistency here. Meetings are planned hours, days even weeks before. No one would ever notice any difference. – Bartłomiej Szypelow Feb 04 '14 at 11:45
  • Ok, but besides, how to handle the specification: "does not exceed the number max of participants" if `Meeting` and `Participation` are both Aggregate Roots? I was reading the Evan's blue book, and in his example of "purchase order" (dealing with an unexpected number of OrderLineItems), he resolves the problem by gathering `Order` and `OrderLineItems` in one Aggregate Root whose entry point is `Order`. My use case is similar, isn't it? – Mik378 Feb 04 '14 at 11:50
  • 2
    Meeting should have a list of value objects referencing participation (ParticipationId). This article might help: http://dddcommunity.org/library/vernon_2011/ – Bartłomiej Szypelow Feb 04 '14 at 11:52
  • :) I have already read the Vernon's book, I enjoyed it. So using references by Id (since `Participation` is another Aggregate, from the `Meeting` perspective), I would validate the participation through the number of participationIds, and let or not the participation be applied. If two `Employee`'s participate at the same time, I guess that the validation barrier might be bypassed because of concurrency issues. Therefore, should I lock the database anywhere (on the whole `Meeting` table (or node, if using a graph database)), while the transaction occurs? (thanks a lot for your answers :)) – Mik378 Feb 04 '14 at 12:06
  • Do you mean that you want to invite more participants than you allow to enter? – Bartłomiej Szypelow Feb 05 '14 at 11:01
  • Actually, it's like events, I send invitations but to more people than needed. (in order to be sure to have the required number of people) Interested people just declare their participation. Therefore, it could easily exceed the number max of persons I would allow for this event. I would like to have a mechanism to check for the limit, and I thought about locking the Meeting itself at each attempt of participation. – Mik378 Feb 05 '14 at 11:04
  • Of course, it would be `Meeting` that would do the check of the number limit to allow the participation to occur or not. – Mik378 Feb 05 '14 at 11:11
  • I would make Meeting and Participant part of the same Aggregate Root in order to keep the invariant. – Mik378 Feb 05 '14 at 22:33
  • Yes, in this case putting Participantion in the Meeting aggregate has more sense. – Bartłomiej Szypelow Feb 06 '14 at 11:19
  • 1
    Thanks a lot for your advices :) I made Participation a simple value object referencing the UserId of User's entity (so that I can link the whole for the graph database). Meeting having a collection of Participations – Mik378 Feb 06 '14 at 11:21