【问题标题】:Howa and where to detain relation logics between two ValueObjectsHowa 以及在哪里保留两个 ValueObject 之间的关系逻辑
【发布时间】:2018-01-08 15:32:45
【问题描述】:

我有一个名为Event 的实体,它使用两个值对象EventTypeEventCategory。每个允许的类别都有一个允许的EventType 子集。

I.E.

WeatherCategory =>     RainEvent
                       SunEvent
CompetitionCategory => FootbalMatch
                       TennishMatch
AnyCategory     =>     RainEvent
                       SunEvent
                       FootbalMatch
                       TennishMatch

当然,我想以某种方式描述这种关系。起初,我使用外部Validator 检查实体的两个 ValueObject 之间关系的有效性。现在我需要根据特定类别获取事件集。

基本上,我拥有的是一个为我获取事件并按类别过滤的存储库合约。不幸的是,我的基础设施没有类别信息,所以我必须根据EventTypes 的列表映射查询。

我想知道 - 根据 ddd - 如果我应该将两个 ValueObjects 之间的映射信息添加到两者之一中,我是否最好将它添加到我的实体中,方法是让两个 valueobjects 没有任何关系信息或将其放入其他类(不知道这个)。

【问题讨论】:

  • 在您看来,这个不变量是否适合 EventAggregate?该聚合体还有哪些其他职责?
  • @ConstantinGalbenu Category 和 EventTypes 之间的映射在所有聚合中始终相同。当然,每个聚合当时可以有一个 EventCategory 和 EventType。我们将来可以添加一些新的类别和/或事件类型。聚合描述了用户可能生成的事件:拨打电话、充值、数据使用等。每个事件都有描述、日期、用户、事件类型和类别。
  • 如何管理这个关联?是硬编码的,即一个数组?
  • @ConstantinGalbenu 这是问题的一部分:-) 我不知道如何以及在哪里描述两个 VO 之间的关系。在聚合内是一个好地方吗?这样我应该在我的存储库的合同中放置 Aggregate 以获得两个 VO 之间的映射。
  • 这不是我问的。关联可以由管理员修改还是可以硬编码?

标签: php domain-driven-design


【解决方案1】:

这取决于您的特定域,但这个不变量很可能不是 EventAggregate 的责任。

事实上,不变量应该在较低级别强制执行:在 Value 对象级别,由域服务执行;我们称之为EventTypeValidator

此域服务可以具有以下形式:

interface EventTypeValidator
{
    public function isEventTypeAllowedInCategory(string $eventCategory, string $eventType): bool;
}

根据管理此关联的方式,可能会有更多实现。如果关联是硬编码的,那么实现可能是这样的:

//somewhere in the Infrastructure layer
class EventTypeValidatorByMap implements EventTypeValidator
{
    public function isEventTypeAllowedInCategory(string $eventCategory, string $eventType): bool
    {
        return $this->isEventInCategory('AnyCategory', $eventType) || $this->isEventInCategory($eventCategory, $eventType);
    }

    private function isEventInCategory(string $eventCategory, string $eventType): bool
    {
        $category = $this->getMap()[$eventCategory];

        return (in_array($eventType, $category));
    }

    private function getMap()
    {
        return [
            'WeatherCategory'     => [
                'RainEvent',
                'SunEvent',
            ],
            'CompetitionCategory' => [
                'FootbalMatch',
                'TennishMatch',
            ],
            'AnyCategory'         => [
                'RainEvent',
                'SunEvent',
                'FootbalMatch',
                'TennishMatch',
            ],
        ];
    }
}

另一方面,如果关联是在数据库中管理的,例如在另一个有界上下文中,域服务可能如下所示:

//somewhere in the Infrastructure layer
class EventTypeValidatorByDatabase implements EventTypeValidator
{
    //...
    // the database PDO get's injected in the constructor

    public function isEventTypeAllowedInCategory(string $eventCategory, string $eventType): bool
    {
       //create a query that returns true if the $eventType is allowed to be in $eventCategory and false otherwise
    }
}

关于值对象至少有两种设计:两个独立的值对象和一个值对象。

我希望类型和类别有两个值对象:

在应用层中,在调用 EventAggregate 之前,会调用 Domain 服务来验证来自 UI 的关联:

class SomeApplicationService
{
    /** @var  EventTypeValidator */
    private $eventTypeValidator;

    public function __construct(EventTypeValidator $eventTypeValidator)
    {
        //the concrete class is resolved by the Dependency injection container
        $this->eventTypeValidator = $eventTypeValidator;
    }

    public function createAnEvent(string $eventId, string $eventCategory, string $eventType)
    {
        if(!$this->eventTypeValidator->isEventTypeAllowedInCategory($eventCategory, $eventType)){
            throw new \Exception(sprintf("Event type %s may not be in the category %s", $eventType, $eventCategory));
        }

        $event = new Event; //the Aggregate

        $event->create($eventId, $eventCategory, $eventType);

        $this->repository->persistEvent($event);
    }
}

如果将 Event 类型和类别实现为单个 Value 对象,即

class CategorisedEventType
{
    /** @var string */
    private $eventCategory;

    /** @var string */
    private $eventType;

    public function __construct(string $eventCategory, string $eventType)
    {
        $this->eventCategory = $eventCategory;
        $this->eventType = $eventType;
    }

    public function getEventCategory(): string
    {
        return $this->eventCategory;
    }

    public function getEventType(): string
    {
        return $this->eventType;
    }
}

然后可以通过注入EventTypeValidator 域服务在Factory 中提取此验证,如下所示:

//defined in the Domain layer
class CategorisedEventTypeFactory
{
    /** @var  EventTypeValidator */
    private $eventTypeValidator;

    public function __construct(EventTypeValidator $eventTypeValidator)
    {
        $this->eventTypeValidator = $eventTypeValidator;
    }

    public function factory(string $eventCategory, string $eventType): CategorisedEventType
    {
        if(!$this->eventTypeValidator->isEventTypeAllowedInCategory($eventCategory, $eventType))
        {
            throw new \Exception(sprintf("Event type %s may not be in the category %s", $eventType, $eventCategory));
        }

        return new CategorisedEventType($eventCategory, $eventType);

    }
}

然后,应用程序服务可能如下所示:

class SomeApplicationService
{
    /** @var  CategorisedEventTypeFactory */
    private $eventTypeFactory;

    public function __construct(CategorisedEventTypeFactory $eventTypeFactory)
    {
        $this->eventTypeFactory = $eventTypeFactory;
    }

    public function createAnEvent(string $eventId, string $eventCategory, string $eventType)
    {
        $eventTypeAndCategory = $this->eventTypeFactory->factory($eventCategory, $eventType);

        $event = new Event; //the Aggregate

        $event->create($eventId, $eventTypeAndCategory);

        $this->repository->persistEvent($event);
    }
}

我更喜欢具有单个 Value 对象和 Factory 的设计,因为它将域逻辑移动到域层。

如果摆脱EventTypeValidator 接口并仅使用具体类,如果其他实现不适用(即它始终是硬编码的关联),则可以简化事情。

【讨论】:

  • 不错的答案!但是想象一下这种情况:我必须公开一个用例来查找按类别过滤的事件。不幸的是,持久性只保留eventType,因此我需要某处有关类别和事件的关系信息。您提到的CategorisedEventTypeFactory 解决方案可能会暴露此类信息,但我想知道这是最佳做法还是我这样做是肮脏的方式
  • @Bertuz 你说你have an Entity named Event using two ValueObjects, EventType and EventCategory。这意味着存储库中的每个事件都分配了一个类别。过滤应该很简单。您是说实际上您的事件没有直接关联到它们的类别?
  • 按领域来看:是的。从持久性的角度来看:没有。数据库中有没有存储类别的事件。这就是我在从存储库中检索数据时需要映射信息的原因。实际上存储库是我无法触及的 RESTful 服务
  • @Bertuz DDD 更专注于验证部分(不允许事件进入无效状态),而不是查询;这也是我的回答的重点。不过,我也可以指导你解决这个问题。
  • @Bertuz 所以,最适合您的验证和查询的解决方案是拥有一个包含事件类型和事件类别的单个值对象,就像我的答案中的那个: CategorisedEventType
猜你喜欢
  • 2013-04-01
  • 2022-08-08
  • 1970-01-01
  • 1970-01-01
  • 2013-10-19
  • 2011-09-10
  • 1970-01-01
  • 1970-01-01
  • 2021-09-20
相关资源
最近更新 更多