All streams
Search
Write a publication
Pull to refresh
8
0
Максим @myks92

PHP Developer

Send message

Спасибо) Но я все это уже прочитал, попробовал и ничего не получилось с Traefik. С Caddy все работает.

Вы случайно не знаете как использовать FrankenPHP + Traefik на Symfony. Там мешает Caddy. Может быть у вас есть решение? Видимо нужно как-то избавиться от Caddy.

Не "ответила", а "ответил")
Первый был не вопрос.

При разделении на микросервисы нужно делать не Database Join, а Application Join. И я не пытался в комментариях получить ответ. Я лишь с вами поделился первоочередными проблемами, которые уже говорят, что так делать нельзя. Разве что в очень простых сервисах. 

Спасибо за статью, но в этой теме стоило бы побольше разобраться. Могу продолжить и ещё дополнить проблемами

1. На пустом месте создали новую инфраструктуру, которую нужно поддерживать, вместо того, чтобы взять готовый инструмент. Это я про маппинг на DTO) Тут либо использовать понятное проверенное решение, либо вообще отказаться от этой затеи.
2. В базе бывают не только скалярные типы, но и JSON, Datetime, Money, которые надо приводить в каким то данным. Json -> Array. DateTime -> DATE_ATOM и тд.

Извиняюсь, ли что-то не так написал. Хочу чтобы побольше разобрались в вопросе и сделали вторую часть)

  1. Использовать Doctrine на чтение очень плохая затея. Лучше уж использовать простой DBAL. Или ElaticSearch.

  2. Почему нельзя было использовать Sumfony Normalizer? Это же проще.

  3. Когда есть пользователь и заказ в больших системах, то такие запросы не получится сделать. Скорее это будут разные контексты и базы данных.

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

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

Чтобы люди не забывали инкрементить версию можно написать юнит тест который найдет все энтити у которых есть методы начинающиеся с 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)

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

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

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

Information

Rating
6,207-th
Location
Россия
Registered
Activity

Specialization

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