DDD 中的显式状态建模

Explicit State Modeling in DDD

我一直在寻找管理根聚合 state/life-cycle 的内容,发现了一些关于使用 Explicit State ModelingExplicit Modeling 优于 State Pattern 的好处的内容,它更干净而且我比如我如何让我领域的明确概念处理它们自己的行为。

我读到的其中一件事是 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)。

问题是这个想法的描述非常简短,周围没有太多内容,当我开始实施它时出现,考虑到我是 DDD 的新手,概念开始重叠,这里有一些我希望更多有经验的 DDD 工程师能提供帮助的问题,或者至少有人比我更好地解释了文本。

  1. 按照本文的示例,我将如何检索所有门(打开和关闭的门)的列表,此结果集将映射到哪个域实体?
  2. 如果所有显式状态模型都是 entities/aggregates,根聚合会是什么?
  3. Root Aggregate 和那些明确建模的实体之间没有引用是否正常?
  4. 如果聚合根(假设是通用门实体)returns 是一个显式状态实体,存储库将如何保存它而不暴露实体或聚合的状态?
  5. 或者所有这些显式实体都是它们自己聚合的根?

我不希望得到以上所有的回答,我只是分享我的想法,所以你可以看到我的立场,因为我分享代码有太多歧义但是希望文章和书中的片段能有所帮助。

一个 git 存储库或一个示例项目来解决其他具有显式建模的 DDD 组件将如何真正有用(我已经检查了一百万个存储库,但 90% 没有运气)。

注意:我没有使用 CQRS

来自媒体文章的示例:

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); 
    }
}

书中的例子:

// 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);
 }
}

Note: I am not using CQRS

我认为这是你面临的最大挑战。

如果您不尝试将它们用于可能不受限于为特定用例设计的显式模型的查询,那么为正在实施的用例检索显式建模的实体不会造成如此令人头疼的问题.

我使用支持“table-splitting”的Entity Framework,这在这种情况下可能会有所帮助。使用此方法,许多实体可以映射到相同的 table,但每个实体都可以处理 table 中字段的子集并具有专用行为。

// 用于一般查询

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;
    }
}

// 用于开门用例

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);
    }
}

// 用于关门用例

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);
    }
}