Как реализуете репозиторий (Doctrine, Analogue, ручками, etc)

Конечная роль - организация схемы соединения устройств по различным шинам (Ethernet, USB, COM и т.д., необязательно компьютерным).
Ну, как обычно. Вся логика вертится вокруг операций типа «сменить имя», а конечная цель — показать полученную иерархию. :/

Например, сделать $port->changeName(); $port_repo->flush(); изменения ушли в БД уже не получится, валидатор будет ругаться.
Во-первых, port не особо походит на сущность.
Во-вторых, даже если это и сущность, то судя по описанию, она должна быть дочерней сущностью aggregate'а «устройства», а значит у неё не должно быть собственного репозитория.
В-третьих, тут смешивается терминология Doctrine и DDD: «репозитории» Doctrine — это другие репозитории, они более низкоуровневые, лежащие в отдельной папочке «Инфраструктура». Там действительно у Port может быть свой Doctrine repository, но использовать его напрямую — это всё равно что напрямую запросы в базу фигачить.

Устройство не м.б. агрегатом, т.к. ссылки в других частях системы идут на сам порт.
Что за ссылки? Почему «другим частям системы» недостаточно port id или device id + port name в качестве идентификатора?
 
Последнее редактирование:
Второй метод, конечно, красивый, но нам нужно имя в порт всё равно прописать, т.е. будет какой-то метод changeName(), иначе Doctrine2 некорректно сохранит сущность в БД.
PHP:
function changeName($old, $new){
  if(in_array($old, array_keys($this->ports))){
    if(!in_array($new, array_keys($this->ports))){
      $this->ports[$new] = $this->ports[$old];
      unset($this->ports[$old]);
    }
    // else throw
  }
  // else throw
}

имя порта это свойство не устройства и не порта в базе, а связки порт-устройство
id, device_id, port_id, name уникально на (device_id, port_id, name)
 
Ну, как обычно. Вся логика вертится вокруг операций типа «сменить имя», а конечная цель — показать полученную иерархию. :/

Во-первых, port не особо походит на сущность.
Во-вторых, даже если это и сущность, то судя по описанию, она должна быть дочерней сущностью aggregate'а «устройства», а значит у неё не должно быть собственного репозитория.
В-третьих, тут смешивается терминология Doctrine и DDD: «репозитории» Doctrine — это другие репозитории, они более низкоуровневые, лежащие в отдельной папочке «Инфраструктура». Там действительно у Port может быть свой Doctrine repository, но использовать его напрямую — это всё равно что напрямую запросы в базу фигачить.

Что за ссылки? Почему «другим частям системы» недостаточно port id или device id + port name в качестве идентификатора?

1) На самом деле всё сложнее, у портов есть наполнения, которые в свои очередь состоят из контейнеров разных типов, которые представляют собой тоже сущности. Поэтому помимо физической связи ещё строятся логические/виртуальные, но это уже детали.

2,3) Не использую репозитории Doctrine2, но DDD-репозиторий использует её.

Другие части системы, уточнение (не думал что так глубоко зайдём) по устройствам. Устройства на самом деле строятся на шаблонах устройств, порты именно у шаблонов, это часть системы называется "разработка". Также есть "проектная" часть, в которой формируются уже реальные устройства и между ними строятся "сети" и т.д. Порты устройств подключаются к шинам, соединяющим устройства. У шины должна быть ссылка на устройство и порт, шина сама также является сущностью, пример опреации:
Код:
$bus->addConnection($unit, $port);
$bus->removeConnection($unit, $port);

Глубоко в проект, наверное, нет смысла закапываться. Изначальным вопросом было как правильно обработать ошибки, использовать ли исключения или накапливать массив и где какую валидацию лучше применять.

На данный момент впиливаю валидацию для команды, по факту получается дублирование с теми правилами, что сейчас есть в сущности и доменных сервисах.
 
Что за ссылки? Почему «другим частям системы» недостаточно port id или device id + port name в качестве идентификатора?

Другим частям системы, например, проектной, ссылки на port_id достаточно, но в разделе "разработка" - нет.
 
Всё же тут как будто слишком много сущностей. Неужели, например, имя действительно должно принадлежать порту, а не задаваться в самом устройстве? Я не могу спокойно поменять в двух устройствах USB-порты (это может привести к тому, что в одном из устройств будет 2 порта с одинаковым именем)? Это как-то не соответствует реальности. Неужели для разработки устройства нам действительно важно различать конкретные порты (абсолютно одинаковые по всем свойствам, но с разными id), разве нам не достаточно знать их количество? То же самое с проектированием сети: разве мне есть какое-то дело в какой конкретно USB-порт я воткнул клавиатуру, если порты одинаковые? Мне, по-моему, достаточно только понимать количество свободных портов нужного мне типа.

Складывается впечатление, что у тебя реально есть несколько контекстов разного масштаба, т.е. проектировщики зданий же не учитывают каждый кирпичик, они не задают каждому кирпичу и каждой двери («двери» в смысле «створ» конкретной фирмы с конкретным замком, а не «проём») в здании id-шник, на масштабе всего здания это бессмысленная информация.

А ответ на исходный вопрос по накапливание ошибок у меня по-прежнему такой: мне кажется, это лишнее. Решается это отбрасыванием ненужных данных для каждого отдельно взятого контекста.
 
Всё же тут как будто слишком много сущностей. Неужели, например, имя действительно должно принадлежать порту, а не задаваться в самом устройстве? Я не могу спокойно поменять в двух устройствах USB-порты (это может привести к тому, что в одном из устройств будет 2 порта с одинаковым именем)? Это как-то не соответствует реальности. Неужели для разработки устройства нам действительно важно различать конкретные порты (абсолютно одинаковые по всем свойствам, но с разными id), разве нам не достаточно знать их количество? То же самое с проектированием сети: разве мне есть какое-то дело в какой конкретно USB-порт я воткнул клавиатуру, если порты одинаковые? Мне, по-моему, достаточно только понимать количество свободных портов нужного мне типа.

Складывается впечатление, что у тебя реально есть несколько контекстов разного масштаба, т.е. проектировщики зданий же не учитывают каждый кирпичик, они не задают каждому кирпичу и каждой двери («двери» в смысле «створ» конкретной фирмы с конкретным замком, а не «проём») в здании id-шник, на масштабе всего здания это бессмысленная информация.

А ответ на исходный вопрос по накапливание ошибок у меня по-прежнему такой: мне кажется, это лишнее. Решается это отбрасыванием ненужных данных для каждого отдельно взятого контекста.

Я понимаю, что тут имеется ввиду, но в моём случае одинаковые порты имеют совершенно разное наполнение (имеется ввиду то какие данные по ним будут бегать) и место подключения играет важную роль. Сеть не является компьютерной, а приведенные ранее типы шин указаны для понимая сути.

На данный момент я понимаю, что валидаторы надо вытаскивать из сущности и проверять снаружи, т.к. внутри можно проверить только саму сущность, но не её внешнюю консистентность. Кидать исключения не вариант, т.к. мне нужно собрать все ошибки и вернуть их пользователю в xls-файле. Всеми валидаторами буду проверять входные данные, а потом только изменять сущности уже без доп проверок.
 
Надеюсь пригодится или для истории.

Вопрос с валидацией решил следующим образом. В нашем проекте фронтенд и бекенд взаимодействуют через команды (ф -> б). В любом проекте передача данных это некая структура и адреса приёма. В контроллере происходит сборка данных и на её основе формируется команда. Команда - это read-only объект после своего создания. Команда на данный момент отправляется сразу в сервис, но! по хорошему её нужно отправлять в сервис через шину, где на уровне шины делать авторизацию/аутентификацию, валидацию и любой другой аспект.

В сервисе первым делом валидируется команда. Проверяются те ли типы данных соответствуют полям, правильны ли поля с точки зрения бизнес-логики, обязательность заполнения и проч. Валидатор возвращает Result, который содержит массив найденных ошибок. Result также immutable. На этом этапе никаких эксепшенов нет и быть не должно, в этом суть валидации понять что не так и выдать наиболее полный ответ. Если result->isValid() == false, то тогда выбрасываем исключение, в которое передаём список ошибок result->getErrors(), но тут могут быть вариации, зависит от требований. Если валидация прошла успешно выполняем код сервиса (бизнес-логику запрошенного действия).

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

На фронте обработку может проводить так. Если форма, то привязать ошибки к соответствующим полям. Если это некое действие (например, перерасчёт данных, импорт/экспорт), то сделать popup с выводом списка всех ошибок.

Итог. Валидация - это многогранный и многоуровневый процесс. Проверить все правила изнутри сущности не возможно. Может быть валидация данных на уровне сущности, на уровне коллекции, на уровне коллекции коллекций, всего проекта целиком и даже с внешними данными и проч. Отталкиваясь от этого одним из верных решений является внешний сервис (класс) валидации, который принимает все зависимости, и выполняет своё назначение. Одним из возможных минусов является дублирование кода, но этого можно обернуть в плюс, мы получаем те участки кода, которые стоит декомпозировать в отдельные функции или классы. Важный плюс отдельного класса валидации: в один момент над сервисом могут работать сразу 2 человека, один пишет саму бизнес-логику, а другой валидацию. Более того валидацию можно написать и позже, да код будет вылетать на исключениях, ну и ладно, для прототипов и начальных версий это вполне допустимо.
 
Последнее редактирование:
Сверху