0

I've been looking managing Root Aggregate state/life-cycle and found some content about the benefits of using Explicit State Modeling or Explicit Modeling over State Pattern, it's much cleaner and I like how I can let explicit concepts of my domain handle their own behavior.

One of the things I read was this article that is influenced by Chapter 16 in "Patterns, Principles, and Practices of Domain-Driven Design - Scott Millett with Nick Tune" book (here's a code sample for the full example).

The problem is the idea is described very briefly and there is not much content around it and that appeared when I started to implement it and given that I am new to DDD, the concepts started to overlap, and here are some of the questions that I am hoping more experienced engineers in DDD would help or at least someone has interpreted the text better than me.

  1. Following the article's example, how would I retrieve a list of all doors (that are both open and closed), what Domain Entity would this result-set map to?
  2. If all the explicit states models are entities/aggregates, what would be the root aggregate?
  3. would it be normal that there is no reference between Root Aggregate and those explicitly modeled entities?
  4. And if the Aggregate Root (let's say a generic Door entity) returns an explicit state entity, how would the repository save it without exposing the state of the entity or aggregate?
  5. Or are all these explicit entities root of their own aggregate?

I am not expecting to get all the above answered, I am just sharing the thoughts that am stuck at, so you are able to see where I am standing, as there is too much ambiguity for me to share code but I hope the snippets from the article and the book can help.

A git repository or a sample project addressing how would other DDD components with Explicit modeling would be really helpful (I have checked a million repositories but 90% with no luck).

Note: I am not using CQRS

Example from Medium Article:

interface ClosableDoor 
{ 
    public function close(); 
}
// Explicit State. (third attempt) 
class CloseDoorService() 
{ 
    // inject dependencies
   
    public function execute($doorId) 
    {
        $door = $this->doorRepository->findClosableOfId($doorId);
        if (!$door) { 
            throw new ClosableDoorNotFound(); 
        }
        $door = $door->close(); 
        $this->doorRepository->persist($door); 
    }
}

Example from the book:

// these entities collectively replace the OnlineTakeawayOrder entity (that used the state pattern)
public class InKitchenOnlineTakeawayOrder
{
 public InKitchenOnlineTakeawayOrder(Guid id, Address address)
 {
 ...
 this.Id = id;
 this.Address = address;
}
 public Guid Id { get; private set; }
 public Address Address { get; private set; }
 // only contains methods it actually implements
 // returns new state so that clients have to be aware of it
 public InOvenOnlineTakeawayOrder Cook()
 {
 ...
 return new InOvenOnlineTakeawayOrder(this.Id, this.Address);
 }
}
public class InOvenOnlineTakeawayOrder
{
 public InOvenOnlineTakeawayOrder(Guid id, Address address)
 {
 ...
 this.Id = id;
 this.Address = address;
 }
 public Guid Id { get; private set; }
 public Address Address { get; private set; }
 public CookedOnlineTakeawayOrder TakeOutOfOven()
 {
 ...
 return new CookedOnlineTakeawayOrder(this.Id, this.Address);
 }
}
Francesc Castells
  • 2,692
  • 21
  • 25
Mazen Elkashef
  • 3,430
  • 6
  • 44
  • 72
  • It is an opinion, but, tbh I find this pattern bad. Basically you need to know the state of an aggregate to be able to look for it. You don't have a simple `findById`. What if you have an action that you can call in each state? The noise come back as you have to copy the code for it in each state. If you are thinking that you can have a base implementation that each of the states can extend to have the common code, you can do the same with a single object that have all the methods throwing an exception and each state just override what you need, and there's no noise also there. – rascio Oct 23 '21 at 19:57
  • Thanks @rascio for your input, I agree that what you are suggesting is a simpler and readable design, I am just looking for alternatives when the model gets more complex, for example having more than 8 states and each one would have various sets of actions that are available for each state. Also, I like the idea of being able to be explicit in my design, I find it very helpful because no technical documentation will keep up with code changes, so our tests and code are our best documentation and guaranteed to be up to date, what do you think? – Mazen Elkashef Oct 27 '21 at 00:29
  • Yet, why don't a big base class/interface with all the possible actions/methods with a default implementation of throwing a not permitted error, and then one extension per state overriding just what it can do, and repository's clients knowing just the interface. By the way in a so complex case I would also check if that 8 states can be splitted in multiple entities. Last thing what I don't like the most about the door service is that with a `findClosableOfId` you cannot say if the entity doesn't exists, or it exists in a different state. – rascio Oct 27 '21 at 09:31

1 Answers1

1

Note: I am not using CQRS

I think this is the biggest challenge you have.

Retrieving explicitly modelled entities for the purpose of the use case being implemented would not cause such a headache if you were not also trying to use them for queries that may not be constrained to an explicit model designed for a specific use case.

I use Entity Framework which supports "table-splitting" that could help in this situation. Using this, many entities can be mapped to the same table but each can deal with a subset of the fields in the table and have dedicated behaviour.

// Used for general queries

class Door
{
    public Guid Id { get; private set; }
    public State State { get; private set; }

    // other props that user may want included in query but are not
    // relevant to opening or closing a door
    public Color Color { get; private set; }
    public Dimensions Dimensions { get; private set; }
    public List<Fixing> Fixings { get; private set; }
}

class DoorRepository
{
    List<Door> GetDoors()
    {
        return _context.Doors;
    }
}

// Used for Open Door use case

class ClosedDoor
{
    public Guid Id { get; private set; }
    public State State { get; private set; }

    public void Open()
    {
        State = State.Open;
    }
}

class ClosedDoorRepository
{
    List<ClosedDoor> GetClosedDoors()
    {
        return _context.ClosedDoors.Where(d => d.State == State.Closed);
    }
}

// Used for Close Door use case

class OpenDoor
{
    public Guid Id { get; private set; }
    public State State { get; private set; }

    public void Close()
    {
        State = State.Closed;
    }
}

class OpenDoorRepository
{
    List<OpenDoor> GetOpenDoors()
    {
        return _context.OpenDoors.Where(d => d.State == State.Open);
    }
}
Neil W
  • 7,670
  • 3
  • 28
  • 41
  • Splitting read from write concerns makes sense, thanks! Just trying to map out here the classes above to DDD, would the explicit models be aggregate roots in the domain layer and the generic Door read model is just a technical concern and doesn't represent my domain? – Mazen Elkashef Oct 22 '21 at 15:47
  • Yes. OpenedDoor and ClosedDoor models would both be ARs. The Door could either simply for querying any type of door, or, depending on your situation, the AR that is used to support use cases that change color, dimensions and fixing (in my example). – Neil W Oct 22 '21 at 16:04
  • Thanks for helping me sort my thoughts, honestly I was hoping for a more agnostic solution and not to have to rely on concrete tech like EF, but that will be a good headstart for me. – Mazen Elkashef Oct 27 '21 at 00:34