Шаблон проектирования конвейера, вы уверены, что не понимаете?

Шаблоны проектирования

Шаблон проектирования Pipeline — это то, с чем я столкнулся, только когда пришел в новую компанию, и это то, что широко используется в проектах. Я думаю, что это очень интересно, поэтому я написал статью, чтобы познакомить вас с ней, надеюсь, она вам понравится.

необходимость

Несколько дней назад Сяо Мин получил запрос на разработку «простого» процесса обработки платежей, чтобы обрабатывать ряд процессов обработки после того, как пользователи размещают заказ. В этом процессе есть много шагов, в том числе: расчет заказа (включая расчет скидки), проверка суммы, проверка запасов, проверка купона, выполнение платежа, вычет купона, вычет запасов, уведомление сторонней логистики, уведомление об оплате пользователям Успех, уведомить продавца об отправке и так далее.

После того, как Сяо Мин получил это требование, он подумал, что это требование не сложное, просто простой расчет, проверка, настройка интерфейса и отправка сообщения. Кто не может написать if-else? Поэтому я начал писать 300 строк кода, как показано ниже, за один раз.

面条
Лапша

рефакторинг

Код закончен. Однако во время Code Review меня отругал начальник: «Ваш код весь свалился в кучу, как вы просите других поддерживать его в будущем? Сможете ли вы сами в нем разобраться за два месяца?»

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

抽方法
насосный метод

После того, как я изменил его, я показал его боссу, и босс серьезно сказал: «Сяо Мин, читабельность кода очень важна, а также очень важна ремонтопригодность. Видите ли, некоторые ссылки могут также использоваться в других сценариях, таких как проверка запасов, выполнение платежей, отправка уведомлений и т. д.»

«Я понимаю. Таким образом я выделяю эти процессы в отдельные классы, чтобы их можно было повторно использовать в будущем».

抽取类
Извлечь класс

Написав, прочитайте боссу еще раз. Босс кивнул, посмотрев на это: «Да, теперь это выглядит намного лучше. Но вы также знаете, что наш бизнес в торговом центре xx сейчас быстро развивается, и сцена оплаты на самом деле сильно изменилась. Например, мы планируем запустить партию виртуальных продуктов в последнее время. Так же, как членство или игровые скины, его нет в наличии, и нам не нужна ссылка для проверки запасов и вычета для этого продукта. И мы планируем развивать индустрию еды на вынос, поэтому последняя ссылка для уведомления немного по-другому. , возможно, вам придется уведомить курьера. Кроме того, мы планируем недавно начать операционную деятельность. Некоторые продукты могут иметь реферальные вознаграждения. После того, как пользователь успешно заплатит, мы вернем ребейт рефереру, и как только операция деятельность завершена, эту ссылку необходимо удалить. Посмотрите, сможете ли вы найти способ поддержать ее, сделать этот процесс более гибким и попытаться снизить стоимость поддержки новых предприятий в будущем».

Услышав это, голова Сяо Мина замерла. Что тут происходит! Теперь этот процесс жестко запрограммирован в коде, хотя он и выделен в один класс, но место вызова по-прежнему прописано в построчном коде.

Использование конвейеров

Итак, Сяо Мин проверил информацию в Интернете и узнал, что есть что-то, называемое шаблоном проектирования конвейера, который очень хорошо подходит для его нужд!

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

Шаблон проектирования Pipeline имеет три концепции: Pipeline, Valve и Context. Их соотношение примерно таково:

pipeline关系图
схема трубопровода

Конвейер имеет один контекст и несколько клапанов. Эти Клапаны маленькие, унитарные, и Клапан делает только одну простую вещь. Связь между передним и задним клапанами осуществляется через Context. Контекст — это простой класс POJO, который хранит данные в этом конвейере.

public interface Pipeline {
    void init(PipelineConfig config);
    void start();
    Context getContext();
}

public class Context {
    
}

public interface Valve {
    void invoke(Context context);
    void invokeNext(Context context);
    String getValveName();
}

конфигурация

Суть шаблона проектирования Pipeline заключается в его конфигурируемости. Используя Pipeline, если вы хотите изменить порядок Valve или если некоторые предприятия используют определенный Valve, его можно настроить извне. Таким образом, его можно гибко адаптировать к разнообразным услугам, а для разных услуг можно настроить разные процедуры обработки.

Внимательно посмотрев на конвейер, вы обнаружите, что он очень похож на фильтр нашего веб-запроса? мы вweb.xmlОпределите, какие фильтры используются конфигурацией, и, наконец, сформируйте цепочку фильтров. Его Контекст — это запрос и ответ. Следует ли выполнять и когда выполнять следующий фильтр, который вызывается явно:

filterChain.doFilter(request, response); 

Tomcat также широко использует шаблон проектирования Pipeline.

tomcat
tomcat

Фактически, мы можем добиться конфигурации многими способами. Вы можете поместить его в файл xml или yml в виде Json и поместить в единый центр конфигурации или базу данных. Вы даже можете написать отличный код для запуска Pipeline, например Jenkins, это зависит от вашей реализации.

Это выглядит так:

{
    "scene_a": {
        "valves": [
            "checkOrder",
            "checkPayment",
            "checkDiscount",
            "computeMount",
            "payment",
            "DeductInventory"
        ],
        "config": {
            "sendEmail": true,
            "supportAlipay": true
        }

    }
}

Если ваш бизнес относительно стабилен, есть много направлений бизнеса, но изменения относительно небольшие. Вы также можете использовать шаблон проектирования Pipeline, но если вы не хотите делать его настраиваемым, вы также можете написать его непосредственно в коде и вызвать его явно.

Варианты трубопровода и эволюция

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

Шаблоны проектирования

Pipeline фактически использует идею модели цепочки ответственности. Но он также хорошо работает с другими шаблонами проектирования.

режим стратегии

Мы в Valve, и нам может понадобиться разная логика в зависимости от разных направлений бизнеса. Например, для одного и того же текста некоторые из них являются электронными сообщениями, некоторые — текстовыми сообщениями, а некоторые — Dingding. В это время вы можете прописать канал для отправки по текущей бизнес-линии в конфигурации, а затем использовать режим стратегии в Valve, чтобы решить, какой канал использовать для отправки, чтобы вам не пришлось писать много if-else в Valve, и его будет легко расширять в будущем. .

Шаблон метода шаблона

Иногда некоторые клапаны имеют общую логику. Например, логика следующего псевдокода:

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

Шаблон фабричного метода

Преимущество Pipeline в том, что он может гибко реализовывать различные процессы для разных предприятий посредством настройки. Добейтесь идеального сочетания унификации и дифференциации. Когда мы генерируем Pipeline, читая конфигурацию, лучше использовать фабричный шаблон.

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

Pipeline pipeline = PipelineFactory.create(pipelineConfig);
pipeline.start();

комбинация

Хотя мы говорим, что Valve делает только одну простую вещь. Но это относительно всего процесса. Иногда излишне детализировать нехорошо, да и управлять неудобно. Правильный подход должен заключаться в хорошей работе по абстрагированию и группировке. Например, у нас будет фаза «проверки», поэтому нам не нужно выделять проверку каждого поля в Valve и выносить ее в основной процесс. Мы можем поместить «проверочный» клапан в основной процесс, а затем сгенерировать «проверочный конвейер» в этом «проверочном» клапане. Таким образом, основной процесс относительно ясен, и обязанности каждого Клапана также относительно ясны.

多个pipeline
Несколько конвейеров

Обратите внимание, что дочерний конвейер должен иметь свой собственный контекст, но он также должен иметь контекст основного конвейера, должен ли он быть реализован через наследование?

дерево и граф

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

Допустим, у нас есть условная ветка в некой ссылке, и по состоянию данных в контексте на тот момент мы можем судить, какой Valve идти дальше, и формировать дерево. В конце концов, он может сойтись в Valve, которая сформирует граф.

图状Pipeline
Графический конвейер

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

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

параллельное выполнение

Ранее мы видели, что Valve прикованы друг к другу. Но иногда несколько клапанов могут не зависеть друг от друга и работать параллельно одновременно. Например, чтобы отправить сообщение, несколько Valve могут отправлять его параллельно.

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

Логи и визуализация

Логирование и визуализация необходимы. Для конвейера рекомендуется сгенерировать traceId в контексте, затем использовать АОП и другие технологии для печати журнала или удаления библиотеки и, наконец, отобразить, какие клапаны, время и время перед выполнением каждого клапана визуально отображаются на интерфейс Контекст и другая информация после выполнения.

Исключения также важны. Если вы используете шаблон проектирования конвейера, рекомендуется определить набор исключений, которые можно разделить на «прерываемые исключения конвейера» и «непрерываемые исключения конвейера». Это зависит от реальных потребностей бизнеса, чтобы решить, прерывать ли конвейер. В нашем предыдущем примере, если мы не проходим этап проверки, мы должны выдать исключение, которое может прервать Pipeline, чтобы он не вышел из строя. Но если при отправке электронной почты возникает исключение, просто поймайте исключение, распечатайте журнал предупреждений и продолжайте работу. Прерывать ли конвейер или нет, зависит от бизнеса.

Использовать ThreadLocal

Теоретически мы должны использовать Context в любом месте для передачи данных по конвейеру. Но Context иногда довольно сложно использовать. Например, когда мы извлекаем приватные методы внутри Valve, мы часто можем передавать Context в качестве параметра метода, что не особенно удобно в использовании. И Valve должна быть без гражданства, не подходит помещать Context в Valve как свойство.

В настоящее время мы можем использовать ThreadLocal для замены роли Context, а Valve использует ThreadLocal для доступа к данным. Но при использовании ThreadLocal следует учитывать три момента.

Если ваш Pipeline должен поддерживать параллелизм, использование ThreadLocal в параллельном Valve не подходит.

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

Не помещайте разрозненные свойства в ThreadLocal, поскольку поток может поместить в ThreadLocal только одно значение. И наш контекст может иметь несколько строковых, логических и других значений. Если вы используете ThreadLocal, вы можете обернуть все свойства в класс Context и поместить их в ThreadLocal.

Недостатки трубопровода

Шаблон проектирования Pipeline является мощным, но он также имеет существенные недостатки.

Первый недостаток — плохая читаемость. Потому что это настраивается, а конфигурация часто является внешней (например, JSON в базе данных). Так что читабельность плохая. Особенно когда мы читаем код Valve,"Если вы не сравниваете конфигурацию, вы на самом деле не знаете отношения вызова до и после нее.".

Второй недостаток заключается в том, что данные передаются между конвейерами через контекст, а не простые вызовы функций. Таким образом, Pipeline имеет состояние, и"Вызов метода внутренне изменяет контекст", а не через возвращаемое значение, имеет побочные эффекты.

Применимые сценарии конвейерного режима

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

Суть трубопровода

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

Конечно, это не серебряная пуля и не решит всех проблем. все еще хочу"Подходящее"Просто сделай это.

Об авторе

Я Ясин, постоянно совершенствующийся новичок.

Публичный аккаунт WeChat: составлена ​​программа

Персональный сайт: https://yasinshaw.com

Подписывайтесь на мой официальный аккаунт и развивайтесь вместе со мной~

公众号
публика

В этой статье используетсяmdniceнабор текста