В прошлый раз я озадачился двумя вопросами о Clean Architecture: как работать со структурами данных типа дерева и какими все же должны быть Сущности. Подумав и почитав ветки, пришел к следующему.
Структура данных Сущности должна отображаться либо в ней самой, либо в специально созданной другой сущности. Но при этом служебным данным в них не место. Например, если нужно иметь дерево задач, я могу эмулировать его свойством children, куда помещать задачи-непосредственные потомки. Или же создать отдельную сущность TaskTree, которая будет содержать иерархию Task. Думаю, на теперешнем этапе более приемлем первый вариант.
public $children = [];
Разумеется, должны добавиться методы, обрабатывающие эту иерархию, в нашем случае addChild()
, removeChild()
и другие. Чтобы не грузить каждый раз всю иерархию, можно добавить поле $hasChildren
, которое будет показывать, есть ли у задачи потомки.
Далее. Как я понял из комментариев, Сущность в первую очередь должна определять бизнес-правила обработки себя. Заполнение, выборка, валидация — все, что работает на высшем, самом абстрактном уровне иерархии приложения. При этом непосредственного доступа к данным она может и не иметь. То есть, Сущность может и должна быть абстрактным классом, от которой наследуются конкретные реализации с ORM, например. Такое усложнение и вызвано, по-видимому, наличием слоя ORM, который сам работает с БД, но при этом должен реализовать требуемые бизнес-методы сущности. Это говорит о том, что у меня все неправильно. Впрочем, ломать сейчас не буду. Есть частные мнения, что сущности все же могут быть конкретными классами с нормальными полями данных, формироваться из данных БД они могут в Репозиториях. Кроме того, хочу, чтобы изменения в коде были естественными, а пока не вижу необходимости менять реализацию класса.
На сегодняшний день реализован сценарий создания задачи.
$taskCreator = new Interactor\Task\Creation($this->taskRepo, $this->userRepo, $this->sessionService);
$this->boolResult = $taskCreator->execute($data);
При создании в него инжектируются репозитории задачи и пользователя, а также сервис для работы с сессией. Для выполнения он получает на вход данные новой задачи, которые дополняет ID создателя и вызывает метод создания задачи в репозитории.
$data['userId'] = $this->session->getLoggedInUserId();
$this->taskRepo->create($data);
В чем достоинство такого кода? Создание задачи отделено от обслуживающего вызов кода (в данном случае, теста). Бизнес-логика реализуется внутри Интерактора, только он знает, какие поля выбрать, куда записать и как сохранить. Но при этом абстрагируется от конкретного способа сохранения — сейчас за это отвечает mock-репозиторий , работающий с оперативной памятью.
Недостатков у кода много, о них пока не буду. Интересно посмотреть, как такой код может взаимодействовать с запросами извне.