Структура данных из репозитория для всех целей?

Мне нужна помощь с чем-то, что я не могу понять, вместилище а также Услуги / Использование регистра шаблон (часть DDD дизайн) Я хочу реализовать в моем следующем (Laravel PHP) проекте.

Кажется, все ясно. Только одна часть DDD, которая сбивает с толку, — это структуры данных из репозиториев. Люди, кажется, выбирают структуры данных, которые должен возвращать репозиторий (массивы или сущности), но все это имеет недостатки. Одним из них является представление о моем опыте в прошлом. И один из них — это то, что у вас нет интерфейсов для простых структур данных (массив или атрибуты простого объекта).

Начну с объяснения опыта предыдущего проекта. У этого проекта были недостатки, но есть некоторые сильные стороны, из которых я научился и хотел бы видеть в своем новом проекте, но с решением некоторых ошибок проектирования.

Предыдущий опыт

В прошлом я создавал веб-сайт, который был ориентирован на API с использованием инфраструктуры Kohana и Doctrine 2 ORM (шаблон отображения данных). Поток выглядел так:

Контроллер веб-сайта → API-клиент (вызовы HMVC) → API-контроллер → Пользовательский репозиторий → Doctrine 2 ORM Собственный репозиторий / Entity-manager

Мой пользовательский репозиторий возвратил простые массивы с помощью Doctrine2 DQL. Doctrine2 рекомендует данные результата массива для операций только для чтения. И да, это сделало мой сайт красивым и легким. Контроллер API только что преобразовал данные массива в JSON. Просто как тот.

В прошлом моя компания создавала проекты, полностью полагаясь на загруженные объекты Doctrine2, и об этом мы сожалели из-за производительности.

Мой REST API поддерживает запросы, такие как
/api/users?include_latest_adverts=2&include_location=true
на ресурсе пользователей. API контроллер пройден include_location в хранилище, которое непосредственно включает в себя отношение местоположения. Контроллер прочитал latest_adverts=2 и вызвал хранилище рекламы, чтобы получить последние 2 рекламы каждого пользователя. Массивы были возвращены.

Например, первый пользовательский массив:

[
name
avatar
adverts [
advert 1 [
name
price
]
advert 2 [
….
]
]
]

Это оказалось очень успешным. Весь мой сайт использовал API. Было бы очень легко добавить нового клиента, потому что API отлично работал, уже используя oauth. Весь сайт работает на нем.

Но у этого дизайна тоже были недостатки. Мой контроллер все еще содержал много логики для проверки, рассылки, параметров или фильтров, таких как has_adverts=true получить пользователей только с рекламой. Это означало бы, что, если бы я создал новый порт, такой как полностью новый интерфейс CLI, мне пришлось бы дублировать множество этих контроллеров из-за всей проверки и т. Д. Но не было бы дублирования, если бы я создал нового клиента. Так что хотя бы одна проблема была решена 🙂

Мои админ-панели были полностью связаны с хранилищем doctrine2 / сущностью-менеджером для ускорения разработки (вроде). Зачем? Потому что в моем API были жирные контроллеры со специальными функциями только для веб-сайта (специальная проверка, рассылка для регистрации и т. Д.). Я должен был бы переделать работу или сделать рефакторинг. Поэтому решили использовать сущности напрямую, чтобы все еще иметь какой-то четкий способ написания кода вместо переписывания всех моих контроллеров API и перемещения их в службы (для сайта & админ) например. Время было проблемой в исправлении моих ошибок в дизайне.

Для моего следующего проекта я хочу, чтобы весь код проходил через мои собственные репозитории и сервисы. Один поток для хорошего разделения.

Новый проект (с использованием идей DDD) и дилемма со структурами данных

Хотя мне нравится идея быть ориентированной на API, я не хочу, чтобы мой следующий проект был ориентирован на API в основном, потому что я думаю, что те же функциональные возможности должны быть доступны без промежуточного протокола HTTP. Я хочу спроектировать ядро, используя идеи DDD.

Но мне понравилась идея использовать слой, который просто говорит как API и возвращает простые массивы. Идеальная база для любого нового порта, включая мой собственный интерфейс. Моя идея состоит в том, чтобы рассматривать мои классы служб как интерфейс API (возвращать данные массива), выполнять проверку и т. Д. Я мог бы иметь службы специально для веб-сайта (регистрации) и простые службы, используемые администратором или фоновыми процессами. В некоторых случаях администрирования в любом случае для простого редактирования CRUD Сервис не требовался, я мог просто использовать репозитории напрямую. Контроллеры были бы очень тонкими. При этом создание реального REST API было бы просто вопросом создания новых контроллеров, использующих те же сервисы, что и мои классы контроллеров внешнего интерфейса.

Для внутренней логики, такой как бизнес-правила, было бы полезно иметь Entities (чистые интерфейсы) вместо массивов из репозиториев. Таким образом, я мог бы выиграть от определения некоторых методов, которые делали логику на основе атрибутов. НО, если бы я использовал Doctrine2, а мои репозитории всегда возвращали сущности, мое приложение сильно пострадает от производительности !!

Одна структура данных обеспечивает производительность, но не имеет четких интерфейсов, другая обеспечивает четкие интерфейсы, но плохую производительность при использовании шаблона Data Pattern, такого как Doctrine 2 (сейчас или в будущем). Также я мог бы получить два типа данных, которые могли бы сбить с толку.

Я думал о чем-то похожем на этот поток:

Контроллер (тонкий) → UserService (включая проверку) → UserRepository (просто хранилище) → Eloquent ORM

Почему Eloquent вместо Doctrine2? Потому что я хочу немного придерживаться того, что является общим в рамках Laravel и сообщества. Таким образом, я мог бы извлечь выгоду из сторонних модулей, например, для создания интерфейсов администратора или аналогичных на основе моделей (в обход моих правил DDD). Помимо использования сторонних модулей, я разработал бы свои основные компоненты, чтобы переключение всегда было простым и не влияло на выбор структуры данных или производительность.

Eloquent — это шаблон активной записи. Таким образом, у меня возникнет соблазн преобразовать эти данные в POPO, как в сущностях Doctrine2. Но нет … как уже было сказано выше, с doctrine2 реальные модели сделали бы систему очень толстой. Поэтому я снова возвращаюсь к простым массивам. Знание этого будет работать как для любой другой реализации в будущем.

Но это плохо всегда полагаться на массивы. Особенно при создании внутренних бизнес-правил. Разработчик должен был бы угадать значения для массивов, не иметь автодополнения в своей IDE, не мог бы иметь специальные методы, как в классах Entity. Но делать два способа работы с данными тоже плохо. Или я просто перфекционист;) Я хочу ОДНУ ясную структуру данных для всех!

Создание интерфейсов и POPO означало бы много дублирующихся работ. Мне нужно будет преобразовать модель Eloquent (просто преобразователь таблиц, а не сущность) в объект сущности, реализующий этот интерфейс. Все это дополнительная работа. И, в конце концов, мой последний уровень будет похож на API, поэтому снова преобразую его в массивы. Это тоже дополнительная работа. Массивы кажутся снова заключенными.

Казалось, что так легко читать в DDD и Hexagonal. Вроде так логично! Но на самом деле я борюсь с этой простой проблемой, пытаясь придерживаться принципов ООП. Я хочу использовать массивы, потому что это единственный способ быть на 100% уверенным, что я не зависим от выбора модели и запроса от моего ORM относительно производительности и т. Д., И у меня нет дубликатов при преобразовании в массивы для представлений или API. Но нет четкого договора о том, как может выглядеть пользовательский массив. Я хочу ускорить мой проект, используя эти шаблоны, а не замедлять их 🙂 Так что не вариант иметь много конвертеров.

Сейчас я читаю много тем. Один делает ПОПО & интерфейсы, которые соответствуют соответствующим сущностям, таким как Doctrine2, могут вернуться, но со всей дополнительной работой для Eloquent. Переключение на Doctrine2 должно быть достаточно простым, но оно может так сильно повлиять на производительность, иначе потребуется преобразовать данные массива Doctrine2 в эти собственные интерфейсы сущностей. Другие предпочитают возвращать простые массивы.

Один убеждает людей использовать Doctrine2 вместо Eloquent, но они не учитывают тот факт, что Doctrine2 тяжелый и вам действительно нужно использовать результаты массива для операций только для чтения.

Мы проектируем репозитории, чтобы быть изменяемыми, верно? Не потому, что это «красиво» только по дизайну. Итак, как мы можем полагаться на полные сущности, если они так сильно влияют на производительность или дублирующую работу? Даже при использовании только Doctrine2 (в сочетании) эта же проблема может возникнуть из-за его производительности!

Все реализации ORM могли бы возвращать массивы, таким образом, никакой дублирующей работы там нет. Хорошая производительность. Но мы пропускаем четкие контракты. И у нас нет интерфейсов для массивов или атрибутов классов (в качестве обходного пути) … Тьфу;)

Я просто пропускаю отсутствующий блок в наших языках программирования? Интерфейсы на простых структурах данных

Разумно ли создавать все массивы и использовать расширенную бизнес-логику для взаимодействия с этими массивами? Таким образом, нет классов с понятными интерфейсами. Любые предварительно вычисленные данные (обычно возвращаемые методом Entity) будут находиться в ключе массива, определенного классом Service. если не разумно, какова альтернатива, учитывая все вышеперечисленное?

Я был бы очень признателен, если бы кто-то с большим опытом в этой «области», учитывая производительность, различные реализации ORM и т. Д., Мог бы рассказать мне, как он / она справился с этим?

Заранее спасибо!

1

Решение

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

  1. Юридические лица / Хранилище
    • Используйте и передавайте сущности всегда при выполнении операций записи (создание вещей, обновление вещей, удаление вещей и их сложные комбинации).
    • Иногда вы можете использовать сущности при выполнении операций чтения (когда вы ожидаете, что чтение может понадобиться для записи вскоре после … т.е. -> findById скоро последует -> сохранить).
    • В любое время, когда вы работаете с сущностью (будь то запись или чтение), хранилище должно быть тем местом, куда вам следует обратиться. Вы должны быть в состоянии сказать новым разработчикам, что они могут сохраняться в базе данных только через Entities и Repository.
    • У сущностей будут свойства, которые представляют некоторый объект домена (много раз они представляют таблицу базы данных с полями таблицы, но не всегда). Они также будут содержать с собой логику / правила предметной области (т. Е. Валидацию, вычисления), поэтому они не являются анемичными. Кроме того, у вас могут быть некоторые доменные службы, если вашим сущностям нужна помощь во взаимодействии с другими сущностями (необходимо инициировать другие события), или вам просто нужно дополнительное место для обработки некоторой дополнительной логики домена (выполните вызовы репозитория для проверки некоторых уникальных условий).
    • Ваши Репозитории предназначены исключительно для работы с сущностями. Репозитории могут принимать сущности и выполнять с ними постоянную работу. Или они могут принять только некоторые параметры и выполнить чтение / извлечение в полные сущности.
    • Некоторые репозитории будут знать, как сохранить некоторые доменные объекты, которые являются более сложными, чем другие. Возможно, сущность, которая имеет свойство, которое содержит список других сущностей, которые необходимо сохранить рядом с основной сущностью (вы можете углубиться в изучение Агрегированных корней, если хотите).
    • Интерфейсы с репозиториями находятся на уровне вашего домена, но не в реальных реализациях этих репозиториев. Таким образом, вы можете иметь версию Eloquent или любую другую.
  2. Другие запросы (шлюз табличных данных)
    • Эти запросы не будут работать с сущностями. Они просто будут принимать параметры и возвращать такие вещи, как массивы или POPO (простые старые объекты PHP).
    • Много раз вам нужно будет выполнять операции чтения, которые не возвращаются в единую сущность. Эти операции чтения обычно предназначены для создания отчетов (не для операций, подобных CRUD, таких как чтение пользователя в форме редактирования, которая в конечном итоге отправляется и сохраняется). Например, у вас может быть отчет, содержащий 200 строк данных JOINed. Если вы использовали репозиторий и пытались вернуть большие глубокие объекты (со всеми заполненными отношениями или даже с отложенной загрузкой), то у вас будут проблемы с производительностью. Вместо этого используйте шаблон Таблицы данных Gatway. Вы просто отображаете данные и не нуждаетесь в силе ООП здесь. Выводимые данные могут, однако, содержать идентификаторы, которые через пользовательский интерфейс могут использоваться для инициирования вызовов методов персистентности репозитория.
    • Когда вы разрабатываете свое приложение, когда вы сталкиваетесь с необходимостью нового запроса Чтение / Отчет, создайте новый метод в каком-то классе где-нибудь в папке Table Data Gatway. Возможно, вы уже нашли аналогичный запрос, поэтому посмотрите, как можно объединить другой запрос. При необходимости используйте некоторые параметры, чтобы сделать запросы метода шлюза более гибкими, в частности (например, столбцы для выбора, порядок сортировки, разбиение на страницы и т. Д.). Не делайте ваши запросы слишком гибкими, хотя, это где строители запросов / ORM идут не так! Вам необходимо в определенной степени ограничить свои запросы тем, где, если вам нужно заменить их (возможно, другим механизмом базы данных), вы сможете легко понять, какие допустимые варианты есть, а какие нет. Вам нужно найти правильный баланс между гибкостью (чтобы у вас было больше кода DRY) и ограничениями (чтобы вы могли оптимизировать / заменить запросы позже).
    • Вы можете создавать службы в своем Домене для обработки параметров приема, затем передавать их в шлюз табличных данных, а затем получать обратные массивы, чтобы сделать еще несколько изменений. Это сохранит логику вашего домена в домене (и вне уровня инфраструктуры / персистентности хранилища & Таблица Data Gateway).
    • Опять же, как и в репозитории, используйте интерфейсы в ваших доменных службах, чтобы детали реализации не выходили за пределы вашего домена и находились в фактической папке шлюза табличных данных.
1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]