Как стать автором
Обновить
8
0
Максим @myks92

PHP Developer

Отправить сообщение

Явное изменение версии хороший вариант - просто и понятно.

С одной стороны да, а с другой - появляется лишний мусор, который никаким образом не относится в бизнес модели. Текущая вещь больше инфраструктурная, поэтому домен не должен знать ни о каких версиях агрегата. Да и в каждом тесте модели придётся делать проверку изменения агрегата.

Чтобы люди не забывали инкрементить версию можно написать юнит тест который найдет все энтити у которых есть методы начинающиеся с add и проверить что они инкрементят версию либо вызывав их , либо тупо посчитав что количество методов равно количеству инкрементов или количество инкрементов равно количеству @OneToMany аннотаций x 3.

В этих случаях мы явно заставляем все использования доктрины проставлять версию агрегата. А что делать с простыми доменами, которое реализуется не по DDD, а простым CRUD. Там вообще нет такого понятия, как агрегат… Unit тест не будет проходить.

Зачем?) Это очень странно. В Yii2 есть ArrayDataProvider. Вам достаточно было отделить слой работы с моделью изменения от слоя чтения - аналог CQRS. Тогда бы у вас было меньше связанности. А так у вас может утекать какая-то логика в ваши DtoBuilder. Например, в User у нас есть статусы (активный, заблокирован). А в UI мам нужно только isActive чтобы показать что-то или скрыть. Тогда получается что вы эту проверку добавите в DtoBuilder. И как это тестировать?

Пойдём дальше, а что если к User нужно ещё добавить например список его комментариев из другого модуля? У вас уже будут совмещённые DtoBuilder или два билдера как-то объединять данные. А с разными репозиториями на чтение мы бы вызвали два репозитория, которые отдают нам две DTO: User и Comment и уже их бы передали в view.

Выше пример тоже не работает. Order агрегат и вряд ли когда-то будет новым, при вызове его из em:

$order = $this->em->getRepository(self::ORDER)->find($id)

крч следите за тем, что сущность должна меняться

Вот как раз над этим и озаботились и эта проблема вышла в данную статью. Ищем лаконичный способ следить за изменениями. Можно пойти простым путём вроде:

public function addLine(Line $line): void
{
    $this->items->add(new OrderLine($this, $line));
    $this->aggregateVersion++; //Обновляем версию агрегата
}

public function editLine(string $id, Line $line): void
{
    foreach ($this->items as $item) {
      if ($item->getId() === $id) {
        $item->edit($line);
        $this->aggregateVersion++; //Обновляем версию агрегата
        return;
      }
    }
  	throw new DomainException('Order line not found.');
}

Но пока от этого способа отошли, так как это требует постоянно изменять версию в каждом методе. При наличии большого количества агрегатов в действующем проекте - это достаточно долго и много изменений. Хотя тоже не плохой вариант.

Ещё был вариант изменениие версии при публикации события:

public function releaseEvents(): array
{
    $events = $this->recordedEvents;
    $this->recordedEvents = [];
    $this->updateAggregateVersion(); // Обновляем версию
    return $events;
}

Этот способ дополнительно требует наличие DomainEvent, даже если он не нужен или не написан в действующем проекте.

К сожалению, такой способ не работает. Пример метода addLine:

public function addLine(Line $line): void
{
    $items = new ArrayCollection();
    $items->add(new OrderLine($this, $line));
    $this->items = $items;
}

При таком использовании поле version остаётся прежней - не меняется. Вроде бы пример правильный.

На счёт коллекций думал иначе. Сделать свою коллекцию, которая будет вызывать корень агрегата вроде такого:

public function __construct(string $id)
{
	$this->items = new MyArrayCollection($this); //Передаём агрегат ($s), чтобы вызвать у него внутри $aggregate->updateAggregateVersion()
}

По-моему OrderLine вообще не должен знать какому order'у он принадлежит (вложенные сущности не должны знать о том, куда их засунули — это не их дело). Завтра OrderLine окажется в другом агрегате, что будете делать?

Соглашусь с Вами что, возможно, пример не самый удачный, но всем понятный. Здесь рассматривал проблему не в том, что OrderLine может жить в другом агрегате или OrderLine сделать агрегатом. Проектирование ответственности агрегата всегда рассматривается индивидуально. Проблема здесь заключается в изменении любых коллекций-сущностей в агрегате, при которых не будет работать оптимистическая блокировка. Ведь такая проблема есть. Проблема, как мне кажется, не архитектуры. Любая вложенная коллекция в виде сущности даст нам эту проблему. Это может быть UserRoles, UserNetworks и так далее.

Я бы думал в сторону вычисления хэша агрегата по его содержимому (у вас наоборот: вы через аннотации учите вложенную сущность определять какому агрегату она принадлежит). Добавляете в супертип AggregateRoot метод getHash(). Доктрина, когда решает увеличивать версию или нет, должна сверять этот хэш: если изменился — увеличивает версию.

Хорошая идея. О таком не подумали. Пока что пришли к такому решению. Посмотрим в сторону предложенного вами решения. Возможно, из этого что-то получится. Здесь только есть одна проблема, когда вложенная сущность изменяется агрегат не попадает в Unit Of Work. Поэтому пришлось придумать обратную ссылку на агрегат, чтобы знать к какому агрегату принадлежит изменённая сущность. Но есть идея как получить rootEntity по другому, а именно вот так:

$meta = $em->getClassMetadata(get_class($aggregateRoot));
$meta->rootEntityName; // App/Entity/Order

Но пока что это ничего не даёт) Надо её ID этого агрегата.

Решение 2: вручную увеличивать версию при изменении агрегата (так же, как это делается в Unit Of Work)

Выбрасывайте события при изменениях, а когда ловите — обновляйте версию соотв.агрегата

Да. О таком решении знали. Иногда даже изменение версии делаются при публикации событий. Такое тоже практикуется некоторыми командами. Однако шли от того, что есть множество агрегатов, которые имеют данную проблему и хотели её решить наименьшим изменением в коде. С данным примером нам удалось достичь этого.

В любом случае спасибо вам за ёмкий комментарий. Здесь есть над чем подумать и придти к каком-то более лаконичному решению, особенно с хэшем агрегата. Если есть что-то добавить — напишите.

Благодарю) Не заметил, исправил)

Информация

В рейтинге
Не участвует
Откуда
Россия
Зарегистрирован
Активность

Специализация

Backend Developer
Middle
PHP
Docker
Symfony
PhpUnit
Elasticsearch
PostgreSQL
OOP
MySQL
Yii framework
Git