【问题标题】:What's the proper flow for creating the aggregate root and it's child objects (entities)?创建聚合根及其子对象(实体)的正确流程是什么?
【发布时间】:2020-06-26 10:21:09
【问题描述】:

我正在将我的数据驱动设计重构为领域驱动,我对代码的结构有几个问题

我有一个User entity

namespace Planner\Domain\Entities;

class UserEntity {
    private $userId;

    private $weight;
    private $height;

    public function __construct(int $id, float $weight, float $height){
        $this->userId = $id;
        ...
    }
}

我还有一个Settings entity,它应该与用户对象的创建一起创建。

namespace Planner\Domain\Entities;

class SettingsEntity {
    private $id;

    private $userId;

    private $darkTheme;

    public function __construct(int $id, int $userId, bool $darkTheme){
        $this->id = $id;
        $this->userId = $userId;
        $this->darkTheme = $darkTheme;
    }
}

Settings 对象不能没有 User 存在,因此 settingsEntity 应该由 User 实体管理。意思是,用户实体不仅仅是一个实体,它应该是一个聚合根。

当前,当用户点击“创建账户”时,App 向example.com/user/save 发起API 请求,控制器动作将请求对象发送到Planner\Application\Services\UserService 保存方法。

namespace Planner\Application\Services;

use Planner\Domain\Entities\UserEntity;
use Planner\Domain\Entities\SettingsEntity;
use Planner\Domain\Repositories\UserRepository;
use Planner\Application\Services\SettingsService;

class UserService {

    public function __construct(UserRepository $repo, SettingsService $settings){
        $this->userRepository = $repo;
        $this->settingsService = $settings;

    }

    public function save($request){
        $user = new UserEntity(...);

        $settings = new SettingsEntity(...);

        if(!$this->userRepository->save($user) || !$this->settingsRepository->save($settings)){
            throw new UserCreationFailedException();
        }
    }
}

问题是,我不知道是否应该一次性创建用户实体和设置实体(从理论上讲,因为我没有用户聚合根,只有一个用户实体)用户聚合根或如果当前方式有效。

我还有其他需要同时创建的实体...与设置对象相同,我只是将它们添加到 if 签入 UserService 保存方法(上面的代码)还是应该全部在用户聚合下,例如:

namespace Planner\Domain;

class User extends AggragateRoot {

    public function __construct(UserRepository $repository){
        $this->repository = $repository;
    }

    public function createAccount($request){
        $user = new UserEntity(...$request);
        $settings = new SettingsEntity(...$user);

        $this->repository->save($user, $settings);
    }
}

【问题讨论】:

    标签: php domain-driven-design


    【解决方案1】:

    所有顶级实体都是聚合根。在您当前的设计中,UserEntitySettingsEntity 是有效的聚合根 (AR)。 AR 是事务性和一致性边界。 AR 的作用是确保与它们封装的数据相关的不变量永远不会被破坏,即使通过并发也是如此。

    AR 应设计得尽可能小,因为它们可以防止对它们保护的数据进行并发修改。为了从 AR 模式中受益,您必须努力将 AR 视为事务边界,因此对于大多数用例(可能存在例外),尝试只修改每个事务的单个 AR。但是,该规则在创建 AR 时并不适用,因为在创建时并发冲突不应该是常见的。

    这里有 2 种可能的设计显而易见,而正确的设计取决于实际的业务不变量和您想要做出的妥协。

    1. UserSettings 具有交叉不变量,这意味着User 的不变量可能取决于Settings 的状态,反之亦然。在这种情况下,UserSettings 必须属于同一一致性边界。您很可能将 User 作为 AR 并将 Settings 作为生活在 User 中的实体。

    2. UserSettings 可以独立进化(除了它们的创建)。在这种情况下,您很可能希望将 UserSettings 保留为它们自己的独立 AR,但在同一事务中创建两者(或不创建 - 最终一致性)。请注意,在 AR 上使用工厂方法来创建另一个工厂方法通常很优雅。

      transaction {
           user = new User(…)
           settings = user.initSettings(...)
           userRepository.save(user);
           settingsRepository.save(settings);
      }
      

      从那时起,UserSettings 将在不同的事务中进行修改。

    PS:我建议删除诸如“实体”之类的技术前缀。语言是 DDD 的关键,我怀疑领域专家在他们的语言中使用“UserEntity”(甚至可能不是“User”)或“SettingsEntity”这个词。

    【讨论】:

      【解决方案2】:

      这取决于它是否是新用户。

      如果它是现有用户,一种处理方法是使用存储库“水合”存储中的用户和设置。然后修改。

      如果是新用户,您可以使用工厂实例化聚合根(用户实体)并使用符合 UL 的工厂方法从输入生成设置。

      获得用户对象后,将其发送到存储库以进行持久化。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-08
        相关资源
        最近更新 更多