Comments 71
Кажется, ни один из известных, по крайней мере из популярных, подходов проектирования не рассматривает задачу избежание сегментации бизнес-логики. Причем это про разные стэки верно.
избежание сегментации бизнес-логики
Можете чуть больше раскрыть проблему?
Я имею в виду ситуацию, когда у нас есть, например, шина событий и, как это часто бывает, задачи, которые надо сделать вчера.
Но начать лучше с начала:
Разберу пример с обновлением какой-то сущности, потому что чтение не так наглядно, хотя и там бывают приколюхи.
- Вначале не было ничего
- Спроектировали идеальную систему с достаточным потенциалом к расширению с учетом всех изначальных требований, где запросы пользователя обрабатываются четко и ровно в том домене, где следует.
- роутинг, десериализаци, валидация, если нужно, выборка нужной сущности — POST без выборки или PUT/PATCH для обновления, сохранение, выдача результата — редирект на другой роут или просто страница со статусом выполненного запроса, или сериализация ответа в случае API
Потом приходят требования, которые можно быстро внедрить, например, прям на уровне (де)сериализации. В моей практике это было требование воспринимать строки true\false\0\1 как bool. Это часть бизнес-логики, потому что отправляющая сторона уже написана и формирует запросы именно так. Другой пример, после сохранения новой сущности нужно тригерить обновление чего-то другого в системе. это очень просто делается добавлением еще одного события и эксклюзивного подписчика оного.
В этих двух простых примерах бизнес-логика после внесения изменений теперь находится в двух частях и сходу не видно как без серьезного переписывания не сегментировать (логику). Вероятно, нужно держать разный набор схемы данных для разных слоёв — (де)сериализация, контроллер, дополнительные обработчики — и получится что-то типа middleware, но, кажется, что 1) код распухает и 2) система становится переусложненной со всеми вытекающими последствиями
P.S.: прошу прощения за не очень спешный ответ.
P.S.: Опыт проектирования у меня чуть более чем нулевой, поэтому я не стал приводить несколько разных примеров архитектуры, но, кажется, одного будет достаточно
Это часть бизнес-логики,
Скорее логики представления. Вы же конвертируете там представление данных а не бизнес логикой занимаетесь. Или там не тупо 0
в true
скастить?
не сегментировать (логику).
То есть под сегментированием логики вы подразумеваете случаи когда логика вытекает наружу? Ну мол нарушение инкапсуляции, закона Деметры, open/close и srp?
1) код распухает
в чем это выражается?
2) система становится переусложненной со всеми вытекающими последствиями
Опять же, мне сложно представить себе подобное разделение ответственности усложняет код. Было бы все же интересно хотя бы приблизительно разобрать ваш случай.
Скорее логики представления. Вы же конвертируете там представление данных а не бизнес логикой занимаетесь
Логика представления специфична для клиента, который присылает данные, а не для протокола, которым клиент пользуется. Причем это один из с пару десятков вариантов специфичности десериализации. Этот код нельзя будет применить для другого клиента. Так что я думаю, что это именно бизнес-логика. Возможно я не прав.
То есть под сегментированием логики вы подразумеваете случаи когда логика вытекает наружу? Ну мол нарушение инкапсуляции, закона Деметры, open/close и srp?
Если говорить известными терминами, то да. Оно самое.
в чем это выражается?
Это когда ты смотришь на исходники и понимаешь, можно бы было обойтись меньшим количеством байт ))
Опять же, мне сложно представить себе подобное разделение ответственности усложняет код. Было бы все же интересно хотя бы приблизительно разобрать ваш случай.
Наверное, лучше привести в пример то, как обычно проходит code review. Обычно приходит набор файлов с диффом, из которого довольно сложно понять как точечные изменения позволяют решить поставленную задачу. Конечно, бывают и другие, где понятно, что и почему изменено и почему этот дифф оптимально решает задачу. Но таких PR мало. Увы.
Поэтому мне лично очень интересно как нужно проектировать систему, которая на достаточно большой перспективе позволяет хорошо организовывать бизнес-логику. И было бы очень круто, если подход не будет требовать писать тонны бойлерплейта.
В вашей общей схеме Applicatioin->router->controller->domain->response вы оригинально подошли только к реализации domain. Эту схему можно организовать на любом современном php фреймворке (symfony, slim ,laravel...), поэтому не стоило акцентировать внимание на вашей реализации («велосипеде»).
Сама же реализация доменных команд через контексты вполне интересна, видел не много примеров, поэтому прочел бы о вашей реализации.
И да, «Обычная ситуация» в современных php фреймворках с бизнес логикой в контроллере — встречается только у новичков и в документации(откуда они это и берут). А в документации просто нет смысла создавать сервисный слой, чтобы показать как отрендерить страницу списка пользователей.
«Обычная ситуация» действительно встречается у новичков и в документации, но она есть и в больших коммерческих проектах часто просто потому что это были изначально так написано и менять никто ничего не планирует.
P.S. Статья очень хорошая, но было бы классно в дальнейшем увидеть продолжение с более подробными примерами различных архитектурных слоев.
Интерфейс на PHP+Smarty шаблонизатор.
А все сущности и логика в хранимых процедурах MS SQL.
Для этой цели архитектура оказалось вполне удачной, а по производительности — быстрее я не видел.
Но новый проект так бы уже делать не стал — сопровождается тяжело.
DDD вы не понимаете.
Enity — хрень с защищёнными свойствами в массиве, магическим __call и даже сеттер для айди работает с неявным поведением $this->id = $this->id == 0? $id: $this->id;
Репозиторий который не репозиторий, в котором мэппер, который почему-то не мэпит, а персистит данные.
Контекст непонятная штука, чей контракт зафиксирован в виде абстрактноо класса и не предполагает нормальную инъекцию зависимостей.
Ну и это первая половина проблемы. Вы не знаете синтаксиса php и работаете с отключёнными ошибками вроде E_NOTICE, E_DEPRECATED, потому принимаете объекты по ссылке. Даже оформление кода не по PSR и вооще пахнет временами php 4.
И ещё куча ног торчит из стога.
чем вообще занимается репозиторий, если он не работает с хранилищем? Зачем он тогда нужен?
Этот подход описан в DDD как альтернатива ActiveRecord который встречается во многих php фреймворках.
Плюс этого подхода в том что мы сущности предметной области отделяем от логики и персистентного хранения в отличии от ActiveRecord, где все свалено в суперкласс который умеет все.
Ну не все, а только сохранение/загрузка сущности)
А подскажите, в чем плюс того, что мы сущности отделяем от логики?
Плюс этого подхода в том что мы сущности предметной области отделяем от логики
Или я не так понял?
AR вполне решает многие задачи и отлично тестируется (не верьте мифам о том что это не так). Далеко не всегда нужен DM с IdentityMap и UnitOfWork.
Хватит уже смотреть на них как на черное и белое.
Отделение логики персистентного хранения может быть реализовано и в нормальном AR.
вот только называется это уже не AR а Row Data Gateway. Суть AR как раз в отсутствии разделения. В этом есть свои преимущества в определенном спектре задач.
AR вполне решает многие задачи и отлично тестируется
А вот тут поподробнее. Либо вы имеете ввиду обычные интеграционные тесты где надо поднимать реальную базу (выходить за пределы процесса в котором выполняются тесты), либо у вас есть чем поделиться с народом по этому вопросу.
Хватит уже смотреть на них как на черное и белое.
Мне в последнее время нравится идея использования DM с IM и UoW для операций записи и бизнес логики, и AR для операций чтения где надо просто в базу сходить да достать данные. Эдакое тупое DTO между базой и view частью приложения. Для этого AR идеально.
В приведенном фрагменте, если в качестве зависимости передать не Repository а Mapper не поменяется вообще ничего.
Полезная нагрузка мапперов например получать сущности по какимто специфическим условиям.
Пример: из предметной области можно просто сказать репозиторию, дай мне пользователя с id = 4 и статусом = 10 и не заблокированного.
И в данном случае предментая область не нуждается в знаниях как это делается, Репозиторий преобразует этот запрос мапперу.
Сам маппер это уже отдельная сущность которая в отличии от репозитория знает как сохранять в базу, как загружать из нее, знает про sql и прочие инфраструктурные нюансы.
Тогда что мешает подменять целиком репозиторий?
В чем зависимость-то?
update
намекает что бизнес логике все еще приходится париться с обновлением данных в базе. А это ломает всю идею.
Для этого придумали unit of work, дабы ваш слой персистентности декларировал объекты бизнес транзакции как некий юнит, который можно было бы красиво "флашнуть".
Да, это сложно, но мне интересно было бы глянуть как вы "сохраняете" большой граф объектов. Просто интересно ибо "изолированно" я этого ниукого не видал. Как-то две крайности — либо размазано либо строгий unit of work.
Репозиторий это абстракция в бизнес-логике которая позволяет не зная ничего о инфраструктурном уровне работы с базой данных получать оттуда сущности, сохранять их туда.
save your repository from save. Когда в вашем репозитории появляется метод save
или update
то он перестает быть репозиторием и становится TableGateway который "вытек" из слоя персистентности в бизнес логику.
Более того, не очень понятно к чему приведет пример. Привели бы реальный пример репозитория для агрегата сущности. Что мол "нам теперь на каждый агрегат придется писать мэппер".
Следующий этап сделать ее ближе к эталону
Опять же, зачем? Это как-то поможет бизнесу? Если да то как?
p.s. есть неплохой докладик на эту тему: Greg Young — Stop Over Engineering
Во вторых нет такого понятия как райт вэй, любая практика берется и адаптируется под конкретные нужды.
От вас к сожалению кроме голой и агрессивной критики я не получил ни одного примера реализации бизнес-логики ни по книге Эванся ни по книгам Фаулера.
Вам я советую почитать книги Карнеги, например «Как завоёвывать друзей и оказывать влияние на людей» и для начала научиться общаться с людьми.
Карнеги хорош, кода хочешь навешать лапши на уши. Здесь же профессиональный ресурс, где люди обмениваются знаниями и опытом, а не заводят новых друзей. В отношении вас я умышленно применил агрессию и троллинг, дабы заставить вас усомниться и освежить знания.
А просто тыкать в умные книжки не надо.
Жду аргументированных комментариев с примервами, с обоснованиями, если таковых нет то удачных вам споров в следущем посте на хабре.
Здесь же профессиональный ресурс, где люди обмениваются знаниями и опытом, а не заводят новых друзей.
А может вы поделитесь опытом на серьезном ресурсе? Автор поделился своей практикой и он молодец.
Напишите, пожалуйста, чем конкретно он не прав и где ошибся в терминологии, а так же приведя примеры из своей практики, как вы сделали «по книжке» и это принесло профит проекту — в этом и ценность ресурса, не так ли?
Именно потому, я не хочу делиться опытом, потому что мои советы тоже могут быть вредными за пределами контекста. Надо опираться на серьёзную литературу, где рассмотрена куча разных кейсов, она не раз уже упоминалась.
К тому же в php есть готовые решения вроде doctrine-propel, которые значительно лучше поделки автора, который даже php знает с оговорками.
Ну во первых книги это только теория
а вы их читали? У Эванса практически все примеры рассматриваются в контексте его проектов. У Фаулера если читать книжки вроде "Рефакторинг" тоже все хорошо.
Тут просто загвоздка какая, с такими концепциями как DDD это возможность "неверно понять" и потом закрепить это неверное понимание на практике. Так что по сути не стоит "сразу делать по DDD" а просто продолжаем делать как делаем и пытаемся осмыслить "чем отличается".
С другой стороны ваша статья просто про ваш вариант архитектуры приложения, в котором не сказать что есть что-то новое.
По своему опыту могу сказать что то что у вас описано как Context
(тут к слову может возникать конфликт с термином bounded context) далеко не самый удобный вариант. Это получше конечно чем "классы менеджеры", но в целом одно и то же (упор на transaction script). У Роберта Мартина в его "чистой архитектуре" скажем похожий концепт назывался Interactor, реализация юзкейса. Но это просто эдакое место в котором декларируется порядок действий в отвязке от UI. Никакой логики там быть не должно, вся логика делегируется сервисам, сущностям и объектам значениям. И вот это вполне удобно, хотя и требует весьма много дисциплины.
Для полноты без практики не обойтись.
практика будет приносить пользу только с постоянным анализом всех принятых решений. Большинство как-то этот момент упускают.
Скажем из описания в вашей статье я так и не услышал зачем вы сделали что-то свое. У меня к примеру есть на этот счет теория почему толковых дата мэпперов мало а реализаций недо-orm пруд пруди. Всем хочется что-то свое что они знают (NiH синдром или просто скучно), но делать что-то по настоящему полезное лень. В итоге мы имеем сотни тысяч строк никому ненужного кода.
Просто скажите сколько человекочасов ушло на реализацию вашей инфраструктуры? При том что все компоненты необходимые для этого уже давно есть. Я понимаю если бы вы инвестировали время в то что бы допилить какой-нибудь интересный компонент который несправедливо забыт, но я этого не увидел.
Ну и все же — хотелось бы примерчики увидеть что бы вышла более конструктивная критика. Ибо я так понял планов выпуска в opensource нет. В частности меня интересует как вы покрываете код тестами. Есть ли логика в сущностях, насколько грамотно вы дробите систему на модули… А то что у вас там есть объект контроллер (не тот который с http работает а тот который декларирует control-flow — context в вашем случае) — это у всех есть так или иначе.
В этом плане у нас все работа с финраструктурными частями основана на готовых решениях, тут и работа с базой и роутинг и всевозможные апи.
На себя было взята только организация каркаса, бизнес-логики и верхней части хранения.
На каркас ушло гдето 1-2 месяца 1 человека, дальше мелкие доработки.
В наше время вообще использование громостких фреймворков часто стрельба по воробьям из пушки, легко можно собрать свое приложение хранящее бизнес логику и окружить его готовыми библиотеками работающими с инфраструктырными частями.
На счет мапперов и хранения мне странно что всех это так волнует, самая важная частьб всетаки юизнес логика а как сохранять ее состояния уже не важно, важно ее отделить от сохранения, об этом говорять все подходы начеленные на работу с бизнес-логикой.
Платформа написана и не стоит на месте, есть планы после анализа развивать ее дальше, написанная статья тоже говорит о том что мы будем учитывать критику :)
Тесты у нас приемочные, есть план перейти на тестирование базнес логики по контекстам.
Логика есть как в контекстах так и в сущностях.
Модулей как таковых нет есть наборы контекстов.
Ну контроллер и TransactionScript конечно похожи но это разные концепции, не стоит их путать. Как вы говорите control-flow инфраструктуры и control-flow бизнес логики это 2 разные вещи, хотя часто их сваливают в одно и это как раз Controller слой в MVC подобных фрейворках.
А если серьёзно, то конечно спрос ниже, чем на всякие общие хау ту по технологиям и новости с жёлтыми заголовоками. Но это нормально, т.к. целевая аудитория небольшая. Она ограничена как языком, так и уровнем, необходимым для восприятия. Большинство начинающих и средних разработчиков этой темой не интересуются, поскольку проектирование такого уровня выполняет кто-то за них. А многие мыслящие на нужном уровне этот этап прошли, и всё уже для себя решили.
Я, когда активно интересовался подобными материалами, как раз столкнулся с дефицитом публикаций, особенно на русском, и уж совсем в контексте php.
Так что пишите обязательно ещё, потомки будут в долгу)
Сама логика разрабатывается так чтоб в мепперах не надо было работать со сложными связями объектов, каждый меппер знает только свой один объект с которым он работает.
SRP очень важен, но в реальности к нему надо стремится как к идеалу но достигнуть его бывает достаточно сложно или в некоторых случаях нецелесообразно.
Ссылка это как бы показатель того что первый код тут был написан уже давно :)
Потом приходят требования, которые можно быстро внедрить, например, прям на уровне (де)сериализации. В моей практике это было требование воспринимать строки true\false\0\1 как bool. Это часть бизнес-логики, потому что отправляющая сторона уже написана и формирует запросы именно так.
Вот тут не часть бизнес-логики в общем случае (бизнес по (де)сериализации данных опустим для ясности :) ). Это лишь уровень представления/интерпретации данных. Бизнесу нет дела до наших типов, в лучшем случае его требования "галочку поставил в форме — должно работать так, убрал — вот так.", а скорее "если пользователь согласился с получением рекламы от нас, то пускаем его дальше, если нет — не пускаем",
Решение проблем организации бизнес-логики в PHP или как пойти своим путем