Русский
Русский
English
Статистика
Реклама

Aop

Ты решил написать свой фреймворк. Стоило оно того?

03.09.2020 16:16:34 | Автор: admin


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

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

Привет! Перед вами частичная расшифровка моего подкаста Между скобок интервью с интересными людьми из мира PHP. В этом выпуске поговорим с Александром Лисаченко, создателем фреймворка, основанного на идеях из Java, который часто гуглят гоферы.

Если вам удобней слушать в аудиоверсии есть больше технических примеров.

Скорее всего, не все из нас сталкивались с такой вещью, как АОП аспектно-ориентированное программирование в PHP. А Саша упоролся и сделал свое решение для втаскивания этой штуки в наши приложения.

Сергей Жук, Skyeng и DriftPHP: Давай начнём с азов что такое АОП и какую проблему оно решает?

Александр Лисаченко, PHP Russia и Go! AOP: Идея не нова. АОП придумали в Xerox, чтобы решить проблему сквозной функциональности. И в мире Java подход активно используется, чтобы проверять такие функциональности, как авторизация, аутентификация, кэширование, логирование, управление feature toggle-ами, circuit breaker-ами.

Очень большой спектр задач можно решить с помощью аспектов. Давай на примерах:

  • Мы хотим сделать транзакцию. Перед началом надо написать, что мы в неё входим. Если она завершилась успешно, написать, что всё хорошо. Иначе написать, что ошибка.
  • Другой пример кэширование. Мы хотим проверить, есть ли у нас что-то в кэше. Если нет, то прогреваем кэш в методе, сохраняем что-то в кэш и потом возвращаем значение уже из него.
  • Профилирование: зачастую непонятно, что происходит внутри каждого конкретного респонса, приложение на проде начало тупить. А включать Xdebug опасно или не дадут.
  • Еще одна хорошая и интересная возможность, которая реализуется с помощью аспектов это circuit breaker, когда нам нужно проверять, что метод не падает, а если падает, то быстро его выключить и перестать ожидать ответ.

Если мы посмотрим на приложение, то увидим, что подобные участки кода попадаются везде и с этим не очень удобно работать. Это так называемая сквозная функциональность она присутствует во всём приложении.

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


Да, конечно, мы можем попробовать взять декоратор но если мы хотим, чтобы наш класс имплементил тот же самый интерфейс, необходимо реализовать или сгенерировать каждый метод прямо в декораторе. То есть, иметь декораторы под все классы, которые мы хотим закэшировать.

Сергей Жук: Ок, понял. Но для этого уже были какие-то PECL-расширения, фреймворк даже был. Почему ты решил написать свой?

Александр Лисаченко: Да, ты правильно заметил, уже существовал ряд решений. Но, как мне показалось, они обладали значительными недостатками.

Часть решений пыталась трансформировать исходный код, сразу встроить в конкретный класс конкретный advice участок кода, который повторяется из одного метода в другой. С помощью xlt они генерируют один класс и кладут туда абсолютно всё. В общем, этот код лучше не открывать совсем)

Второй класс решений это экстеншены. Например, PHP AOP он, в принципе, довольно функционален, но делает всё в runtime. Когда мы добавляем один advice, вроде как всё хорошо, но добавляем второй и скорость начинает пропорционально уменьшаться. Соответственно, на десяти advice-ах приложение начинает подтормаживать, а если мы добавляем ещё пару десятков, всё гарантирован timeout.

Как-то так сложилось, что я увидел, как в фреймворке Lithium была реализована идея под названием фильтры такой прообраз современных middleware-ов. Мы создаём какие-то заранее известные точки в программе, и к этим точкам можно применять фильтры как до вызова, так и после. Эта идея показалась настолько интересной, что я решил написать приложение и вынести сквозную функциональность с помощью этих фильтров. Начал изучать, как это сделано в Java.

И понял, что в PHP это с наскоку не удастся реализовать. То был, наверное, самый интересный момент.


В целом, весь процесс написания фреймворка был постоянной борьбой с нельзя и невозможно. Казалось бы, я не мог реализовать фундаментальные вещи, но тем интереснее было с ними разобраться.

Сергей Жук: Я просто не могу не спросить. Почему Go AOP? Первый релиз фреймворка состоялся в начале 2013-го, язык Go тогда уже был, а у тебя PHP. Это чтобы было как с JavaScript и Java, да?)

Александр Лисаченко: Первый коммит не равняется началу локальной разработки на моём компе) В тот момент Go ещё не был популярен. Я погуглил, увидел, что есть какая-то внутренняя разработка Google, она занимает какую-то свою нишу И не стал заморачиваться.

Само название фреймворка я выбрал, исходя из внутреннего, так сказать, пожелания. От go иди, вперёд, делай.

Сергей Жук: А переименовать потом не было мыслей?

Александр Лисаченко: Формально, полное название фреймворка Go! AOPPHP. Но со временем я убрал PHP, потому что это ставится через Composer и, кажется, нет смысла делать масло масляным.

Пока я получаю дополнительный трафик: многие, кто пытается найти АОП для Go, попадают на мой фреймворк для PHP. Возможно, в какой-то будущей версии придется сделать акцент, что это не про Go. Пока issue на GitHub-е на этот счет мне никто не заводил. Никаких претензий от Google или сообщества тоже не было.

Сергей Жук: Ок, а как это реализовано с точки зрения клиентского кода? Вот у меня есть приложение, например, на Laravel-е, есть пара интеграций со сторонними сервисами, я хочу эти вызовы логировать.

Александр Лисаченко: Под Laravel уже есть специальный модуль. Он поставит тебе всё необходимое в систему, настроит. Тебе необходимо будет написать аспект это будет сервис, ты его протегируешь и он автоматически подхватится ядром АОП. В самом аспекте тебе необходимо понять, в каких точках приложения, в каких классах и методах ты хочешь реализовать функционал кэширования. Ты можешь указать метод прямо сигнатурой (но вариант не особо гибкий, потому метод можно переименовать, а в аспекте он останется), либо пометить метод аннотацией. Второй вариант более гибкий: там, где надо закэшировать, просто пометил cacheable, а движок за тебя сделает кэширование и вызовет callback, который находится в коде аспекта.

В момент загрузки твоего класса Composer-ом фреймворк вмешивается и проверяет, есть ли к этому классу готовая версия в кэше со встроенным аспектом. Если есть, он сразу её отдаёт, в runtime-е никаких проверок дополнительных не производится. Если же кэша нет, то мы построим AST-дерево, а поверх этого дерева создадим класс рефлекшена, но без его загрузки в память. И в этот момент мы сможем изменить его так, как хотим, то есть вплести код аспекта внутрь этого класса.

Я не люблю лапшекод внутри аспектов и решил делить класс на две части.


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

Почему, собственно, наследование. Есть очень много методов и классов, которые возвращают instance обратно. Например, известный всем chain-метод: мы вызываем цепочку методов у объекта, он возвращает $this. Если мы задекорируем, то первый вызов он отработает, но вот дальше аспект отвалится. Заодно с наследованием экономится память потому что instance в памяти по-прежнему один.

Сергей Жук: Вся эта архитектура, движок, ты с самого начала все продумал или?

Александр Лисаченко: Было много вещей, в которые пришлось погружаться. Например, я не был хорошо знаком с AST, поэтому изучил много дисциплин, связанных с описанием грамматик. И если посмотреть на мой фреймворк, то pointcut у меня реализует полноценную грамматику и имеет свой синтаксис это, наверное, одно из больших достижений. Можно написать сколько угодно сложные выражения, например, вызов любого публичного метода, не начинающегося с asset, имплементирует интерфейс такой-то внутри space-а такого-то.

Также я много копал внутрь PHP. Смотрел, где какие экстеншены, как они работают. Где-то сидел с профайлингом, что-то оптимизировал, тюнил: зато сейчас, если просто подключить АОП-фреймворк, приложению добавятся какие-то смешные 7-10 миллисекунд. На уровне классических 100 миллисекунд ответа даже незаметно, что вызывается подкапотно такой огромный фреймворк.

Сергей Жук: А есть специфика под разные PHP-фреймворки?

Александр Лисаченко: В принципе, АОП-фреймворк задумывался как общая библиотека, не требующая специфической обвязки. Основное условие использовать Composer. Но вот с Symfony оно не очень дружит.

В Symfony слишком много чёрной магии, и когда она схлестывается с магией в моём фреймворке, побеждает сильнейший Symfony.


В целом идея Symfony в том, что есть контейнер, надо им пользоваться и не изобретать отдельные фреймворки, чтобы получить функциональность АОП. Есть более традиционные способы: подключить бандл скажем, JMS AOP или мой Symfony Go AOP bundle.

Сергей Жук: Давай поговорим про сообщество и конкурентов. Есть ли они у тебя?

Александр Лисаченко: Фреймворков, насколько мне известно, сейчас три. Есть Ray. Aop, но он не пригодится для продакшена, потому что не умеет эффективно работать с Composer-ом. Авторы Flow подают свой фреймворк под соусом, что у нас тут есть АОП. Есть еще что-то ещё рядом с китайскими фреймворками, поверх Swoole есть обвязки, но это всё на уровне экстеншенов, а экстеншены могут админы по соображениям безопасности не пропустить. У меня всё-таки классический фреймворк, и он остаётся живым на любых версиях.

Что касается сообщества. Тех, кто разбирается и понимает хорошо, наверное, всего человека четыре: я, один парень из Сербии и два человека с моей бывшей работы, которые участвовали во всём том, что я делал. Я им показывал, естественно, все свои наработки, результаты. Последние пару месяцев, как я менял работу, вкладывал в open source очень мало сил и энергии, но оно живёт и работает автономно.

Возможно, кто-то знает мою другую библиотеку Z-Engine c ней мы можем иметь доступ ко всем структурам в памяти, классам, методам и всему что угодно в PHP.


Я планирую, как только освободится время, продолжить работу над Z-Engine и сделаю следующую версию фреймворка, базирующуюся уже на внутренних структурах самого языка. Оно будет работать практически как джавовский AspectJ. Цель прийти к этому к восьмой версии PHP.

Сергей Жук: Это же почти полностью переписывать фреймворк?

Александр Лисаченко: Нет, у меня всё декомпозировано. Изменяется только процесс внедрения кода: буквально несколько классов, которые отвечают за то, чтобы внести правки в конкретный класс. И в данном случае этот класс будет делать не файлики в кэше с разными структурами, а будет в этот момент в runtime-е изменять OPcache и модифицировать структуры PHP в памяти.

Сергей Жук: А какое вообще отношение PHP-сообщества к этой теме? Я вот увлекаюсь асинхронным PHP, он мало кого оставляет равнодушным. У тебя как с этим дела?

Александр Лисаченко: Всегда найдутся те, кто скажет, что мы можем это сделать и без АОП, зачем нам это в приложениях. Я отвечаю: если вы не работаете в энтерпрайзе, аспекты вам не пригодятся. И если вы считаете, что у вас энтерпрайз, но у вас один сервис и 2-3 разработчика, вам тоже такое не подойдёт) АОП хорошо работает в командах, где есть несколько десятков человек, каждый из них пишет в своём стиле, есть, как правило, несколько приложений, и, как правило, микросервисная архитектура.

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

Сергей Жук: А ты ведешь какую-то просветительскую деятельность? Фреймворк у тебя достаточно взрослый, но я бегло поискал туториалы негусто. Кажется, спроси у десяти человек, что такое АОП, девять скажут, что не знают.

Александр Лисаченко: Да, верно.

Сергей Жук: Хотя, может, хорошо, что не знают?

Александр Лисаченко: Это спорный вопрос) Но есть проблема, которая у меня была и есть это документация. Я по своей природе не люблю писать документацию. Могу написать крутые решения, могу изобретать какие-то необычные вещи, библиотеки, но с документацией это прямо боль.

Мне даже в один момент друг из Сербии предлагал: давай напишем. И даже начал её писать, но через некоторое время запал тоже закончился Так и получилось, что документацией не богаты, скажем так.

Сергей Жук: Получается, естественный отбор? Самые стойкие, кто залезли, поковырялись, те и юзают

Александр Лисаченко: Да, и это те люди, которые имеют достаточный уровень, чтобы не накосячить.

Сергей Жук: А ты получал какой-то фидбек от людей, которые использовали фреймворк так, как ты бы не догадался?

Александр Лисаченко: Да, были такие кейсы. Например, Михаил Боднарчук, который написал AspectMock. Он взял фреймворк и понял, что с его помощью можно решить проблему моканья финальных методов, классов и даже функций.

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

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


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

p.s. Спасибо, что дочитали и дослушали! Другие выпуски подкаста можно найти здесь.
Подробнее..

Сквозной функционал через обертки

16.07.2020 10:19:49 | Автор: admin
При разработке мы не редко сталкиваемся с ситуацией, когда при выполнении какой-либо бизнес-логики требуется записать логи, аудиты, разослать оповещения. В общем реализовать некоторый сквозной функционал.
Когда масштабы производства небольшие, можно особо не усердствовать и все это делать прямо в методах. Постепенно, конструктор сервиса начинает обрастать входящими сервисами для выполнения БЛ и сквозного функционала. А это уже ILogger, IAuditService, INotifiesSerice.
Не знаю как Вы, а я не люблю много инъекций и большие методы, которые выполняют много действий за раз.

Можно накрутить на код какую либо реализацию АОП. В стеке .NET такие реализации делают инъекции в ваше приложение в нужные места, внешне похожи на магию 80 уровня и, зачастую, имеют проблемы с типизацией и отладкой.

Я попытался найти золотую середину. Если данные проблемы не обошли вас стороной, добро пожаловать под кат.

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

И помогли мне в этом декораторы и надстройка над DI. Кто-то далее скажет, что это прокси, с радостью обсужу это в комментах.

Итак, что я хочу как разработчик?
  • При реализации БЛ не отвлекаться на левый функционал.
  • Иметь возможность в юнит тестах тестировать только БЛ. Причем я не люблю делать 100500 моков, чтобы отключить весь вспомогательный функционал. 2-3 еще ладно, но больше не хочу.
  • Понимать, что происходит, не имея 7 пядей во лбу. :)
  • Иметь возможность управлять временем жизни сервиса и каждой его обертки ОТДЕЛЬНО!


Что я хочу, как проектировщик и лидер команды?
  • Иметь возможность декомпозировать задачи наиболее оптимально и с наименьшей связностью, чтобы одновременно можно было задействовать как можно больше разработчиков на разные задачи и при этом чтобы они тратили как можно меньше времени на исследование (если разработчику надо разработать БЛ, а параллельно думать, что и как залогировать, он потратит больше времени на исследование. И так с каждым куском БЛ. Куда проще взяться за записи аудитов и распихать их по всему проекту).
  • Оправлять порядком выполнения кода отдельно от его разработки.


Поможет мне в этом вот такой интерфейс.
    /// <summary>    ///     Обертка для сервиса.    /// </summary>    /// <typeparam name="T"> Класс сервиса. </typeparam>    public interface IDecorator<T>    {        /// <summary>        ///     Делегат для работы декоратора.        /// </summary>        Func<T> NextDelegate { get; set; }    }

Можно использовать как то так
interface IService{    Response Method(Request request);}class Service : IService{    public Response Method(Request request)    {        // BL    }}class Wrapper : IDecorator<IService>, IService{    public Func<IService> NextDelegate { get; set; }    public Response Method(Request request)    {        // code before        var result = NextDelegate().Method(request);        // code after        return result;    }}


Таким образом, действие у нас будет уходить в глубину.
wrapper1
wrapper2
service
end wrapper2
end wrapper1


Но, постойте. Это же уже есть в ООП и называется наследование. :D

class Service {}class Wrapper1: Service {}class Wrapper2: Wrapper1 {}


Я как представил, что появится дополнительный сквозной функционал, который придется внедрять по всему приложению в середину или менять местами уже имеющиеся, так у меня на спине волосы дыбом встали.
Но моя лень это не уважительная причина. Уважительная причина в том, что будут большие проблемы при модульном тестировании функционала в классах Wrapper1 и Wrapper2, тогда как в моем примере NextDelegate можно просто замокать. Более того, у сервиса и каждой обертки свой собственный набор инструментов, которые инжектятся в конструктор, тогда как при наследовании последняя обертка обязана иметь ненужные инструменты, чтобы передать их родителям.

Итак, подход принят, осталось придумать, где, как и когда назначать NextDelegate.

Я решил, что самым логичным решением будет делать это там, где я регистрирую сервисы. (Startup.sc, по умолчанию).

Вот как это выглядит в базовом варианте
            services.AddScoped<Service>();            services.AddTransient<Wrapper1>();            services.AddSingleton<Wrapper2>();            services.AddSingleton<IService>(sp =>            {                var wrapper2 = sp.GetService<Wrapper2>();                wrapper2.NextDelegate = () =>                {                    var wrapper1 = sp.GetService<Wrapper1>();                    wrapper1.NextDelegate = () =>                    {                        return sp.GetService<Service>();                    };                    return wrapper1;                };                return wrapper2;            });


В целом, все требования выполнены, но появилась другая проблема вложенность.

Эту проблему можно решить перебором или рекурсией. Но под капотом. Внешне все должно выглядеть просто и понятно.

Вот чего мне удалось добиться
            services.AddDecoratedScoped<IService, Service>(builder =>            {                builder.AddSingletonDecorator<Wrapper1>();                builder.AddTransientDecorator<Wrapper2>();                builder.AddScopedDecorator<Wrapper3>();            });


А помогли мне в этом вот эти методы расширения

А помогли мне в этом вот эти методы расширения
    /// <summary>    ///     Методы расширения для декораторов.    /// </summary>    public static class DecorationExtensions    {        /// <summary>        ///     Метод регистрации декорируемого сервиса.        /// </summary>        /// <typeparam name="TDefinition"> Интерфейс сервиса. </typeparam>        /// <typeparam name="TImplementation"> Реализация сервиса. </typeparam>        /// <param name="lifeTime"></param>        /// <param name="serviceCollection"> Коллекция сервисов. </param>        /// <param name="decorationBuilder"> Построитель декораций. </param>        /// <returns> Коллекцию сервисов после регистрации декораторов. </returns>        public static IServiceCollection AddDecorated<TDefinition, TImplementation>(            this IServiceCollection serviceCollection, ServiceLifetime lifeTime,            Action<DecorationBuilder<TDefinition>> decorationBuilder)            where TImplementation : TDefinition        {            var builder = new DecorationBuilder<TDefinition>();            decorationBuilder(builder);            var types = builder.ServiceDescriptors.Select(k => k.ImplementationType).ToArray();            var serviceDescriptor = new ServiceDescriptor(typeof(TImplementation), typeof(TImplementation), lifeTime);            serviceCollection.Add(serviceDescriptor);            foreach (var descriptor in builder.ServiceDescriptors)            {                serviceCollection.Add(descriptor);            }            var resultDescriptor = new ServiceDescriptor(typeof(TDefinition),                ConstructServiceFactory<TDefinition>(typeof(TImplementation), types), ServiceLifetime.Transient);            serviceCollection.Add(resultDescriptor);            return serviceCollection;        }        /// <summary>        ///     Метод регистрации декорируемого сервиса с временем жизни Scoped.        /// </summary>        /// <typeparam name="TDefinition"> Интерфейс сервиса. </typeparam>        /// <typeparam name="TImplementation"> Реализация сервиса. </typeparam>        /// <param name="serviceCollection"> Коллекция сервисов. </param>        /// <param name="decorationBuilder"> Построитель декораций. </param>        /// <returns> Коллекцию сервисов после регистрации декораторов. </returns>        public static IServiceCollection AddDecoratedScoped<TDefinition, TImplementation>(            this IServiceCollection serviceCollection,            Action<DecorationBuilder<TDefinition>> decorationBuilder)            where TImplementation : TDefinition        {            return serviceCollection.AddDecorated<TDefinition, TImplementation>(ServiceLifetime.Scoped,                decorationBuilder);        }        /// <summary>        ///     Метод регистрации декорируемого сервиса с временем жизни Singleton.        /// </summary>        /// <typeparam name="TDefinition"> Интерфейс сервиса. </typeparam>        /// <typeparam name="TImplementation"> Реализация сервиса. </typeparam>        /// <param name="serviceCollection"> Коллекция сервисов. </param>        /// <param name="decorationBuilder"> Построитель декораций. </param>        /// <returns> Коллекцию сервисов после регистрации декораторов. </returns>        public static IServiceCollection AddDecoratedSingleton<TDefinition, TImplementation>(            this IServiceCollection serviceCollection,            Action<DecorationBuilder<TDefinition>> decorationBuilder)            where TImplementation : TDefinition        {            return serviceCollection.AddDecorated<TDefinition, TImplementation>(ServiceLifetime.Singleton,                decorationBuilder);        }        /// <summary>        ///     Метод регистрации декорируемого сервиса с временем жизни Transient.        /// </summary>        /// <typeparam name="TDefinition"> Интерфейс сервиса. </typeparam>        /// <typeparam name="TImplementation"> Реализация сервиса. </typeparam>        /// <param name="serviceCollection"> Коллекция сервисов. </param>        /// <param name="decorationBuilder"> Построитель декораций. </param>        /// <returns> Коллекцию сервисов после регистрации декораторов. </returns>        public static IServiceCollection AddDecoratedTransient<TDefinition, TImplementation>(            this IServiceCollection serviceCollection,            Action<DecorationBuilder<TDefinition>> decorationBuilder)            where TImplementation : TDefinition        {            return serviceCollection.AddDecorated<TDefinition, TImplementation>(ServiceLifetime.Transient,                decorationBuilder);        }        /// <summary>        ///     Метод        /// </summary>        /// <typeparam name="TService"></typeparam>        /// <param name="implType"></param>        /// <param name="next"></param>        /// <returns></returns>        private static Func<IServiceProvider, TService> ConstructDecorationActivation<TService>(Type implType,            Func<IServiceProvider, TService> next)        {            return x =>            {                var service = (TService) x.GetService(implType);                if (service is IDecorator<TService> decorator)                    decorator.NextDelegate = () => next(x);                else                    throw new InvalidOperationException("Ожидался декоратор");                return service;            };        }        /// <summary>        ///     Создание фабрики для декорируемого сервиса.        /// </summary>        /// <typeparam name="TDefinition"> Тип контракта сервиса. </typeparam>        /// <param name="serviceType"> Тип реализации сервиса. </param>        /// <param name="decoratorTypes"> Типы делегатов в требуемом порядке. </param>        /// <returns> Фабрику создания сервиса через DI. </returns>        private static Func<IServiceProvider, object> ConstructServiceFactory<TDefinition>(Type serviceType,            Type[] decoratorTypes)        {            return sp =>            {                Func<IServiceProvider, TDefinition> currentFunc = x =>                    (TDefinition) x.GetService(serviceType);                foreach (var decorator in decoratorTypes)                {                    currentFunc = ConstructDecorationActivation(decorator, currentFunc);                }                return currentFunc(sp);            };        }    }


Теперь немного сахара для функциональщиков

Теперь немного сахара для функциональщиков
    /// <summary>    ///     Базовый класс декоратора.    /// </summary>    /// <typeparam name="T"> Тип декорируемого сервиса. </typeparam>    public class DecoratorBase<T> : IDecorator<T>    {        /// <summary>        ///     Делегат для получения следующего декоратора или сервиса.        /// </summary>        public Func<T> NextDelegate { get; set; }        /// <summary>        ///     Выполнить код декоратора с вызовом следующего декоратора.        /// </summary>        /// <typeparam name="TResult"> Тип возвращаемого значения. </typeparam>        /// <param name="lambda"> Выполняемый код. </param>        /// <returns></returns>        protected Task<TResult> ExecuteAsync<TResult>(Func<T, Task<TResult>> lambda)        {            return lambda(NextDelegate());        }        /// <summary>        ///     Выполнить код декоратора с вызовом следующего декоратора.        /// </summary>        /// <param name="lambda"> Выполняемый код. </param>        /// <returns></returns>        protected Task ExecuteAsync(Func<T, Task> lambda)        {            return lambda(NextDelegate());        }    }


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

    public Task<Response> MethodAsync(Request request)    {        return ExecuteAsync(async next =>        {            // code before            var result = await next.MethodAsync(request);            // code after            return result;        });    }


А если конкретный метод не надо оборачивать текущим декоратором, можно просто написать так

    public Task<Response> MethodAsync(Request request)    {        return ExecuteAsync(next => next.MethodAsync(request));    }


Немного магии все же осталось. А именно назначение свойства NextDelegate. Сходу не понятно, что это и как использовать, но опытный программист найдет, а неопытному надо 1 раз объяснить. Это как DbSet'ы в DbContext :)

Я не выкладывал это на гит хаб. Кода немного, он уже обобщенный, так что можно дергать прямо отсюда.

В заключении хочу ничего не говорить :)
Подробнее..
Категории: C , Net , Net core , Dependency injection , Aop

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru