5

If I have two aggregates like this:

First Aggregate :

  • WorktimeRegulation (Root)
  • Worktime
  • RegulationEnrolment

Clarification with data:

WorktimeRegulation :

 public class WorkTimeRegulation : Entity<Guid>, IAggregateRoot
    {
        private WorkTimeRegulation()//COMB
       : base(Provider.Sql.Create()) // required for EF
        {
        }
        private WorkTimeRegulation(Guid id) : base(id)
        {
            _assignedWorkingTimes = new List<WorkingTime>();
            _enrolledParties = new List<RegulationEnrolment>();
        }
        private readonly List<WorkingTime> _assignedWorkingTimes;
        private readonly List<RegulationEnrolment> _enrolledParties;
        public string Name { get; private set; }
        public byte NumberOfAvailableRotations { get; private set; }
        public bool IsActive { get; private set; }
        public virtual IEnumerable<WorkingTime> AssignedWorkingTimes { get => _assignedWorkingTimes; }
       public virtual IEnumerable<RegulationEnrolment> EnrolledParties { get => _enrolledParties; }
        //...
    }

Id|    Name            |   NumberOfAvailableRotations|  IsActive 

 1|    General Rule    |          2                  |    true   

Worktime :

public class WorkTime : Entity<Guid>
    {
        private WorkTime()
      : base(Provider.Sql.Create()) // required for EF
        {
        }
        private WorkTime(Guid id) : base(id)
        {
            ActivatedWorkingTimes = new List<WorkingTimeActivation>();
        }
        private ICollection<WorkingTimeActivation> _activatedWorkingTimes;

        public string Name { get; set; }
        public byte NumberOfHours { get; set; }
        public byte NumberOfShortDays { get; set; }
        public Guid WorkTimeRegulationId { get; private set; }
        public virtual ICollection<WorkingTimeActivation> ActivatedWorkingTimes { get => _activatedWorkingTimes; private set => _activatedWorkingTimes = value; }
        //....
   }

Id|  Name   |   NumberOfHours| NumberOfShortDays |WorkTimeRegulationId 

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

Second Aggregate :

  • Shift (Root)
  • ShiftDetail
  • ShiftEnrolment

Clarification with data:

Shift :

  public class Shift : Entity<Guid>, IAggregateRoot
    {
        private readonly List<ShiftDetail> _assignedShiftDetails;
        private readonly List<ShiftEnrolment> _enrolledParties;


        public string Name { get; set; }
        public ShiftType ShiftType { get; set; }
        public int WorkTimeRegulationId { get; set; }
        public bool IsDefault { get; set; }
        public virtual WorkingTimeRegulation WorkTimeRegulation { get; set; }
        public virtual IEnumerable<ShiftDetail> AssignedShiftDetails { get => _assignedShiftDetails; }
        public virtual IEnumerable<ShiftEnrolment> EnrolledParties { get => _enrolledParties; }
        //...........
   }

Id|  Name      |  ShiftType  |  WorkTimeRegulationId  | IsDefault 
1 | IT shift   |  Morning    |    1                   |  1 

ShiftDetail:

  public class ShiftDetail : Entity<Guid>
    {
        public Guid ShiftId { get; private set; }
        public Guid WorkTimeId { get; private set; }
        public DateTimeRange ShiftTimeRange { get; private set; }
        public TimeSpan GracePeriodStart { get; private set; }
        public TimeSpan GracePeriodEnd { get; private set; }
        public virtual WorkTime WorkTime { get; private set; }

        private ShiftDetail()
        : base(Provider.Sql.Create()) // required for EF
        {
        }
        //..........
   }

ShiftId  WorkTimeId shift-start  shift-end   
  1          1        08:00        16:00
  1          2        08:00        14:00

My questions here:

  • Is it okay for non aggregate-root (ShiftDetail) to hold a reference for another non aggregate-root (WorkTime)?
  • The domain expert clarify that: To create a valid shift then we should have a shift detail for every worktime related to a specific worktimeRegulation. And can't update the number of workhours in worktime if there's reference in shiftDetails. The previous example shows that we have two worktimes(winter,summer), So we have a shiftdetail for winter to stick with 8 workinghours and a shiftdetail for summer to stick with 6working hours. Now I feel that invariant of shift details controlled by non-aggregate root(worktime) How to force this invariant?

  • According to the previous information do I make a mistake related to aggregates specifications?

Anyname Donotcare
  • 11,113
  • 66
  • 219
  • 392

1 Answers1

1

Is it okay for non aggregate-root (ShiftDetail) to hold a reference for another non aggregate-root (WorkTime)?

No, unless they exist in the same Aggregate.

You may hold references only to other Aggregate root's ID.

You may hold a reference to the ID of a nested Entity from another Aggregate but you should note that this ID is opaque, you may not assume anything about how it is used internally by it's Aggregate root to find the nested Entity.

Now I feel that invariant of shift details controlled by non-aggregate root(worktime) How to force this invariant?

You can enforce an invariant in two ways:

  1. Inside an Aggregate. This means that the Aggregate must me sufficient large, it must own all the state it needs to. This enforcement is strongly consistent.

  2. Coordinated by a Saga/Process manager. This component react to changes inside possible multiple Aggregates and send commands to other Aggregates. A Saga is the opposite of an Aggregate. This enforcement is eventually consistent.

Constantin Galbenu
  • 16,951
  • 3
  • 38
  • 54
  • Thanks a lot for your reply, I will be grateful if you explain more how to implement this in my specific case. – Anyname Donotcare Sep 09 '18 at 08:58
  • 1
    @AnynameDonotcare please explain more about your invariant – Constantin Galbenu Sep 09 '18 at 09:10
  • Firstly for each `WorkTime` instance related to a specific `worktimeregulation` I should have a `ShiftDetail`, in my example I have two `worktimes(winter,summer)` so I should have two `shiftdetails` one for the `winter` and one for the `summer`, Secondly the `ShiftTimeRange` `shiftdetails` in should equal the `NumberOfHours` in worktime, In my example for `winter` worktime `NumberOfHours = 8` So the `ShiftTimeRange` = `08:00 to 16:00` -->`(16-8 = 8hours)` – Anyname Donotcare Sep 09 '18 at 09:21
  • @AnynameDonotcare explain me as you'd be explained by a domain expert, as I won't know anything about programming – Constantin Galbenu Sep 09 '18 at 11:01
  • I tried to explain, but excuse me because my first language isn't English. We have here a very basic top rule `worktimeregulation` for each one there may be at least worktime(the most important attribute is `NumberOfworkHours`), and if we have a general rule this rule has two rotation per year (number of worktime) -->(winter,summer), where we work `8hours` in `winter` and `6hours` in `summer`. Now we want to know when the employees should attend? So we create `Shifts` and those shifts related to specific `worktimeregulation`. – Anyname Donotcare Sep 09 '18 at 11:11
  • For Every `Shift` there are number of details(the actual from-to) and the number of details are specified based on the number of `worktime` for the `worktimeregulation` which specified in `Shift`. So this's the first `invariant`, for `winter` the employees should attend from `08:00 to 16:00` and for `summer` from `08:00 to 14:00` and by this `invariant` we are confident that when the `winter` comes we have the required `shiftdetails`. – Anyname Donotcare Sep 09 '18 at 11:16
  • The second `invariant` we need to make sure that in `winter` for example the `to-from` = the number of workhours – Anyname Donotcare Sep 09 '18 at 11:17
  • 1
    @AnynameDonotcare from what I see you can enforce the invariant in a Factory that get's as input the worktimeRegulation and it returns a ShiftTimeRange – Constantin Galbenu Sep 09 '18 at 11:51
  • 1
    @AnynameDonotcare this Factory could be a static method on the Shift Aggregate that returns a Shift instance – Constantin Galbenu Sep 09 '18 at 12:15
  • But how to prevent UPDATE `NumberOfHours` in `worktime` if it's used ? I mean there was a reference in `ShiftDetails` – Anyname Donotcare Sep 10 '18 at 10:03
  • @AnynameDonotcare you are not allowed to have inter-aggregate references (only by ID). Make a Value object instead – Constantin Galbenu Sep 10 '18 at 10:14
  • Excuse me I'm a beginner to `DDD` so I try to avoid the `perfectionism state` and try to do the things right and learn from you. `inter-aggregate references` I can't get what you are talking about, So I will be grateful If u could explain in detail and in the context of my specific case model. – Anyname Donotcare Sep 10 '18 at 10:51
  • @AnynameDonotcare for example, in Shift aggregate you have a reference to another aggregate (namely WorkingTimeRegulation) amd this is bad. Instead you should have a reference only to the WorkingTimeRegulationId – Constantin Galbenu Sep 10 '18 at 11:07
  • @AnynameDonotcare to prevent it in a strongly consistent manner is impossible (because there are more than one Aggregate involved); you can check before mutating it if it is used and/or use a Saga to detect such modifications. – Constantin Galbenu Sep 10 '18 at 11:11