Pull to refresh
1
0

Руководитель разработки

Send message

Если логика составного бизнес процесса реализуется совместной работой нескольких доменных объектов - нельзя говорить, что она содержится в них. Кроме одного случая - у вас домене есть объект, олицетворяющий бизнес-логику. Но, на мой вкус, такие объекты не сильно далеки от сервисов бизнес-логики. Хотя нет, многие делают иначе - оставляют в объектах домена куски несвойственной им логики, а потом (порой болезненно) ищут решения проблем по типу той, которая описана в статье.

Да, в объектах домена содержится их индивидуальная логика. Но логика бизнес-процесса должна быть в том, что с нею связано. Вот та же заявка на кредит - она же, по сути, документ. То, что клиент делает в онлайн-банке аналогично тому, что он придёт в офис и заполнит бланк заявки. Потом это заявку рассматривают, ставят на ней визы и много чего ещё делают другие люди. И есть заявка, а есть бизнес-процесс одобрения заявки. И это точно не один доменный объект.

Вы на примерах показываете, что так оно есть, но на словах согласится не хотите.

Всё так. Но вы сами пишете, что вся логика " находится внутри доменных объектов.". И я вам именно про это говорю - ну не помещается логика бизнес-процесса в одном объекте.

А уж как они будут взаимодействовать - дело другое.

Я про саму проверку. Тут и лимит ни фига не константа, а имеет куча зависимостей, да и суть не процентах, а прохождении бизнес-проверок, о сути которых заявка на кредит может и не знать. Но что более интересно, что проверка уровня риска для кредитных продуктов - отдельный процесс, работающий во многих продуктах. И вот, внезапно, на равном месте, где-то в вашей доменной модели появляется сущность "оценка кредитного риска", которая живёт по своим правилам, может обрабатываться как автоматически, так и с привлечением риск-менеджера, имеет кучу зависимостей (кредитная история, куча сведений о клиента, базы антифрода, рисковые модели и пр). Ну вот это точно не внутренность объекта "заявка на кредит".

А если проверка на " кредитная нагрузка клиента превышает 80%" относится к категории рисковых оценок, используется во многих процессах, то где она должна быть? Будете дублировать в каждом объекте?

Да, всё так. И я про то же толкую :)

Логика обработки внутреннего состояния - внутри объекта. Логика составного бизнес-процесса (и то, что выходит за рамки внутреннего состояния объекта) - снаружи. Вариантов (архитектур) реализации - множество.

Касательно примера из статьи, для меня лично, ключевой вопрос - является ли требование уникальности email естественным (неотъемлемым) правилом объекта user. Если да - проверка должна быть встроена в операцию доменного объекта (техники, опять же, множество). В иных случаях эту проверку должен делать тот, кого она интересует.

вся доменная логика, которая касается регистрации пользователя, находится внутри объекта User

Вы уверены? Если для без подтверждения email-а пользователь ничего делать не может, то он вроде как и не зарегистрировался. И наверняка, чуть позже другой пользователь попробует зарегистрироваться с этим email-ом, подтвердить его и только тогда его регистрация завершится. Кстати, на многих сайтах всё именно так и работает. А порой список действий, который должны быть выполнены, прежде пользователь станет таки зарегистрированным, ещё больше

Таким образом, вопрос в том, где у вас проходит граница бизнес-логики. Если в конкретном случае она помещается в рамках одной транзакции работы с объектом - то ок, может пробовать запихать внутрь. Но в достаточно большой системе (достаточной для того, чтобы проектировать её согласно DDD) достаточно часто так не будет получаться (достаточно для того, чтобы изначально делать такую архитектуру, где бизнес-логика реализуется вне доменных объектов)

И вот тут тонкая грань между "создать пользователя" и "создать объект типа Пользователь". Конструктор в нашем приложении может много где вызываться. И даже при вычитывании пользователя из БД. Потому конструктор класса - точно не подходит. А где же тогда проверять бизнес-правила?.. Ну, где-то перед тем, как мы его в БД создадим? Ну, тут, как бы можно даже constraint на таблицу повесить. А если будет требование подтверждения того, что email принадлежит пользователю? То после записи в БД (с проверкой уникальности) надо будет ему ещё письмо послать и подождать, пока он ссылку из письма откроет, а это кто сделает? А если ещё надо проверить, нет ли этого email или комбинации фио+возраст+город в списках фрода?

Список можно бесконечно продолжать. И тут дело не в моей фантазии, а в том, что так живёт бизнес - там всё время что-то новое придумывают. И во множестве реальных ситуация рано или поздно становится понятно, что бизнес-правила - это одном, а низкоуровневая программная модель - это другое. И тут уж хоть саги городи, хоть хитроумные валидаторы, хоть сервисы. Но вот точно, держать всё эту кухню внутри классов, моделирующих объекты доменной модели, уже не получится.

Я тут вижу явное противоречие. 

Я не говорю, что в объектах ничего не было, я говорю, что они не содержали посторонней бизнес-логики.

Есть специальные шаблоны предназначенные для этого, такие как сага или оркестратор

Тут уже вопрос в инструменте. Можно и сагу запилить, а можно и OrderProcessingService. В любом случае в объекте Order не будет операций бизнес-процесса.

И если уж вернуться к теме топика, то можно понять, требование уникальности email - это внешнее требование, а потому не должно быть внутри нашего объекта User.

Да никто не мешает всё делать в рамках ООП (лично я именно так и делаю). Но я вам пытаюсь показать, что на каждом этапе бизнес-процесса участвуют более одного объекта доменной модели. "изменении состояния объекта Заказ по определенному алгоритму  " - это лишь запчасть бизнес-процесса. Ну не может заказ рулить всеми элементами бизнес-процесса. И курьерская служба не должна рулить состоянием заказа, т.к., грубо говоря, у курьера задача взять коробку из пункта А и привезти в пункт Б. И т.д.

Я не говорю, что объект заказ должен быть анемичным. Но с точки зрения классов в ООП, кто-то является заказом, а кто-то должен олицетворять элементы бизнес-процесса, а кто-то оркестрировать всем этим ансамблем.

В чем принципиальная разница меду этими двумя фрагментами кода с точки зрения моделирования бизнес процесса

С мой точки зрения оба фрагмента не моделируют бизнес-процесс.

Потому что в реальности всю будет гораздо сложнее. Вы ведь заказывали в интернет-магазинах товары с доставкой? Примерно помните, как выглядит фасад бизнес-процесса? Собрав корзину вы нажимаете кнопку "оформить заказ", после чего выбирает опции доставки и оплаты. Потом нажимаете "подтвердить" и вот тут начинается бизнес-процесс. Возьмём, к примеру, заказ в wildberries с доставкой в ПВЗ и оплатой с привязанной карты после получения товара покупателем. В модели этого бизнес-процесс будет три этапа (упрощённо): оформление доставки, доставка товара в ПВЗ, акцепт получения тех позиций, которые покупатель забрал. На первом этапе будет выполнено аллоцирование товара у продавца, будет создан заказ-наряд на доставку (точнее множество, если в корзине не один товар). На втором служба доставки сначала заберёт товар у продавца, потом привезёт в ПВЗ, где сотрудник подтвердит приёмку товара (а покупатель в ЛК будет видеть все этапы доставки). На третьем покупатель предъявит в ПВЗ код получения, сотрудник выдаст товары, покупатель проверит целостность, что не сломалось, что заберёт, другое вернёт, после чего должна сработать оплата одних товаров и запущен процесс возврата других.

И это ещё не всё, а так, процентов 10 от реальной сложности бизнес-процесса. И вот теперь вопрос - чем в этом бизнес-процессе является заказ? Кто выполняет все указанные действия? Одного $order->deliver(); тут точно не хватит. Кто и когда будет делать всё остальное?

Скорее нужны менеджер доставки и агент курьерской службы. Первый по выбору покупателя формирует в заказ на доставку в той или иной курьерской компании, а второй принимает заказ в работу и исполняет его (или не исполняет). По итогу исполнения агент курьерской службы сообщает менеджеру доставки результат (успех/неуспех), а тот выполняет необходимые действия с покупкой.

В реальном магазине будут ещё складская служба, платёжный шлюз и много кто ещё.

Не, тут вопрос не в точном воспроизведении. И, конечно, это не процедурный подход.

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

В программной модели и то и другое реализуется через объекты, но они разные.

Объект "оплата" - это что-то вроде чека. Ну или счёт-фактура. Т.е. тоже документ, либо намерение, либо факт. Но кто-то должен выполнять активную функцию, вносить изменение в общее состояние системы (принимать деньги, вносить их на счёт, изменять состояние заказа на "оплачен" и "доставлен", а курьерскую заявку переводить в статус "выполнена". В некоторых нотациях бизнес-процессов этого кого-то называют Actor. И даже если это User, это не та же сущность, которую мы извлекаем из БД по запросу GET api/users/{id}. А в сложно-сочинённых сценариях Actor - это модуль системы.

Ну или в чём-то типа PaymentManager (service/processor/ etc).

Иначе говоря, в реальном мире всем понятно, что заказ себя не оформляет, не оплачивает, не доставляет. И даже User сам себя не регистрирует. Этим всем занимаются манагеры, сиречь уполномоченные субъекты, они же - сервисы доменной модели.

А вот как раз сервисы имеют полное право взаимодействовать с кучей сущностей и других сервисов - всё, что будет полезно для выполнения функции. А объекты доменной модели должны оставаться существительными.

Скажите, а оплата заказа в интернет-магазине с использованием промо-кода и накопленных бонусов, особенно, если сама оплата делается при получении заказа через терминал у курьера, она в каком объекте доменной модели должна быть? Order? User? что-то ещё?

А я в описанном примере вижу другую проблему. В приложении есть слой для объектов с состоянием, но нет слоя для бизнес-логики. Т.е. проблема возникла тогда, когда бизнес-логику втащили с объект доменной модели. И если для анемичных правил (типа непустого имени) это ещё может быть корректно, то для правил, выходящих за границы объекта, это точно некорректно.

Что делать - понятно. Выносить бизнес-логику в отдельный слой. Остаётся только вопрос - когда она будет вызвана. И тут очевидно, что должно быть событие/действие, в рамках которого работает бизнес-логика. Но тут важно одно уточнение. По идее никаких действий с объектами домена приложения не должно выполняться в обход бизнес-логики. И тут многие идут "упрощением", втаскивая бизнес-логику внутрь объекта. Тогда как правильно, чтобы все действия изначально были бы в слое бизнес-логики. Иначе говоря, в случае веб-сайта из контроллера вызов идёт в сервис слоя бизнес-логики. И если уж нужна валидация почты - оно должно делаться где-то там и до сохранения данных с БД.

Всё это изрядно пересекается с принципами SOLID. И все представленные в статье варианты нарушают те или иные принципы. Я на одном из проектов реализовал иной подход. Основой стала мысль о том, что бизнес-правила в общем случаем формулируются однажды, а работать должны всегда. Т.е. в любой момент времени может прийти новое требование, которое должно работать и в новом коде и в старом. Перелопачивать горы кода мне, конечно, лень. Поэтому в том проекте были реализован механизм валидаторов правил. При этом необходимость выполнения требований навешивались атрибутами на методы сервисов бизнес-логики, а выполнялись сервисом валидации, который вызывался интерсептором до вызова метода. В атрибуте указывался тип операции с объектом (из множества операций, связанных с типом) и сам объект (ссылка на параметр метода). Сервис же валидации находил все зарегистрированные требования для типа объекта и типа операции и вызывал проверку всех требований. Как итог, все бизнес-правила лежали в одном месте и их список можно было спокойно расширять. Объекты доменной модели вообще ничего не знали про логику, а сервисы бизнес-логики не вылезали за границы своей ответственности.

Правильно понимаете.

Проблема подобных обучающих материалов в том, что за словами теряется суть. Например то, что выражение "value-типы хранятся в стеке" верно только для локальных переменных, да и то, с уточнениями.

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

Первая ошибка в мотивации (причине) использования обобщений.

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

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

Вторая ошибка в самом примере. Всё же тип обобщения подразумевает контракт - цель его использования. Был бы у вас тип Product<TPrice> - было бы понятно, это тип цены, и это, очевидно, должен быть численный тип.

Ну а дальше можно ещё больше покритиковать пример. Например, продукт сам себя не продаёт. В нормальном мире было бы что-то типа "market.sell(product, buyer);". А если уж надо по разному печатать цену в зависимости от условий, то у продукта был бы метод "ShowPrice()".

Лучше бы вы приводили пример на базе библиотечных типов, заодно бы объяснили, чем хороши обобщения. Например, Dictionary<TKey, TValue> отлично раскрывает тему.

Уважаемы автор! В этой статье, написанной в стиле прошлого столетия, вы забыли главное - рассказать о том, что заявлено в заголовке: "Технология SQL-файл".
Есть отдельные кусочки описания, но понять, что же такое на самом деле, прочитав статью, невозможно. При этом вы много рассказываете про перечисляете используемые командеры, хелперы, эксперименты, собственные статьи...

Но где сам "SQL-файл?
Хоть бы один пример такого проектика? С описанием задачи, решаемых проблем...
А то, всё, что есть, это фраза, где-то в цитате, "Идея состоит в том, чтобы поддерживать исходный код и/или вспомогательные скрипты в виде SQL-файлов в директориях, транслируя их в базу данных, по отдельности либо группами. "

Если ваш "SQL-файл" - это хиленький препроцессор и cmd для выполнения скриптов БД, то не понятно, а что в нём интересного, кроме кучи устаревших подходов?
Кодогенерация - так есть куча альтернатив (и они сильно мощнее простенького препроцессора). Апдейт базы - так есть миграции (и это не обязательно ORM). Редакторы кода - тем более. И всё это нормально дружит с git и devops, что сильно снижает трудозатраты и риск ошибки...

Иначе говоря, скажите, какую проблему решает ваша технология, которую (проблему) не решают другие популярные современные решения.

И того и другого.

SOLID, если его правильно готовить, приводит к тому, что нечто пишется в коде один раз. Если тебе надо это доработать - то тут и дорабатываешь, притом с осознаваемыми последствиями.

Автоматизированные тесты (самые разные) позволяют раньше отловить вторичные последствия, в частности, из-за ошибок реализации доработок. А если уж количество и качество тестов близки к идее TDD, то можно смело делать рефакторинги, определяя необходимые доработки по свалившимся тестам.

Но, факт, при нормальной архитектуре кода хорошие автотесты писать намного проще. В мною переписанном проекте большая часть юнит-тестов выглядели как куски бизнес-сценариев (местами полные, например, имитировали обработку REST-запроса на создание заказа, где целью теста было убедиться, что из входных данных в итоге формируется правильный набор объектов), что хорошо работало благодаря заглушкам внешних интерфейсов (БД, файлы, внешние апи), внедрённых в DI-контейнер. В итоге, при не очень большом количестве тестов тестовое покрытие было 96%.

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity