Как определить где размещать бизнес-логику
От: Nikita Lyapin Россия https://architecture-cleaning.ru/
Дата: 30.04.21 10:50
Оценка:
Привет всем!

Часто возникают обсуждения о том где размещать бизнес-логику приложения. В итоге я собрался силами, систематизировал свои знания по этому вопрос и написал статью. Здесь
По большому счету Фаулер все написал, но я взял на себя смелость его дополнить. Я прав? Или ошибаюсь? Хотелось бы обсудить с сообществом.
Re: Как определить где размещать бизнес-логику
От: scf  
Дата: 30.04.21 11:07
Оценка:
Здравствуйте, Nikita Lyapin, Вы писали:

NL>По большому счету Фаулер все написал, но я взял на себя смелость его дополнить. Я прав? Или ошибаюсь? Хотелось бы обсудить с сообществом.


Похоже, надо перечитать книгу. Совершенно непонятно, что мешает использовать transaction script с domain model, стараясь по возможности совмещать модельные классы с table module.

Про стоимость реализации тоже сомнительно — большие системы тяготеют скорее к процедурному подходу, чем к ООП, даже если состоят из объектов.
Отредактировано 30.04.2021 11:08 scf . Предыдущая версия .
Re[2]: Как определить где размещать бизнес-логику
От: Nikita Lyapin Россия https://architecture-cleaning.ru/
Дата: 30.04.21 14:57
Оценка:
Здравствуйте, scf, Вы писали:

scf>Здравствуйте, Nikita Lyapin, Вы писали:


NL>>По большому счету Фаулер все написал, но я взял на себя смелость его дополнить. Я прав? Или ошибаюсь? Хотелось бы обсудить с сообществом.


scf>Похоже, надо перечитать книгу. Совершенно непонятно, что мешает использовать transaction script с domain model, стараясь по возможности совмещать модельные классы с table module.


Вопрос скорее в соотношении. На практике — да, часто приходится для перфоманса делать что-то на чистом SQL. Но это даже не 15% от общего числа кода.

scf>Про стоимость реализации тоже сомнительно — большие системы тяготеют скорее к процедурному подходу, чем к ООП, даже если состоят из объектов.

Но вроде бы ООП со сложностью борется лучше процедурного подхода. Нет? Про функциональный подход, конечно помню. Но мы ведь о процедурном говорим? Если да, то не соглашусь... Но свою позицию не навязываю.
Re: Как определить где размещать бизнес-логику
От: varenikAA  
Дата: 01.05.21 09:42
Оценка:
Здравствуйте, Nikita Lyapin, Вы писали:

NL>Привет всем!


NL>Часто возникают обсуждения о том где размещать бизнес-логику приложения. В итоге я собрался силами, систематизировал свои знания по этому вопрос и написал статью. Здесь

NL>По большому счету Фаулер все написал, но я взял на себя смелость его дополнить. Я прав? Или ошибаюсь? Хотелось бы обсудить с сообществом.

Бизнес-логика это своего рода домен-специфичный калькулятор, поэтому где размещать тут все просто — в отдельном модуле.
Быть может все определяют конкретные условия в которых рождается и развивается проект?
Вот например, для чего вводят интерфейс сервиса?
Для возможнсти тестирования/нескольких реализаций/разделения функционала.
Тестирование предполагает наличие для этого времени.
Несколько реализаций быть может, но редко, чаще используется базовый класс а все наследники реализуют одну логику, особенно если это бизнес-логика.
Разделить функционал имеет смысл только при разделении компонентов. В монолитном приложении смысла в создании query-command разделения нет.
Опять же это возможно и оправдано при наличии времени и людей. Если проект разрабатывается в одного то не вижу смысла.
Другой аспект это возможности ЯП по созданию нормальной архитектуры.
Позволю встречный вопрос, как реализовать обработку ошибок try|catch. Пихать в каждый метод. Реализовать прокси. Использовать Функцию типа TryWithDo(() => ok(),() => fail(), () => fin())?
В каких ЯП какие есть средства/методы очистить бизнес-логику от инфрастуктурного кода?
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re: Как определить где размещать бизнес-логику
От: Sinclair Россия https://github.com/evilguest/
Дата: 05.06.21 02:23
Оценка: +1
Здравствуйте, Nikita Lyapin, Вы писали:

NL>По большому счету Фаулер все написал, но я взял на себя смелость его дополнить. Я прав? Или ошибаюсь? Хотелось бы обсудить с сообществом.

Фаулер устарел на 20 лет. Прямо в этом же форуме уже были подробные обсуждения его заблуждений. Коротко о главном:
1. Transaction Script у Фаулера почему-то прибит гвоздями к процедурному подходу. Даже двадцать лет назад ничто не мешало порождать скрипт объектно-ориентированным способом.
Это сразу же убирает обозначенные Фаулером недостатки.
2. В современном .Net есть нормальные ORM, которые позволяют вообще не считать код по обмену данными между базой и приложением частью приложения. В Java тоже всё гораздо лучше, чем во времена Фаулера, хотя и отстаёт на одно-два поколения от linq2db.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Как определить где размещать бизнес-логику
От: Nikita Lyapin Россия https://architecture-cleaning.ru/
Дата: 09.06.21 10:56
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Nikita Lyapin, Вы писали:


NL>>По большому счету Фаулер все написал, но я взял на себя смелость его дополнить. Я прав? Или ошибаюсь? Хотелось бы обсудить с сообществом.

S>Фаулер устарел на 20 лет. Прямо в этом же форуме уже были подробные обсуждения его заблуждений. Коротко о главном:
S>1. Transaction Script у Фаулера почему-то прибит гвоздями к процедурному подходу. Даже двадцать лет назад ничто не мешало порождать скрипт объектно-ориентированным способом.
S>Это сразу же убирает обозначенные Фаулером недостатки.

Парадигмы не устаревают. Вы можете использовать все самые новомодные библиотеки, но при этом хорошо бы озадачиться вопросом в какой парадигме вы пишете. Если вы пишите на ООП языке вроде C# и при этом у вас везде static методы да и только — у вас не используется парадигма ООП. Дело в том, что фреймворки и ОРМ по своей природе накладывают ограничения на то как их можно использовать.

S>2. В современном .Net есть нормальные ORM, которые позволяют вообще не считать код по обмену данными между базой и приложением частью приложения. В Java тоже всё гораздо лучше, чем во времена Фаулера, хотя и отстаёт на одно-два поколения от linq2db.

Linq2Db занял свою нишу. Это типизированный SQL по факту. Рассуждать о том, что хранимые процедуры больше относятся к ООП, чем к процедурам. Ну странно как-то... Ведь разница между хранимой процедурой и кодом на Linq2Db в сущности только в типизации и все...

В идеале для реализации ООП подхода в полной мере нужно получить набор доменных обьектов с методами и все. Т.е. бизнес логика должна быть выражена на чистом языке программирования без примесей технических деталий. Бизнес правила и так слишком сложны. Код больше читают, чем пишут.
Re[3]: Как определить где размещать бизнес-логику
От: Sinclair Россия https://github.com/evilguest/
Дата: 09.06.21 12:00
Оценка: +2
Здравствуйте, Nikita Lyapin, Вы писали:

NL>Парадигмы не устаревают. Вы можете использовать все самые новомодные библиотеки, но при этом хорошо бы озадачиться вопросом в какой парадигме вы пишете. Если вы пишите на ООП языке вроде C# и при этом у вас везде static методы да и только — у вас не используется парадигма ООП. Дело в том, что фреймворки и ОРМ по своей природе накладывают ограничения на то как их можно использовать.

Нет, дело в том, что Фаулер устарел. Вопросом о "парадигме" озадачиваться не надо. "Парадигмы" — это для слабых духом, кто пока неспособен самостоятельно отличить хороший код от плохого.
ООП само по себе не хорошо и не плохо, как и ФП, и процедурное программирование. Хорошим или плохим является конкретный код.
Например,фреймворк, который позволяет уменьшить количество кода, которое нужно написать программисту — это хороший фреймворк.

Ещё раз попробую объяснить сложную вещь: transaction script совершенно не обязан быть прибит гвоздями к процедурному подходу.
Transaction script характерен тем, что в нём программа (или сервер приложений, если мы говорим о трёхзвенке) порождают набор инструкций для DBMS.
Фаулеру просто не пришло в голову, что код, порождающий этот набор инструкций, вовсе не обязан быть в процедурах (или в static методах).
Я могу порождать его вполне себе ООП-кодом. Например, использую паттерн Стратегия для того, чтобы добавить код расчёта скидок в мой код обработки заказа.
При этом и тот и другой код не будет собственно ничего считать. Просто в батч будут добавляться различные SQL-стейтменты.
И это — гораздо более рациональный способ использования ООП для работы с данными, чем материализация "заказов", "позиций заказов", и "правил начисления скидок" в виде настоящих объектов, а затем мучительное сохранение их обратно в базу.

NL>Linq2Db занял свою нишу. Это типизированный SQL по факту. Рассуждать о том, что хранимые процедуры больше относятся к ООП, чем к процедурам. Ну странно как-то... Ведь разница между хранимой процедурой и кодом на Linq2Db в сущности только в типизации и все...

Вы слишком узко трактуете Фаулера, понимая под термином "процедура" только "хранимая процедура". На всякий случай напомню, что процедурное программирование придумали лет за двадцать до того, как изобрести реляционную алгебру, и за тридцать до идеи хранимых процедур.

NL>В идеале для реализации ООП подхода в полной мере нужно получить набор доменных обьектов с методами и все. Т.е. бизнес логика должна быть выражена на чистом языке программирования без примесей технических деталий. Бизнес правила и так слишком сложны. Код больше читают, чем пишут.

Нет. У вас неверное представление об идеале. В нормальном решении задачи автоматизации никаких доменных объектов с методами нет. Есть реляционная модель, описывающая факты, и бизнес-модель, описывающая правила трансформации фактов.
Наиболее естественный способ описывать трансформации фактов — язык SQL. В нём можно напрямую записать сложные вещи типа "после проведения приходной накладной количества складских позиций увеличиваются на количества соответствующих товарных позиций в накладной", не опускаясь на уровень циклов и ветвлений.

А вот порождать этот SQL нужно объектно-ориентированным способом, т.к. сам SQL к декомпозиции приспособлен очень плохо. Он спроектирован для одноразовых запросов, которые в командной строке набирает оператор.
linq2db — гораздо больше, чем "типизированный SQL", хотя и этого уже очень много — в большинстве сред нет и его.
То, что он умеет делать — как раз декомпозировать порождение SQL.
Без помощи linq2db невозможно сделать ничего похожего на
IQueryable<T> CheckPermissions<T>(IQueryable<T> items, User user) 
  where T: ISecurable => 
    from i in items 
    from p in db.Permissions.Where(pr=>pr.ObjectId == i.Id && pr.UserId == user.Id)
     select i;

Это позволяет не заниматься написанием однообразных процедур (хранимых или традиционных) для каждого из десятка-двух видов таблиц. При этом — 100% контроль компилятора за порождением кода. И можно централизованным образом менять правила Row-Level Security при необходимости:
IQueryable<T> CheckPermissions<T>(IQueryable<T> items, User user) 
  where T: ISecurable => 
    user.IsInRole(KnownRoles.SuperAdmin) 
    ? items
    : from i in items 
      from p in db.Permissions.Where(pr=>pr.ObjectId == i.Id && pr.UserId == user.Id)
       select i;

Подобный подход позволяет бить логику на кусочки — предположим, CheckPermissions() у меня входит в некоторый интерфейс IAccessPolicy. Я параметризую свой код либо типом, который реализует этот интерфейс, либо экземпляром этого типа:
public Vehicle ReserveTransportation(User requestor, int numberOfPersons, IAccessPolicy accessPolicy)
{
   var vehicles = accessPolicy.CheckPermissions(db.CompanyVehicles, requestor);
   vehicles = from v in vehicles where v.Capacity >= numberOfPersons && v.IsAvailable orderby v.Cost;
   return vehicles.First(); 
}

Вот вам пример transaction script, который построен по принципу ООП. На каждый реквест, который выставлен наружу, есть отдельная "процедура". Но внутри эта процедура собирает конкретную логику из многих кусочков. Её порождает некоторый объект какого-то класса, который пользуется всеми методами ООП и паттернами проектирования — наследование, перегрузки, шаблонный метод, стратегия, фасад, you name it.
Ничего подобного Фаулер придумать не смог; в основном потому, что в его время об этом мало кто задумывался. Ну, и потому, что реализация некоторых аспектов такого подхода на доисторических языках требует нечеловеческих усилий.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: Как определить где размещать бизнес-логику
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 09.06.21 14:01
Оценка: +1
Здравствуйте, Nikita Lyapin, Вы писали:

NL>В идеале для реализации ООП подхода в полной мере нужно получить набор доменных обьектов с методами и все.

Почему существует таое мнение, что ООП = доменная модель, а отсутстие доменной модели это не ООП.

Вся разница заключается в том, какой вариант выбрать:
order.Process()

или
orderProcessor.Process(order)


При этом ВСЕДА внтури первого варианта вызывается второй.

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

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

На каком языке это правило выразить проще всего? Внезапно SQL. То же самое правило, записанное на C#, займет гораздо больше строк и гораздо больше технческих деталей будет содержать.


NL>Бизнес правила и так слишком сложны. Код больше читают, чем пишут.

Многие "правила" являются не инвариантами, а пред- или пост- условиями для опредеенных тразакций. В сложных случаях доходит до того, что у разных транзакций предусловия будут противоречить друг другу и виде "бизнес-правил модели" их не запишешь. Поэтому чем сложнее система, тем лучше подойдет архитектура transaction script, безотносительно того какими средствами генерируются запросы к базе данных.
Re[4]: Как определить где размещать бизнес-логику
От: Nikita Lyapin Россия https://architecture-cleaning.ru/
Дата: 09.06.21 18:25
Оценка:
Здравствуйте, Sinclair, Вы писали:

NL>>В идеале для реализации ООП подхода в полной мере нужно получить набор доменных обьектов с методами и все. Т.е. бизнес логика должна быть выражена на чистом языке программирования без примесей технических деталий. Бизнес правила и так слишком сложны. Код больше читают, чем пишут.

S>Нет. У вас неверное представление об идеале. В нормальном решении задачи автоматизации никаких доменных объектов с методами нет. Есть реляционная модель, описывающая факты, и бизнес-модель, описывающая правила трансформации фактов.
S>Наиболее естественный способ описывать трансформации фактов — язык SQL. В нём можно напрямую записать сложные вещи типа "после проведения приходной накладной количества складских позиций увеличиваются на количества соответствующих товарных позиций в накладной", не опускаясь на уровень циклов и ветвлений.

S>А вот порождать этот SQL нужно объектно-ориентированным способом, т.к. сам SQL к декомпозиции приспособлен очень плохо. Он спроектирован для одноразовых запросов, которые в командной строке набирает оператор.

S>linq2db — гораздо больше, чем "типизированный SQL", хотя и этого уже очень много — в большинстве сред нет и его.
S>То, что он умеет делать — как раз декомпозировать порождение SQL.
S>Без помощи linq2db невозможно сделать ничего похожего на
S>
S>IQueryable<T> CheckPermissions<T>(IQueryable<T> items, User user) 
S>  where T: ISecurable => 
S>    from i in items 
S>    from p in db.Permissions.Where(pr=>pr.ObjectId == i.Id && pr.UserId == user.Id)
S>     select i;
S>

S>Это позволяет не заниматься написанием однообразных процедур (хранимых или традиционных) для каждого из десятка-двух видов таблиц. При этом — 100% контроль компилятора за порождением кода. И можно централизованным образом менять правила Row-Level Security при необходимости:
S>
S>IQueryable<T> CheckPermissions<T>(IQueryable<T> items, User user) 
S>  where T: ISecurable => 
S>    user.IsInRole(KnownRoles.SuperAdmin) 
S>    ? items
S>    : from i in items 
S>      from p in db.Permissions.Where(pr=>pr.ObjectId == i.Id && pr.UserId == user.Id)
S>       select i;
S>

S>Подобный подход позволяет бить логику на кусочки — предположим, CheckPermissions() у меня входит в некоторый интерфейс IAccessPolicy. Я параметризую свой код либо типом, который реализует этот интерфейс, либо экземпляром этого типа:
S>
S>public Vehicle ReserveTransportation(User requestor, int numberOfPersons, IAccessPolicy accessPolicy)
S>{
S>   var vehicles = accessPolicy.CheckPermissions(db.CompanyVehicles, requestor);
S>   vehicles = from v in vehicles where v.Capacity >= numberOfPersons && v.IsAvailable orderby v.Cost;
S>   return vehicles.First(); 
S>}
S>

S>Вот вам пример transaction script, который построен по принципу ООП. На каждый реквест, который выставлен наружу, есть отдельная "процедура". Но внутри эта процедура собирает конкретную логику из многих кусочков. Её порождает некоторый объект какого-то класса, который пользуется всеми методами ООП и паттернами проектирования — наследование, перегрузки, шаблонный метод, стратегия, фасад, you name it.
S>Ничего подобного Фаулер придумать не смог; в основном потому, что в его время об этом мало кто задумывался. Ну, и потому, что реализация некоторых аспектов такого подхода на доисторических языках требует нечеловеческих усилий.

Ничего нового. Такой подход мне знаком и я бы не сказал, что он меня как-то впечатляет. И да, он все таки больше процедурный. Вы попытались реализовать логику формирования запросов с помощью ООП. Но сама бизнес-логика и сами бизнес правила явно не выделены.
1. Признак это переменная db. Это что? Статическая переменная? Ну или контекст? — сути не меняет — привет глобальное состояние.
2. Все это больше похоже на попытку мыслить в функциональном стиле. У вас есть функции и есть данные, далее поверх этих данных вы пытаетесь навесить поведение. Но опять же делаете это не явно и у вас функции с состоянием. Тогда уж лучше разнести. Например, CheckPermissions — у вас items принимается как параметрю. А вот Permissions нет.
3. На самом деле большую опасность здесь несут неявные связи. SQL может в мгновение ока построить вам связь между сущностями. Это как спор между языками с статической типизацией и динамической. Вот вы говорите, что теперь у пользователей есть права и они мапятся по Id для всех у кого такой-то признак. И бац — оформили это в вашем запросе как джойн. В случае с мапингом на доменные сущности вам нужно заморочиться. Обьявить коллекцию, заполнить ее данными и т.п. — дофига кода. Это и плюс и минус одновременно. Если мы работаем с несложной бизнес-логикой где пару тройку доменных сущностей. Ну 10, 15. Данное свойство SQL и как следствие LINQ2DB не оказывает влияния. С другой стороны мапинг на доменные сущности дает лишь оверхэд. Но в сложных доменах обьявление класса уже является и документацией. По этому коду можно понимать модель. В SQL нужно прошерстить и держать в голове все запросы и какие они связи образуют.
4. По тому коду, что вы привели — крайне сложно отслеживать зависимости между сущностями. Вот CheckPermissions я вижу использует items и Users. Но то что он использует Permissions можно догадаться только из названия. А если я добавляю еще одну зависимость мне что метод переименовывать? Хочется верить, что нет.
5. Тесты вы как пишите? Они у вас быстрые? Или тесты для слабаков?
Re[4]: Как определить где размещать бизнес-логику
От: Nikita Lyapin Россия https://architecture-cleaning.ru/
Дата: 09.06.21 18:40
Оценка:
Здравствуйте, gandjustas, Вы писали:


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

G>Меня всегда умиляла наивность подобных утверждений.
G>Вот есть простое правило — нельзя продать два билета на одно место. Мы делаем систему для продажи билетов в театр в кассах по всему городу.

G>На каком языке это правило выразить проще всего? Внезапно SQL. То же самое правило, записанное на C#, займет гораздо больше строк и гораздо больше технческих деталей будет содержать.


Верно. Но это же не значит, что остальные 99,5 приложения нужно тоже на SQL писать.
Re: Как определить где размещать бизнес-логику
От: IT Россия linq2db.com
Дата: 09.06.21 22:26
Оценка:
Здравствуйте, Nikita Lyapin, Вы писали:

NL>Часто возникают обсуждения о том где размещать бизнес-логику приложения. В итоге я собрался силами, систематизировал свои знания по этому вопрос и написал статью. Здесь


ООП само по себе уже есть добавление сложности в любую логику. Поэтому переход на ООП при усложнении бизнес-логики — это так себе решение. В некоторых случаях может помочь, но скорее всего станет только хуже.
Если нам не помогут, то мы тоже никого не пощадим.
Отредактировано 10.06.2021 1:01 IT . Предыдущая версия .
Re[5]: Как определить где размещать бизнес-логику
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.06.21 07:21
Оценка:
Здравствуйте, Nikita Lyapin, Вы писали:

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

Как это не выделена? Вполне себе выделена. В совершенно явном и понятном виде: вот есть правило про проверку разрешений, вот реализация этого правила.
Некуда делать это более ООПшным.

NL>1. Признак это переменная db. Это что? Статическая переменная? Ну или контекст? — сути не меняет — привет глобальное состояние.

В данном примере это статическая переменная. В этом нет никакой проблемы, т.к. у этой "переменной" нет состояния; но если по-вашему тут недостаточно паттернов, то можете сделать эту переменную параметром.

NL>2. Все это больше похоже на попытку мыслить в функциональном стиле. У вас есть функции и есть данные, далее поверх этих данных вы пытаетесь навесить поведение. Но опять же делаете это не явно и у вас функции с состоянием. Тогда уж лучше разнести. Например, CheckPermissions — у вас items принимается как параметрю. А вот Permissions нет.

NL>3. На самом деле большую опасность здесь несут неявные связи. SQL может в мгновение ока построить вам связь между сущностями. Это как спор между языками с статической типизацией и динамической. Вот вы говорите, что теперь у пользователей есть права и они мапятся по Id для всех у кого такой-то признак. И бац — оформили это в вашем запросе как джойн. В случае с мапингом на доменные сущности вам нужно заморочиться. Обьявить коллекцию, заполнить ее данными и т.п. — дофига кода. Это и плюс и минус одновременно.
Нет, это чистый минус.
NL>Если мы работаем с несложной бизнес-логикой где пару тройку доменных сущностей. Ну 10, 15. Данное свойство SQL и как следствие LINQ2DB не оказывает влияния.
Оказывает. Объём кода — это прямые расходы на разработку, тестирование, и поддержку. Даже если у вас доменных сущностей две. Просто вам кажется, что написать лишние 200 строк кода — это малозначимая фигня, а вот если бы их было 200 тысяч, тогда решение становится важным. На практике вы не пишете сразу 200000 строк, вы получаете их в результате тысячи доработок, в каждой из которых дописывается по 200 строк.

NL>С другой стороны мапинг на доменные сущности дает лишь оверхэд. Но в сложных доменах обьявление класса уже является и документацией.

Всё верно. Вот мы объявили Vehicle: ISecurable и сразу этим показали, что права доступа к нему контролируются через таблицу Permissions.
NL>По этому коду можно понимать модель. В SQL нужно прошерстить и держать в голове все запросы и какие они связи образуют.
И опять это зависит от того, как вы пишете этот SQL. В чистом SQL, написанном в процедурном стиле — именно так. Понять, нужно ли контролировать доступ к какой-либо сущности по таблице разрешений, можно только путём вдумчивого копания в текстах параметризованных view и хранимых процедур.
В рамках полноценного фреймворка вроде Linq вы видите эти связи прямо на уровне деклараций.

NL>4. По тому коду, что вы привели — крайне сложно отслеживать зависимости между сущностями. Вот CheckPermissions я вижу использует items и Users. Но то что он использует Permissions можно догадаться только из названия. А если я добавляю еще одну зависимость мне что метод переименовывать? Хочется верить, что нет.

Что здесь вы называете "сущностями"? То, что хранится в базе? Или те объекты, которые реализуют бизнес-логику?
В нашем примере есть некоторая реализация интерфейса IAccessPolicy (которого мы выделяем только если это вообще нужно — в простых моделях такой нужды нет, и можно просто реализовывать CheckPermissions как метод-расширение к DatabaseContext). Напимер, ACLBasedAccessPolicy. У неё будет вполне явная зависимость от IQueryable<Permission> Permissions.
У какой-то другой реализации IAccessPolicy такой зависимости может и не быть — это не свойство модели данных, а свойство бизнес-логики. Которая сегодня — одна, а завтра — совсем другая (например, заменяем ACL на RBAC).

NL>5. Тесты вы как пишите? Они у вас быстрые? Или тесты для слабаков? \

А что именно вы хотите тестировать? Видите ли, когда вы врукопашную велосипедите стандартный набор репозиториев и прочего хлама, вам придётся тестировать код репозиториев и прочего хлама.
Когда вы пишете код в указанном стиле, вам не нужно тестировать linq2db — его тестируют другие люди. Вам нужно только протестировать, что результатом конкретной реализации IAccessPolicy.CheckPermissions является наложение требуемого предиката.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: Как определить где размещать бизнес-логику
От: Nikita Lyapin Россия https://architecture-cleaning.ru/
Дата: 10.06.21 07:58
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>А что именно вы хотите тестировать? Видите ли, когда вы врукопашную велосипедите стандартный набор репозиториев и прочего хлама, вам придётся тестировать код репозиториев и прочего хлама.

S>Когда вы пишете код в указанном стиле, вам не нужно тестировать linq2db — его тестируют другие люди. Вам нужно только протестировать, что результатом конкретной реализации IAccessPolicy.CheckPermissions является наложение требуемого предиката.
Странный вопрос. Даже очень. Тестировать я хочу бизнес-правила. Например, что при задании определенных пермишенов мне только заданные items отоброжаются. И да, Linq2Db мне тестировать ну совсем не хочется. И как это реализовать в вашем примере без теста Linq2Db? С статическими то переменными в коде... выкрутиться можно. Но будет такое себе зрелище. Просто интересно как вы это решаете.
Re[7]: Как определить где размещать бизнес-логику
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.06.21 08:25
Оценка:
Здравствуйте, Nikita Lyapin, Вы писали:
NL>Странный вопрос. Даже очень. Тестировать я хочу бизнес-правила. Например, что при задании определенных пермишенов мне только заданные items отоброжаются.
Ничего странного. Вопрос "а что именно тестируется" очень важен. К примеру, я не думаю, что вы в вашем коде проверяете, что компилятор, стандартная библиотека, и процессор корректно выполняют, скажем, умножение.
Если мы говорим о юнит-тестах, то они тестируют именно конкретный юнит.
Когда вы тестируете SQL, вряд ли вы тестируете, что в результат join ... on ... попадают только те строчки, которые попадают под предикат. Эту работу уже выполнила команда QA-инженеров соответствующей СУБД.

Так и тут — у вас есть некоторые обязанности кода, их вы и проверяете.

NL>И да, Linq2Db мне тестировать ну совсем не хочется. И как это реализовать в вашем примере без теста Linq2Db? С статическими то переменными в коде... выкрутиться можно. Но будет такое себе зрелище. Просто интересно как вы это решаете.


Очевидным способом — проверяем, что при передаче обычного пользователя возвращается результат Join, а при передаче суперпользователя — тот же экземпляр IQueryable.
Можно проверить текст результирующего SQL, но это контрпродуктивно в случае поддержки разных SQL-диалектов.

Интеграционные тесты, как и везде, будут относительно медленными, и ничем не будут отличаться от интеграционных тестов любого Enterprise-приложения.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[5]: Как определить где размещать бизнес-логику
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.06.21 09:08
Оценка:
Здравствуйте, Nikita Lyapin, Вы писали:

NL>Здравствуйте, gandjustas, Вы писали:



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

G>>Меня всегда умиляла наивность подобных утверждений.
G>>Вот есть простое правило — нельзя продать два билета на одно место. Мы делаем систему для продажи билетов в театр в кассах по всему городу.

G>>На каком языке это правило выразить проще всего? Внезапно SQL. То же самое правило, записанное на C#, займет гораздо больше строк и гораздо больше технческих деталей будет содержать.


NL>Верно. Но это же не значит, что остальные 99,5 приложения нужно тоже на SQL писать.


Если мы повторим упражнение для других "бизнес-правил", то внезапно окажется что 99,5% из них проще выразить в SQL, чем в C#.
Например для той же системы продажи билетов: нельзя продать билет на место, если соседнее было куплено (антиковидное ограничение).
В SQL это элементарно: в выборку свободных мест доставляется условие, филтрующее места с номерами +\-1 от купленного. А в "доменной модели" как будет?
Re[7]: Как определить где размещать бизнес-логику
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 10.06.21 09:11
Оценка:
Здравствуйте, Nikita Lyapin, Вы писали:

NL>Странный вопрос. Даже очень. Тестировать я хочу бизнес-правила. Например, что при задании определенных пермишенов мне только заданные items отоброжаются. И да, Linq2Db мне тестировать ну совсем не хочется. И как это реализовать в вашем примере без теста Linq2Db? С статическими то переменными в коде... выкрутиться можно. Но будет такое себе зрелище. Просто интересно как вы это решаете.


Я не знаю как там у linq2db, но у EF вполне можно инмемори контекст для тестов сделать. И в конце концов что мешает тесты запускать на базе sqlite например?
Re[8]: Как определить где размещать бизнес-логику
От: Nikita Lyapin Россия https://architecture-cleaning.ru/
Дата: 10.06.21 09:20
Оценка: +1 -1
Да, это все очень важно. Меня лишь удивило отсутсвие понимание подтекста вопроса о тестах. Ну или не желание на него отвечать прямо в виду невыгодности позиции.
Но давайте подытожим, что я вижу:
1. Тестов либо нет, либо они делаются QA ручками без всяких CI CD. Либо они интеграционные и медленные. И доля интеграционных тестов — большинство. Все это работает и так делать можно. Но если проект у вас не крупный. И даже не средний. Если тесты выполняются руками, то в крупном проекте регрес будет проходить за неделю. Правите в одном месте — опять полный регрес. Если интеграционные тесты — в крупном проекте их тысячи. Должны быть быстрыми для комфортной разработки.
2. Обьем кода от ООП (ОРМ и обвесов) не растет линейно. Т.е. да, вы в начале добавляете 200 строк кода. Но по мере роста вы не добавляете по 200 строк на каждое бизнес-правило. Наоборот. Дополнительный оверхед достигается за счет мапинга, сервисов и репозиториев. Далее вы уже используете готовую инфраструктуру. Отсюда вывод — для маленького проекта не имеет смысла. Дла большого — имеет.
3. Все такпи статичекие переменные — это как раз признак процедурности. Ну или в любом случае затрудненной сопровождаемости кода. Командная работа, поять же сложный и комплексный домен, который невозомжно уместить в голове — все это сразу мимо. И опять — для маленького проекта все окей.
Итого, стиль который вы описали отлично подходит для небольшого по обьему проекта. Со сложным — будут проблемы. Единственное, что тут нужно учесть — это микросервисы. С их приходом каждый конкретный микросервис в целом простой. И там вполне можно так писать. И я бы даже сказал, что иногда нужно так и только так.
Вот такая у меня позиция.
Re[8]: Как определить где размещать бизнес-логику
От: Nikita Lyapin Россия https://architecture-cleaning.ru/
Дата: 10.06.21 09:23
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Здравствуйте, Nikita Lyapin, Вы писали:


NL>>Странный вопрос. Даже очень. Тестировать я хочу бизнес-правила. Например, что при задании определенных пермишенов мне только заданные items отоброжаются. И да, Linq2Db мне тестировать ну совсем не хочется. И как это реализовать в вашем примере без теста Linq2Db? С статическими то переменными в коде... выкрутиться можно. Но будет такое себе зрелище. Просто интересно как вы это решаете.


G>Я не знаю как там у linq2db, но у EF вполне можно инмемори контекст для тестов сделать. И в конце концов что мешает тесты запускать на базе sqlite например?


Решение с sqllite — костыль. Давайте себе честно в этом признаемся. Разница с постгресом будет где-то в два раза быстрее. Настоящие true unit тесты будут в тысячи раз быстрее. Тут я оперирую конкретным
опытом рефакторинга одного проекта, где мне довелось через все этим стадии пройти.
Ну вот вы пишите с EF — да уже лучше. Немно сдвинули нашу кривую затратности (см график из статьи). Но опять же не всегда имеет смысл. Для простейшего проекта где две таблицы и все — достаточно дапера. Дазем все уложнять. И интеграционные тесты там вполне могут жить, потому что их не много и написать их легко.
Re[9]: Как определить где размещать бизнес-логику
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.06.21 11:08
Оценка:
Здравствуйте, Nikita Lyapin, Вы писали:

NL>1. Тестов либо нет, либо они делаются QA ручками без всяких CI CD. Либо они интеграционные и медленные. И доля интеграционных тестов — большинство. Все это работает и так делать можно. Но если проект у вас не крупный. И даже не средний. Если тесты выполняются руками, то в крупном проекте регрес будет проходить за неделю. Правите в одном месте — опять полный регрес. Если интеграционные тесты — в крупном проекте их тысячи. Должны быть быстрыми для комфортной разработки.

NL>2. Обьем кода от ООП (ОРМ и обвесов) не растет линейно. Т.е. да, вы в начале добавляете 200 строк кода. Но по мере роста вы не добавляете по 200 строк на каждое бизнес-правило. Наоборот. Дополнительный оверхед достигается за счет мапинга, сервисов и репозиториев. Далее вы уже используете готовую инфраструктуру. Отсюда вывод — для маленького проекта не имеет смысла. Дла большого — имеет.
NL>3. Все такпи статичекие переменные — это как раз признак процедурности. Ну или в любом случае затрудненной сопровождаемости кода. Командная работа, поять же сложный и комплексный домен, который невозомжно уместить в голове — все это сразу мимо. И опять — для маленького проекта все окей.
NL>Итого, стиль который вы описали отлично подходит для небольшого по обьему проекта. Со сложным — будут проблемы. Единственное, что тут нужно учесть — это микросервисы. С их приходом каждый конкретный микросервис в целом простой. И там вполне можно так писать. И я бы даже сказал, что иногда нужно так и только так.
NL>Вот такая у меня позиция.
Давайте так — покажите код, который реализует вот это простое бизнес-правило "сотруднику предоставляется самый дешёвый автомобиль среди тех, которые ему можно использовать".
С тестами.
Может быть, действительно, linq будет плохим ответом на подобную задачу.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[10]: Как определить где размещать бизнес-логику
От: Nikita Lyapin Россия https://architecture-cleaning.ru/
Дата: 10.06.21 11:41
Оценка: :)
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Nikita Lyapin, Вы писали:


S>Давайте так — покажите код, который реализует вот это простое бизнес-правило "сотруднику предоставляется самый дешёвый автомобиль среди тех, которые ему можно использовать".

S>С тестами.
S>Может быть, действительно, linq будет плохим ответом на подобную задачу.

Я могу вам порекомендовать курс
Там все подробно разбирается. Если у меня будет время — напишу вам пример, который вы хотите. Но чуть позже. Пока времени нет.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.