代码之家  ›  专栏  ›  技术社区  ›  Stark

创建聚合根及其子对象(实体)的正确流程是什么?

  •  0
  • Stark  · 技术社区  · 5 年前

    我正在将我的数据驱动设计重构为领域驱动,我有几个关于代码应该如何构造的问题

    我有一个 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对象不能存在,因此Settings entity应由用户实体管理。也就是说,用户实体不仅仅是一个实体,它应该是一个聚合根。

    目前,当用户单击“创建帐户”时,应用程序会向 example.com/user/save ,控制器操作将请求对象发送到 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

    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);
        }
    }
    
    0 回复  |  直到 5 年前
        1
  •  1
  •   plalx    5 年前

    所有顶级实体都是聚合根。在你目前的设计中 UserEntity 以及 SettingsEntity

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

    这里有两个很明显的潜在设计,正确的设计取决于实际的业务不变量和您想要做出的妥协。

    1. User Settings 具有交叉不变量,这意味着 可能取决于 设置 用户 设置 必须是相同一致性边界的一部分。你很可能 用户 作为AR和 设置 用户 .

    2. 用户 设置 可以独立进化(除了他们的创造)。如果那样的话,你很可能会 设置 作为自己独立的AR,但在同一事务中创建两者(或不创建最终一致性)。注意,在AR上有一个工厂方法来创建另一个工厂方法通常是优雅的。

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

      从那以后, 用户 设置 将在不同的事务中修改。

    注:我建议去掉“实体”等技术前缀。语言是DDD的关键,我怀疑领域专家在他们的语言中使用“用户实体”(甚至可能不使用“用户”)或“设置感知”一词。

        2
  •  1
  •   Louis    5 年前

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

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

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

    一旦拥有了用户对象,就将其发送到存储库进行持久化。