Здравствуйте, 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.
Ничего подобного Фаулер придумать не смог; в основном потому, что в его время об этом мало кто задумывался. Ну, и потому, что реализация некоторых аспектов такого подхода на доисторических языках требует нечеловеческих усилий.