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

Viewed 272

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

3 Answers

You are wrong in the second option. Worktime doesn't belong to first aggregate as a child entity. It just belongs to the second aggregate as the root. The first aggregate would reference the second aggregate. You said you don't want it because it would break the integrity. Well it's all about where you want to put transactional boundaries. If you want to change the three entities in one transaction, go for the first opinion (one aggregate). If you can live with delays, go for the second option (two aggregates) with eventual consistency between them.

UPDATE:

It seems like you have a wrong idea about aggregates. Your new drawing:

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

Is wrong too. If WorkTime is a child entity in an aggregate, then the other aggregate must reference the root, not the child. And apart from that, WorkTime is one entity. You cannot draw it two times belonging to both aggregates. An entity just belongs to one aggregate.

You have 3 entities. You have these posibilities of aggregates:

  • One aggregate with the 3 entities.
  • Two aggregates: one with 2 entities; and another one with the other entity.
  • Three aggregates: each one with one entity.

Some basic rules:

  • If an aggregate references another, it must reference the root, invoking the behaviour that the root offers.
  • Consequently, a child entity of an aggregate cannot be referenced by an entity of another aggregate.
  • A child entity of an aggregate can reference another aggregate root.

What you must study is whether in a transaction you have to modify the state of the 3 entities in order to maintain the invariants the same, or you can take out one entity apart that would be another aggregate on its own.

Even if the 3 entities should form just one aggregate to keep invariants the same... you could split it too, but you would have to communicate both aggregates with events in order to have eventual consistency, as you broke transactional consistency.

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

It depends.

The more important point: it depends very little on the structure of the relationships between domain entities.

Identifying aggregate boundaries is primarily about how the state of the entities change; an in particular if any of those changes require simultaneous coordination between the two entities.

Grouping entities together into a single aggregate makes change coordination simple, but there is a cost - you can no longer make independent concurrent changes to the entities. Independent edits will either need to block waiting for each other, or one edit will need to be recalculated.

Splitting entities into separate aggregates makes concurrent editing much easier, but at a cost - coordinating changes among entities becomes harder.

A single aggregate really wants to have a single storage location -- trying to coordinate writes at two different locations (aka two phase commit) is a drag. If two entities belong to different aggregates, the model implies that they could be stored in separate appliances.

It also suggests that they have independent life cycles -- one of the advantages of aggregates is having a clear understanding of the graph of objects that get archived together.

Another thing to check is whether your object graph is multiple entities, or a single record with a compound value describing its state. If it might be a single value, then that suggests trying a single aggregate first.

You might also look at the source of the data -- especially sources for changes over time. If the changes always come from the same authorities, then it is a lot more likely to be a single aggregate. On the other hand, if WorktimeRegulation and Worktime data comes from different sources, that makes concurrent change more likely, which hints at multiple aggregates.

An important question to explore: what's the cost to the business if the entities aren't in exact agreement? If the data stored in Worktime doesn't agree with WorktimeRegulation for a minute, does anybody notice? Cases where you have some freedom to make things line up, where you have flexibility about the order in which changes are made, tend toward multiple aggregates.

Two aggregates the WorkTime is the child in both of them

Every entity in your model should belong to exactly one aggregate; you could, in theory have a WorkTime inside a WorktimeRegulation, and a WorkTime inside a WorktimeActivation, but to have the same worktime be inside a WorktimeRegulation aggregate and inside a WorktimeActivation aggregate is "against the rules". It implies that either that changes to WorktimeActivation can influence WorktimeRegulation or that WorkTime itself is something that should be broken into two separate pieces.

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).

Related