5

If I have two classes [Shift,ShiftDetails] where the [Shift] is the aggregate root. based on a specific field I want to constraint the number of instances of ShiftDetails.


I create a ShiftDetailsView class then create two methods in Shift aggregate root to control the invariant:

  • public IEnumerable ConstructShift();//based on the NumberOfSuccessions field

This method Should create number of intialized ShiftDetailsView based on that field and then add them to a list and return the result back to user(developer) as IEnumerable.

Then the user should call CompleteShift after filling the intial IEnumerable returned from the previous method:

  • public List CompleteShift(IEnumerable shiftDetailsViews)

I did two separate steps to control the number of childs through the aggregate root and I think there are better ways to do that to guarantee the (ACID) of the whole thing.


Based on the comments, The question needed more clarification, and because I try to simplify the problem, I made a mistake and change the real problem. because when we treat with domain problems we have to be accurate and clarify the exact problem. So I will try to explain it in more detail.

I have the following Two aggregates:

1. First Aggregate

1-WorkingSystem: (aggregate root)

    private readonly ICollection<WorkingTime> _assignedWorkingTimes;

    public string Name { get; private set; }
    public short NumberOfSuccessions { get; private set; }
    public Week WeekStart { get; private set; }
    public bool IsActive { get; private set; }
    public bool IsDefault { get; private set; }
    public short NumberOfWeekends { get; private set; }
    public virtual ICollection<WorkingTime> AssignedWorkingTimes { get => _assignedWorkingTimes; }

Example:

 Id |Name     | NumberOfSuccessions|WeekStart|IsActive|NumberOfWeekends 
  1 |Employees|       2            |Sunday   |  1     |   2
  2 |Lecturers|       1            |Saturday |  1     |   1

2-WorkingTime:

public string Name { get; set; }
public short NumberOfHours { get; set; }
public int WorkingSystemId { get; private set; }

Example:

Id|Name   |  NumberOfWorkingHours  | WorkingSystemId 
 1|Summer |  8                     | 1
 2|Winter |  6                     | 1
 3|General|  8                     | 2

2. Second Aggregate

3-Shift (aggregate root).

private readonly List<ShiftDetail> _assignedShiftDetails;
public string Name { get; set; }
public ShiftType ShiftType { get; set; }
public int WorkingSystemId { get; set; }
public virtual WorkingSystem WorkingSystem { get; set; }
public virtual IEnumerable<ShiftDetail> AssignedShiftDetails { get => _assignedShiftDetails; }

Example:

Id|Name            |ShiftType | WorkingSystemId 
1 |restaurant-shift|Morning   |  1

4- ShiftDetails:

public Guid ShiftId { get; private set; }
public int WorkingTimeId { get; set; }
public DateTimeRange ShiftTimeRange { get; set; }
public virtual WorkingTime WorkingTime { get; set; }

Example:

 ShiftId = 1|WorkingTimeId = 1|ShiftStart = 8:00|ShiftEnd = 16:00
 ShiftId = 1|WorkingTimeId = 2|ShiftStart = 9:00|ShiftEnd = 15:00

Now I want to constraint the number of details based on the NumberOfSuccessions for each WorkingSystem! and because I have an access to WorkingSystemId in my aggregate root Shift then I can access this info.

Could you help me to control the number of instances based on a field in the aggregate root ?

Anyname Donotcare
  • 11,113
  • 66
  • 219
  • 392
  • 1
    Well, you could simply add an AsignShiftDetail(detail) method which throws an exception when it's called more than NumberOfDetails. But choosing the best solution depends on your domain. What are you modeling? What problem are these classes trying to solve? – Ali Doustkani Aug 26 '18 at 09:31
  • is ShiftDetails and ShiftDetailsView the same?Because I feel it may be misleading if correct please fix them – amirhamini Aug 27 '18 at 08:41
  • @amirhamini no it's a smaller version of the domain class – Anyname Donotcare Aug 27 '18 at 10:02
  • 2
    Why do you need `ConstructShift()`? Do you have to setup some kind of template to be filled based on the current `shift` instance or it's just a matter of letting the client know how many shifts must be provided? – plalx Aug 27 '18 at 16:23
  • @plalx:This's a very good question. based on what i have read, the aggregate root (`shift`) in my example should control the Invariant (`the number of shift details`) in my example for the system to be in a consistent state. So I create this method for the client to call it and it will take care of returning `IEnumerable of shiftDetailsViews` it's initialized by the correct number of empty `shiftDetailsViews` then the client can fill this initial template `IEnumerable` through looping on it and setting their values then call `CompleteShift(IEnumerable shiftDetailsViews)` at the end. – Anyname Donotcare Aug 27 '18 at 21:13
  • @AnynameDonotcare Well, I wouldn't consider `ConstructShift` to be part of the invariant checking at all. The invariant should just be enforced in `CompleteShift`. `ConstructShift` is just a factory method to simplify the calling client's code, but I would probably get rid of that. Couldn't you just have `shift.detailsAssignmentPolicy` which right now only specifies a `maxNumberOfDetails`. Prior to calling on `assignShiftDetails(IEnumerable shiftDetails)` the calling code would lookup the `detailsAssignmentPolicy` to know the constraints. `ShiftDetail` would be VOs. – plalx Aug 28 '18 at 13:18
  • @AnynameDonotcare The main problem is that you haven't explained the business case properly. Why do you need `ConstructShift`. Why can't the client just check `shift.numberOfDetails`, construct `ShiftDetail` value objects and pass them to `shift.assignShiftDetails(shiftDetails)`? Why do you need `shift.constructShift`? How is it helpful? The invariant seems trivial to protect here, within `shift.assignShiftDetails`. `if (shiftDetails.count() != numberOfDetails) throw ...`. – plalx Aug 29 '18 at 13:55
  • @plalx:I edit my question and try to give a simple example, because It will be hard to explain a lot of details. but I use `ConstructShift` because I don't want the client code to check this validation? I think it should be enforced in the root aggregate so this method return `IEnumerable`. Is it okay for the client code to interact with aggregates`(ShiftDetails)` instead of the root `Shift` because I learned that it cannot be accessed directly but through aggregate root only? – Anyname Donotcare Aug 30 '18 at 09:09
  • @AnynameDonotcare Still not clear. What determines `WorkingTimeId`, `ShiftStart` and `ShiftEnd`? Is there any invariants to enforce? Can a specific shift be amended or they are just values which are entirely replaced? From what I can see above `ShiftDetail` are just value objects because they have no identity of their own. I honestly dont see any valid reason for having `ConstructShift`. If the only reason you have it is to return an exact number of instantiated `ShiftDetailsView` it's unnecessary and forces you to have mutable objs: the client could just check `shift.numberOfAllowedDetails`. – plalx Aug 30 '18 at 12:54
  • @plalx: Excuse me for this confusion, I edit my question and reexplain every detail. – Anyname Donotcare Aug 30 '18 at 19:17

2 Answers2

4

Could you help me to control the number of instances based on a field in the aggregate root ?

The using of these two methods are in the creation of new shift only. and because I'm new to DDD I think I didn't constrain the invariants in the proper way

Oh, I see -- you are worried specifically about what happens in the transaction that creates the aggregate.

The usual pattern, described by Eric Evans (chapter 6), is that the domain model will expose a factory that knows how to take data from the application and return a new aggregate instance.

Within that factory, you are creating all of the values that make up the initial state of the aggregate. The values understand their own constraints ("I am an Amount, so I need an integer greater than or equal to zero"; "I am a Money, so I need a non null Amount and a non null CurrencyCode).

The factory accepts data from the application, constructs the graph of values, and in the end calls the constructor of the aggregate with the current state.

It's a perfectly normal thing to have values in the model that have the responsibility for validating data that has been passed in, and that can include having a value that verifies two other values are in agreement.

So it might be, for example, that you factory will take a NumberOfDetails and a List<ShiftDetails>, and from them produce a VerifiedDetails type, from which the aggregate will eventually be constructed.

A very good read on this approach is Scott Wlaschin's book Domain Modeling Made Functional.

Community
  • 1
  • 1
VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91
  • Thanks for your reply, but there's a misunderstanding here, The user can't update the root after saving the data in DB. so there's nothing like that. The using of these two methods are in the creation of new shift only. and because I'm new to `DDD` I think I didn't constrain the `invariants` in the proper way, So I ask the help. How to control the `invariants` through the root. – Anyname Donotcare Aug 29 '18 at 08:05
  • @AnynameDonotcare *"The using of these two methods are in the creation of new shift only"* - I don't get why you wrote separate methods if all this happens at object instantiation time. `Complete()` suggests that this is a mutliple-step, long-lived transformation, not just a simple object instantiation. – guillaume31 Aug 29 '18 at 12:46
  • Thanks, I have some questions here. 1-Why Should I use `Factory` instead of `Constructor` to handle my variants ? 2-The meaning of `Factory` here is `static factory method in the same class` or a separate `abstract factory class` ? 3-Shoud I use factory method just in my aggregate root and it will be responsible for the creation of the whole aggregate? or every child in the aggregate has a factory method and what makes the one in the root different is controlling the different variants? – Anyname Donotcare Aug 30 '18 at 08:46
  • 4-According to your answer, you recommend to pass `List` for the factory method in the root! Is it okay for the client code (the application code) to treat child in the aggregate directly ? because I create `ShiftDetailsview` for that reason. 5-Could I ask which align with `DDD`, Considering `ShiftDetails` as value object or entity? 6-My last question excuse me, Could I ask what do you mean by `VerifiedDetails type` in this context? – Anyname Donotcare Aug 30 '18 at 08:46
  • @AnynameDonotcare since the invariant is only enforced at construction time, why is there even a `NumberOfDetails`field? Doesn't the code that does `new Shift()` know exactly the number of `ShiftDetails`it will pass? Why is it even an invariant? – guillaume31 Aug 30 '18 at 09:53
  • @guillaume31 This information comes from another entity outside the aggregate ,but to simplify the problem I said that I have the number of instances is in the aggregate root. – Anyname Donotcare Aug 30 '18 at 10:19
  • 1
    That's a very important "detail" :) If the invariant resides in aggregate A, you should have it enforced by aggregate A and not aggregate B that is being constructed... – guillaume31 Aug 30 '18 at 10:36
  • @guillaume31 :I'm so sorry, I'm new to this, I edit my question and clarify every thing. Could u take a look please – Anyname Donotcare Aug 30 '18 at 19:18
  • @VoiceOfUnreason Could you take a look at my question again please – Anyname Donotcare Sep 01 '18 at 11:56
  • @guillaume31 Could you explain how to enforce this invariant from `aggregate A`? – Anyname Donotcare Sep 01 '18 at 11:58
  • There's a - slightly old - example there : http://udidahan.com/2009/06/29/dont-create-aggregate-roots/ – guillaume31 Sep 01 '18 at 17:51
  • Note that this will embark `WorkingSystem` in the transaction - something you might not want if there's a lot of concurrent access to this aggregate. Immediate consistency/atomicity comes at a cost. – guillaume31 Sep 01 '18 at 17:55
-1

I would add a method named AssignedShiftDetail(ShiftDetail shiftDetail) to the Shift class and will implement any related logic inside this method.

S.Samani
  • 33
  • 1
  • 5