Как реализуете репозиторий (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 в качестве идентификатора?
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
Второй метод, конечно, красивый, но нам нужно имя в порт всё равно прописать, т.е. будет какой-то метод 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 человека, один пишет саму бизнес-логику, а другой валидацию. Более того валидацию можно написать и позже, да код будет вылетать на исключениях, ну и ладно, для прототипов и начальных версий это вполне допустимо.
 
Последнее редактирование:
Сверху