Walking Machine
У нас в банке нужно ходить по сервисам. Некоторые сервисы вполне шаблонны: сначала нужно сделать первый запрос куда-то, потом в другое место метнуться прочитать статус; в некоторых случает достаточно 1 хопа, а в некоторых случаях даже могут ветвления быть и цепочка из четырех вызовов. Вообщем хоть бери и графами все это описывай и BPE для этого используй. Но тащить BPE для того, чтобы стуктурировать пайплайн внешних сервисов не очень, BPE больше для структурирования внутренней бизнес локиги, к тому же BPE предлагает контекст и историю. А для обхода всех эндпойнтов нам не нужен прямо бизнес контекст, нам хочется чтобы все без контекста было только с аккумулятором ответов. К тому же внутри BPE только Эрланг термами мы общаемся а тут эта муть с богомерзкими JSON и XML, вообщем надо что-то более легковесное и заточенное на форматтеры. N2O умеет форматтеры, но N2O еще тяжелее BPE, поэтому пришлось писать суперлегковеский Сервис Уолкер.
- — Полная абстракция от функций формматирования
- — Желание полной декларативности
- — Простая но удобная система моков и их тестов
- — По коду определить количество необходимых моков
- — Построение кода по примерам сообщений
- — Исключить падения из протокола через try из-за аккумулятивности
- — Возвращать не только статус но и все шаги
- — Транзакционность и Персистентность контекста опроса
- — Настройка retry запросов, типов запросов и HTTP методов
- — Двухуровневая модульность по портам подключения и точкам подключения
- — Программирование последовательностей запросов в пайпах для построения не только линейных, но и ветвящихся алгоритмов обхода сетевых сервисов.
За счет того, что каждый вызов необходимо разорвать сразу перед вызовом нижележащего HTTP клиента, получилась система из трех типов и трех функций:
- — Форматтер (распаковка)
- — Протокол (вызов)
- — Эрланг Терм (запаковка)
Запаковка и распаковка это типа return или pure, штуки которые втягивают какой-то объект в обертку какого-то типа. Например у нас Data а мы над Data конструируем такой тип:
Вот этот тип и есть тот тип-протокол, благодаря которому наши функции Упаковки и Распаковки будут звучать. Т.е. имея этот тип мы делаем наши функции упаковки и распаковки композабельными. Теперь как мы будем выстраивать в цепочку нашу монадку. Для начала опишем интерфейс функций форматтирования:
Тут вы не смотрите, что JSON не автоиматически преобразует типы по пайплайну а нужно вручную в поля заглядывать, это сделано для того чтобы программисты которые на этом будут писать, чувствовали немного анархии, впрочем мы им не запрещаем самим конвертировать JSON в Proplists с помощью нашего synrc/rest. Как видите Ans берет Erlang терм и пакует его в наш протокольный тип Pipe, который дальше уже пойдет на вызов сервиса, а после вызова нам нужно будет распарсать ответ от сервиса и опять запаковать его в наш протокольный тип, которые вместе с функциями упаковки и распаковки образуем монаду. Сама программа обхода выглядит следующим образом:
Тут это просто развернутая запись на Erlang композиции функций форматтирования и вызовов внешних сервисов:
Котрая происходит при вызове сервиса. Первым параметром мы ложим программу обходов сервиса, т.е. композицию функции. Вторым параметром мы ложим Erlang объект который хотим залифтить монадично по цепочке, дальше таймаут дефалный и начальное положение акумулятора. Покажем как это работает на примере:
Не забываем про декларативное описание все-го что нам нужно для доступа к сервису, половина уже описана в самих шагах как то METHOD, MIME, количество Retries и точка монтирования порта. Сам порт залается функуией host/1, а кастомные заголовки для сервиса header/1 в которых вы можете свободно обращаться к акумулятору за полями которые вы можете положить в акумулятор на любом шаге выполнения этой композиции.
Вообщем в модуле больше ничего не нужно, кроме имплементации обязательной функции test/0 и вместе с ней моками которые имитируют ответы от каждого вебсервиса, естественно моки должны быть в тех же модулях что и функции упаковки и распаковки.
тут мы цифрами указываем моки которые тестируют разные входы пайпов. Каждая система шагов это как граф по сути, у которого может быть много точек входа, тут мы в фукнции тест программируем какие моки для каких точек входы выбирать. А вот и сами моки:
В сигнатура функции mock/2 второй параметр это список всего что нужно передать в httpc клиент. А вот собственно три функции Упаковки, Вызова и рапаковки:
Где мокабельный http_request выгляит так:
Получилось жутко удобно. Публикую здесь краткое описание для всех, кто собирается использовать этот продукт. Что касается моков, то их количество зависит от количества case в методах распаковки. Проверить что все моки работают можно скопом:
Вот так может выглядеть например модуль сервиса:
Что дал переход на такую систему сервисов? Я смогу за 2 дня переписать 15 сервисов убрав все лишнее, что не касается форматтироврания, в результате у меня получился общий код который единобразно работает со всеми сервисами, дает трейсы выполнения любых программ обхода сервисов, и методику закатывания любых сервисов не думая особо над кодом: моки создаются по коду, а код пишется по мокам.
Цена вопроса 100 LOC, в среднем получается 2-5КБ на сервис. Аналоги в других мирах: Apache Camel, WCF.