Здравствуйте, 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. Тесты вы как пишите? Они у вас быстрые? Или тесты для слабаков?