Search code examples
c#oopdomain-driven-designaggregationaggregateroot

Should one root be for the whole aggregate graph or should multiple roots be in the same graph?


I'm confused about the structure of the following aggregate in my design.

Should one root be for the whole aggregate graph or should multiple roots be in the same graph?

My case:

WorktimeRegulation

Id|    Name            |   NumberOfAvailableRotations|  IsActive 
 1|    General Rule    |          2                  |    true 

WorkTime

Id|  Name   |   NumberOfHours| NumberOfShortDays |WorkTimeRegulationId 

1 | Winter  |     8          |    1              |    1
2 | Summer  |     6          |    0              |    1

WorkTimeActivation

 Id|  StartDate  | EndDate      | IsDeFacto |WorkTimeId    
 1 |  2018-10-1  | 2018-12-30   |    1      |    1

Note: I consider (StartDate&EndDate) as DateTimeRange(valueobject)


Now Should I consider the WorktimeRegulation the root for the whole graph, so it controls both (WorkTime,WorkTimeActivation)?

WorktimeRegulation(ROOT)
        |
        V
    WorkTime
        |
        V
WorkTimeActivation

Or Should I have Two aggregates like this:

WorktimeRegulation(ROOT)          |        WorkTime(ROOT)  
        |                         |             |
        V                         |             V
    WorkTime                      |      WorkTimeActivation

So In the second solution I have two roots! But If I consider WorkTime as a root It will be available to be used separately from the WorktimeRegulation and I don't want this because this will break the integrity of the first aggregate.


Or Should I have Two aggregates like this:

WorktimeRegulation(ROOT)          |      WorkTimeActivation(ROOT)  
        |                         |             |
        V                         |             V
    WorkTime                      |         WorkTime

Based on the Comment :

Could you just explain the business problem this solves and what invariants must be protected instead of the database structure

  • Every WorkTimeRegulation should have at least one WorkTime to be valid. So in the creation of WorkTimeRegulation I make sure to enforce this invariant.
  • I need to check any conflicts between activated WorkTimes, so after that I enforce specific activation based on the recent activation or IsDeFacto enforced by the user

  1. What behaviors can be performed on WorkTime? So far I can see it can be activated.

    Yes, In addition to updating the WorkTime, It can be activated.

  2. In order to validate the activation do you need the entire activation history or only the latest event?

    Not the entire activation history, I could say the most recent single activation history.

  3. Does the activation rules span multiple WorkTime (e.g. if one is active then the other can't be)?

    Yes Per WorktimeRegulation I mean If I have WorktimeRegulation contains 3 WorkTimes then there's one and only one active Worktime for this regulation.

  4. Can the name, numberOfHours, etc of activations be modified?

    If You meant IsDeFacto for Activation then yes (Through this attribute the user can enforce specific activation if there was conflict between two activations of the same WorktimeRegulation),and because the previous attributes in your question belong to WorkTime not Activation, the numberOfHours can be modified.

  5. Should modifying these details conflict with other business operations (e.g. activating a WorkTime?

    If You meant by these details the previous attributes,then the answer is no.

  6. "if there was conflict between two activations of the same WorktimeRegulation" Could you expand on what kind of conflicts could occur? Shouldn't the system prevent activation conflicts from occuring? How does the activation process work exactly?

    According to the business expert explanation: It might happen a conflict and the system should not prevent it but it alerts this conflict to the end user. The activation performed like this: The user selects a specific WorkTime in WorkTimeRegulation to activate, Then the activation popup allowing the user to insert StartDate and expected EndDate, and when the user clicks activate, It should check for any conflict with the previous activation in the same WorkTimeRegulation and alert the user if he wants the priority for the previous activation then He should use IsDefacto to enforce one of them in the case of conflicts. NOTE: The end user doesn't know exactly the end date for activating worktime in advance, So he inserts expected end date, So the conflict may happen.

  7. what would be the cost of having WorktimeRegulation point to a non-existing WorkTime for a short period of time (e.g. regulation.activateWorkTime(workTimeId) but then the activated work item gets deleted)? Can WorkTime get deleted/archived?

    WorktimeRegulation can't point to a non-existing WorkTime the work time can't be deleted or archived if it activated once at least, Just activated and (deactivated when activate other work time in the same regulation)

  8. Can WorkTime be associated to a different Regulation after it's creation?

    No

  9. Are you sure it doesn't matter if for instance, name or numberOfHours changes at the same time as someone attempts to activate a WorkTime?

    Now I get your question, The user can't change WorkTime after the first activation.

  10. I'm still not clear as to why conflicting activations could occur. Why would you want to allow overlapping activation periods, but then use a flag to specific which one to enforce. Seems easy enough to prevent overlapping activation periods instead, no? Is it really possible from the business perspective that two activation periods overlap conceptually? Why do users even enter the startDate and endDate manually? Couldn't you just track the dates at which activation/deactivation occurs in the system?

    According to the business expert, The start date of the activation for a specific WorkTime is a decision taken from the company manager(not planned) to work with this WorkTime and after a time duration (can't be predicted because It's a decision) The company manager take a decision to switch to another WorkTime, So HR employee executes the decision by inserting a specific start date and inserts roughly end date, So the next activation may conflict with the most recent one.

  11. What's the actual business meaning of a WorkTime activation/deact?

    During the year, The employees whom subject to a specific WorkTimeRegulation, Their WorkTimes changed based on the activation, I mean The WorkTime may be 8hours from Oct to Dec then switched to 6 hours(another WorkTime) from Jan to Sep and so on It's like a cycle.

Note: The decision provides for start date only (e.g Winter WorkTime Starts from date ...) and not specifying the end date! So it is inserted roughly and as a result the start date for the next activation may conflict with the end date for the previous one)

  1. Is it because they might create activations ahead of time? In that case it's the same thing IMO. Enforcing the end date of a conflicting activation to be exactly when the primary activation starts is pretty much the same as having a deFacto concept, because when you use deFacto to force an activation over another that overlaps you implicitly state that the other activation has ended when the deFacto one starts, no?

    After asking the business expert about this, he explained the case like this: The HR department sends a suggestion for the activation period (Start date, expected end date) to the company manager, After the manager confirmed the suggestion, The HR executes the suggestion, and they need an alert before the end date of the current active worktime to notify them to send the next suggestion to the company manager so that they can use defacto to enforce the next period or just activate not overlapped worktime, So yes the activations made a head of time

13.I think the only thing we need to work out is whether multiple activate suggestions can be made in parallel and what are the rules?

No multiple activate suggestions can be made in parallel for the same WorkTimeRegulation, It may be for multiple WorkTimeRegulation but not for the same WorkTimeRegulation.

  1. Furthermore, does the suggestion approval process need to be modeled in the system (e.g. tracking who approved, perhaps by uploading an email copy of the approval)?

    No this process is performed manually, no need to model it.

  2. Finally, should you only be able to activate a suggested activation that was approved?

    YES


Solution

  • Assuming the following rules:

    • There can only be one active work time per work time regulation at any given time.

    • There can only be one pending work time activation suggestion at any given time.

    • The suggestion approval process doesn't have to be tracked in the system.

    • A work time regulation can't point to a non-existing work time. A work time can't be deleted or archived once it's been activated once.

    I think it would be reasonable to model this so that WorkTimeRegulation is an aggregate root that has a collection of WorkTime entities. Furthermore, it would have to model the current activation suggestion as well as the current active WorkTime in order to enforce the above-mentioned rules. The suggestion/active state could either be modeled on the WorkTimeRegulation itself or the WorkTime instances (in which case the root must ensure only one is active at a time, etc.). That's really a design preference at that point and both strategies would allow you to protect the invariants. Furthermore, the WorkTime entity should also track whether or not it was ever activated in order to prevent further modifications to it's other attributes (e.g. numberOfHours).

    Here's a draft of what it may look like:

    enter image description here

    The above model would ensure all required data to check invariants are part of the same root and models the minimum structure required to store the state and enforce the rules in memory.

    However, this model alone doesn't solve the activation history problem. You dont need the entire history to enforce the invariants, so you dont need to model it within the boundary of an AR, but the business will certainly still need such data.

    At this point there are several strategies you could use, like:

    • Model your ARs with event sourcing, getting history for free.
    • Dispatch domain events in parallel to storing the current AR state and store them in an event store, still allowing you to project the history of events.

    • Model an immutable ActivationHistoryEntry AR. E.g. historyEntry = regulation.activate(workTimeId); save(historyEntry); save(regulation);. This is very similar to domain events.

    The above model is certainly not perfect and could be refined. For instance, I also thought of modeling the concept of an Activation VO rather than using a boolean flag, which could be of different types (active, inactive, suggested). This would perhaps allow for a more expressive language, where for instance an ActiveActivation can only be obtained through the activation of an ActivationSuggestion, where the ActiveActivation holds onto the suggested end date (if available, to notify HR they need to send the next suggestion).