Каков правильный процесс создания агрегата root и его дочерних объектов (сущностей)? - PullRequest
0 голосов
/ 14 марта 2020

Я реорганизую свой дизайн, ориентированный на данные, в сторону домена, и у меня есть несколько вопросов о том, как должен быть структурирован код

У меня есть 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;
        ...
    }
}

I также есть 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 не может существовать без пользователя, поэтому настройкой settingsEntity должен управлять объект User. Это означает, что сущность User не является просто сущностью, это должна быть совокупность root.

В текущий момент, когда пользователь нажимает кнопку «создать учетную запись», приложение отправляет запрос API на * 1013. *, действие контроллера отправляет объект запроса в 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();
        }
    }
}

Проблема в том, что я не знаю, следует ли создавать объект User и объект настройки в одном go из (теоретически, поскольку у меня нет User Aggragate Root, только пользовательский объект) Aggregate User root или, если текущий способ действителен.

У меня есть другие объекты, которые должны быть создается одновременно ... так же, как объект настроек, я просто добавляю их к проверке if в методе сохранения UserService (приведенный выше код) или все это должно быть под пользователем Aggregate, например:

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

Ответы [ 2 ]

1 голос
/ 15 марта 2020

Это зависит от того, новый ли это пользователь или нет.

Если это уже существующий пользователь, один из способов подойти к нему - использовать репозиторий для «гидратации» пользователя и настроек из хранилища. Затем измените.

Если это новый пользователь, вы можете использовать фабрику для создания агрегата root (User Entity) и использовать фабричный метод, который соответствует UL для генерации настроек из входных данных.

Как только у вас есть пользовательский объект, отправьте его в свой репозиторий для сохранения.

1 голос
/ 15 марта 2020

Все сущности верхнего уровня являются совокупными корнями. В вашем текущем дизайне UserEntity и SettingsEntity являются эффективно агрегированными корнями (AR). AR представляют собой границы транзакций и согласованности. Роль AR состоит в том, чтобы гарантировать, что инварианты, связанные с инкапсулированными данными, никогда не нарушаются, даже через параллелизм.

AR должны быть спроектированы как можно меньшего размера, поскольку они предотвращают одновременное изменение данных, которые они защищают. Чтобы извлечь выгоду из шаблона AR, вы должны стремиться уважать AR как транзакционную границу и, следовательно, пытаться модифицировать только один AR на транзакцию для большинства случаев использования (могут быть исключения). Это правило не применяется при создании AR, потому что конфликты параллелизма не должны быть обычными при создании.

Здесь есть 2 потенциальных дизайна, которые очевидны, и правильный зависит от реальных бизнес-инвариантов и компромиссов, которые вы хочу сделать.

  1. User и Settings имеют перекрестные инварианты, то есть инварианты User могут зависеть от состояния Settings и наоборот. В этом случае User и Settings должны быть частью одной границы согласованности. Скорее всего, у вас будет User как AR и Settings как существо, живущее в пределах User.

  2. User и Settings могут развиваться независимо (за исключением их создание). В этом случае вы, скорее всего, захотите сохранить User и Settings в качестве их собственного независимого AR, но создать оба в одной транзакции (или нет - возможная согласованность). Обратите внимание, что зачастую элегантно иметь фабричный метод в AR для создания другого.

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

    С этого момента User и Settings будут изменены в отдельных транзакциях.

PS: Я бы рекомендовал удалить технические префиксы, такие как «Entity». Язык является ключевым для DDD, и я сомневаюсь, что эксперты в области используют слово «UserEntity» (возможно, даже не «User») или «SettingsEntity» в своем языке.

...