Pull to refresh

Comments 71

Кажется, ни один из известных, по крайней мере из популярных, подходов проектирования не рассматривает задачу избежание сегментации бизнес-логики. Причем это про разные стэки верно.

Тут я соглашусь, все подходы дают часто только теоретическую базу, а все важные нюансы появляются как раз на практике. Если я правилньо понял про сегментирование бизнес-логики, то мы в одном проекте использовали подход разделение бизнес-логики на под бизнес домены. Сценарии пользователя строили по взаимодействию этих доменов.
избежание сегментации бизнес-логики

Можете чуть больше раскрыть проблему?

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


Но начать лучше с начала:


Разберу пример с обновлением какой-то сущности, потому что чтение не так наглядно, хотя и там бывают приколюхи.


  1. Вначале не было ничего
  2. Спроектировали идеальную систему с достаточным потенциалом к расширению с учетом всех изначальных требований, где запросы пользователя обрабатываются четко и ровно в том домене, где следует.
  3. роутинг, десериализаци, валидация, если нужно, выборка нужной сущности — 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 фреймворках с бизнес логикой в контроллере — встречается только у новичков и в документации(откуда они это и берут). А в документации просто нет смысла создавать сервисный слой, чтобы показать как отрендерить страницу списка пользователей.
Согласен с тем что данную схему можно организовать на любом современном фреймворке, но также придется повозиться, а акцентировал внимание я в связи с тем что мы старались сделать какуюто платформу именно для бизнес логики в то время как все современные фрейворки предоставляют инструменты для создани одной инфраструктуры.
«Обычная ситуация» действительно встречается у новичков и в документации, но она есть и в больших коммерческих проектах часто просто потому что это были изначально так написано и менять никто ничего не планирует.
Фреймворки предоставляют инструменты лишь для инфраструктуры, чтобы вы могли реализовать свою бизнес логику как вашей душе угодно(у вас это Transaction Script, у вторых Service Layer, у третьих CommandBus\CQRS). Вы же говорите, что создали платформу для написания бизнес логики — она тем более должна быть «framework agnostic». В любом случае увидеть еще один способ полезно.
Если я всё правильно понял, то мне кажется вы немного ошибаетесь, что фреймворки предоставляют инструменты для создания только одной инфраструктуры, это верно не для всех фреймворков (Из php тут же в голову пришел Zend Framework), не говоря уже о микрофреймворках, которые дают базовые вещи, на основе которых можно построить любую структуру которую можно пожелать (правда и писать кода придется побольше).

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) имеют свои плюсы и минусы. Отделение логики персистентного хранения может быть реализовано и в нормальном AR.
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 который "вытек" из слоя персистентности в бизнес логику.


Более того, не очень понятно к чему приведет пример. Привели бы реальный пример репозитория для агрегата сущности. Что мол "нам теперь на каждый агрегат придется писать мэппер".

Не буду говорить что данная реализация репозитория удалась, но она решает поставленные задача. Следующий этап сделать ее ближе к эталону
Тут палка о двух концах, в целях бизнеса вообще могут быть не нужны все эти архитектурные плюшки, есть проекты в которых жуткий га*нокод и они приносят бизнесу доход.
С другой стороны инженер должен исследовать.
Здесь уже надо балансировать между этим двумя крайностями)
Почему удалась? Статья ужасная и вредная для прочтения. Не дай о кто-то прочтёт, не дойдёт до комментариев и будет это воспринимать как райт вэй. Автору надо подучить сам php, а затем перечитать книги на который он ссылался и почитать какого-нибудь Нильссона, чтобы увидеть практическую реализацию.
Жду вашей статьи на хабре про «райт вэй»!
Зачем писать? Есть книги Эвайнса и Фаулера и куча статей от этих же авторов — это райт вэй. Надо их внимательной читать, а не набравшись по вершка и статьям лепить свои велосипеды.
Ну во первых книги это только теория которая нуждается в шлефовке на практике что и было сделано.
Во вторых нет такого понятия как райт вэй, любая практика берется и адаптируется под конкретные нужды.
От вас к сожалению кроме голой и агрессивной критики я не получил ни одного примера реализации бизнес-логики ни по книге Эванся ни по книгам Фаулера.
Вам я советую почитать книги Карнеги, например «Как завоёвывать друзей и оказывать влияние на людей» и для начала научиться общаться с людьми.
Да почитайте вы их, там есть реализации с кодом, плюс в посте выше я писал о Нильссоне, у него практики ещё больше. Но вы даже с терминами не разобрались, а пытаетесь делиться «опытом».

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

А может вы поделитесь опытом на серьезном ресурсе? Автор поделился своей практикой и он молодец.
Напишите, пожалуйста, чем конкретно он не прав и где ошибся в терминологии, а так же приведя примеры из своей практики, как вы сделали «по книжке» и это принесло профит проекту — в этом и ценность ресурса, не так ли?
Я в начальном комментарии указал пару проблем, но вся статья хреновая, а главное вредная.
Именно потому, я не хочу делиться опытом, потому что мои советы тоже могут быть вредными за пределами контекста. Надо опираться на серьёзную литературу, где рассмотрена куча разных кейсов, она не раз уже упоминалась.
К тому же в 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.

Так что пишите обязательно ещё, потомки будут в долгу)
За плюс спасибо. Информации на этот счет действительно мало, поэтому в свое время я и занялся практическим применением этих подходов. Буду очень рад видеть что программисты начнут использують хотябы TransactionScript как стандарт в своих проектах.
вот и я столкнулся с дефицитом. Теории много, а вот чтоб с практикой — совсем мало.
очень хочется увидеть инструмент по работе с БД на основе DataMapper, выложите пожалуйста на github
Работа с бд сделана на основа FluentPDO, на счет выкладки на github подумаем.
конкретно Маппинг связанных сущностей интересует, транзакционность, валидация
Могу четсно сказать что с этим мы не придумывали ничего сложного и нового у нас простая раздельная работа со связанными сущностями. В мапперах нет хранения связей между таблицами, все связи прописаны в бизнес логике и за ее пределы не выходят.
UFO just landed and posted this here
Я бы сказал так что маппер делает только меппинг, а сохранение как финальная чать мепинга.
Сама логика разрабатывается так чтоб в мепперах не надо было работать со сложными связями объектов, каждый меппер знает только свой один объект с которым он работает.

SRP очень важен, но в реальности к нему надо стремится как к идеалу но достигнуть его бывает достаточно сложно или в некоторых случаях нецелесообразно.

Ссылка это как бы показатель того что первый код тут был написан уже давно :)
Бизнес-логика обычно не оперирует на уровне приведения строк к булевым. В модели булево, а привидение к нему строковых, числовых и прочих значений — задача других слоёв. Вообще часто встречаюсь с тем, что к бизнес-логике относят почти всё, хотя к ней относится только то, чем оперирует бизнес. Простой тест на то относить что-то к бизнес-логике или к иной — представить данное правило/алгоритм/… в «докомпьютерную эпоху». Почти всегда этот приём позволяет отделить бизнес-логику от иной. Скажем, в обычном документообороте нет понятий «сохранить» или «загрузить» документ или «привести строку к булеву».
Бизнес-логика конечно не оперирует, но вот программная модель этой бизнес логики которую разрабатывают программисты еще как будет оперировать и привидением типов и использовать циклы и операторы и создавать типы данных.
Потом приходят требования, которые можно быстро внедрить, например, прям на уровне (де)сериализации. В моей практике это было требование воспринимать строки true\false\0\1 как bool. Это часть бизнес-логики, потому что отправляющая сторона уже написана и формирует запросы именно так.

Вот тут не часть бизнес-логики в общем случае (бизнес по (де)сериализации данных опустим для ясности :) ). Это лишь уровень представления/интерпретации данных. Бизнесу нет дела до наших типов, в лучшем случае его требования "галочку поставил в форме — должно работать так, убрал — вот так.", а скорее "если пользователь согласился с получением рекламы от нас, то пускаем его дальше, если нет — не пускаем",

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

Sign up to leave a comment.

Articles