HighLoad++, Анастасия Цымбалюк, Станислав Целовальников (Сбербанк): как мы стали MDA

Следующая конференция HighLoad++ пройдет 6 и 7 апреля 2020 года в Санкт-Петербурге
Подробности и билеты по ссылке. HighLoad++ Siberia 2019. Зал «Красноярск». 25 июня, 14:00. Тезисы и презентация.

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

ujhflolbmpow2ysfvjjdsiomcya.jpeg

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

Наш подход использует все преимущества метаданных, динамического кода SQL и кодогенерации на основе Swagger codegen и handlebars. Это решение сокращает время разработки и переконфигурации системы, а добавление новых объектов управления не требует ни единой строки нового кода.

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

Анастасия Цымбалюк (далее — АЦ):  — Меня зовут Настя, а это — Стас!

Стас Целовальников (далее — СЦ):  — Всем привет!

АЦ:  — Сегодня мы расскажем вам про MDA, и как мы с помощью этого подхода сократили время на разработку и явили миру промышленную масштабируемую систему управления метаданными. Ура!

СЦ:  — Настя, что такое MDA?

АЦ:  — Стас, я думаю, мы к этому сейчас плавно перейдём. Точнее, об этом расскажу чуть-чуть в конце презентации. Давай сначала расскажем о нас:

zy5qt1-etpbxnwskirsjg_sq-ri.jpeg

Себя я могу охарактеризовать как искатель синергии в промышленных IT-решениях.

СЦ:  — А меня?

Чем занимается команда SberData?


АЦ:  — А ты просто промышленный мастодонт, потому что вывел в пром не одно решение!

СЦ:  — На самом деле мы работаем вместе в «Сбербанке» в одной команде и занимаемся управлением метаданными SberData:

n9ouqjstpumrwh93zv0zcl6olby.jpeg

АЦ:  — SberData, если по-простому — это аналитическая платформа, куда стекаются все цифровые следы каждого клиента. Если вы клиент «Сбербанка», вся информация о вас стекается именно туда. Там хранится множество dataset, но мы понимаем, что объём данных не означает их качества. А данные без контекста иногда бывают вовсе бесполезны, потому что мы не можем их применить, интерпретировать, защитить, обогатить.

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

СЦ:  — Другими словами, миссия наше й команды — повышение коэффициента полезного действия информационной аналитической платформы «Сбербанка» за счет того, что информация, о которой, ты только что рассказала, должна быть доставлена нужным людям в нужное время в нужном месте. А помнишь, ты ещё говорила о том, что, если данные — это современная нефть, то метаданные — карта месторождений этой нефти.

АЦ:  — Действительно, это одно из моих гениальных высказываний, которым я очень сильно горжусь. Технически эта задача сводилась к тому, что мы должны были создать инструмент управления метаданными внутри нашей платформы и обеспечить его полный жизненный цикл.
Но для того, чтобы погрузиться в проблематику нашей предметной области и понять, в какой точке мы находимся, я предлагаю откатиться на 9 месяцев назад.

Итак, представьте: за окном ноябрь месяц, птицы все улетели на юг, мы грустим… И у нас к тому моменту с командой был проведён успешный пилот, были заказчики — мы все пребывали в зоне комфорта, пока не случилась та самая точка невозврата.

Система управления метаданными моделей


gahcyp-lwacxan__ba3h4amhsbe.jpeg

СЦ:  — Там у тебя ещё что-то было о том, что мы пребывали в зоне комфорта… Фактически нам была поставлена задача создания Metadata Broker, который должен был дать возможность общения с нашими клиентами, программами, системами. Наши клиенты должны были иметь возможность на уровне бэкенда либо отправить какой-то пакет метаданных, либо получить. И мы, обеспечивая эту функцию, на своём уровне должны были аккумулировать максимально согласованную, актуальную информацию по метаданным на четырёх логических уровнях:

  • Уровень бизнес-глоссария.
  • Уровень логической модели.
  • Уровень физической модели.
  • Состояние среды, которое мы получали за счёт реверса промышленных сред.


И всё это должно быть согласованно.

АЦ:  — Да, действительно. Но здесь я бы тоже как-то объяснила по-простому, поскольку не исключаю, что предметная область неясна и непонятна…

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

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

Физическая модель — это о том, когда наступает очередь суровых программистов, архитекторов, которые реально понимают, как эти объекты приземлить — в какую таблицу положить, какие поля создать, какие индексы навесить…

Состояние среды — это некий слепок. Это как показания с машины. Программист иногда хочет сказать машине одно, а она неправильно понимает. Как раз состояние среды показывает нам реальное положение дел, и мы постоянно всё сравниваем; и понимаем, что есть разница между тем, что сказал программист, и реальным состоянием среды.

Кейс для описания метаданных


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

xm4y5gvemtitzrlgzug4znuhbtk.jpeg

К этому уровню человек уже должен иметь свой бизнес-глоссарий (терминов обязательной отчётности) или его завести (средний остаток на лицевом счёте). Дальше приходит аналитик, который его прекрасно понимает, может с ним поговорить на одном языке, но при этом может говорить на одном языке и с программистами.

Он говорит: «Слушай, у тебя здесь вся история разбивается на отдельные счета как сущности, и у них есть атрибут — средний остаток».

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

Сказано — сделано. А потом пришёл наш парсер, который сходил на промышленный контур и сказал: «Да, вижу — есть необходимые таблицы…» Что ещё обогатило эту таблицу? Здесь (в качестве примера) — партиции и индексы, хотя, строго говоря, и партиции и индексы могли бы находиться на уровне проектирования физмодели, а здесь могло быть что-то ещё (например, объём данных).

Регистрация и хранение на уровне метаданных


АЦ:  — Как это хранится всё у нас? Это суперупрощённая форма того примера, который расписал ранее Стас! Как это всё будет лежать у нас?

cl6bcun8zmcowyo5pocbcjy64j8.jpeg

Фактически это будет одна строка в объекте «Глоссарий», одна — в объекте «Термины», одна — в «Сущностях», одна — в «Атрибутах» и так далее. На рисунке выше каждый прямоугольник — это объект в нашей системе управления, который представляет собой ту или иную хранимую там информацию.

Чтобы вас потихоньку начать вводить в терминологию, я прошу отметить следующее… Что такое объект управления метаданными? Физически это представлено в виде таблицы, но на самом деле там хранится определённая информация по терминам, глоссариям, сущностям, атрибутам и т. д. Этот термин, «объект», мы дальше и будем использовать в своей презентации.

СЦ:  — Здесь надо сказать, что каждый кубик — это просто таблица в нашей системе, где мы храним метаданные, и это мы называем тем самым объектом управления.

Требования по введению метаданных


Что мы имели на входе? На входе нам пришли достаточно интересные требования. Их было очень много, но здесь мы хотим показать три основных:

byhesadzdaf8chjm3cncp9hhzxs.jpeg

Первое требование достаточно классическое. Нам говорят: «Ребята, всё, что к вам однажды зашло, оно должно зайти навсегда». Историзация полная, причём любое изменение в вашей системе метаданных, которые к вам пришли (неважно, пришёл ли пакет из 100 полей (100 изменений), либо одно поле в одной таблице изменилось), требуют обязательной регистрации новой ревизии метаданных. Также требуют вернуть ответ:

  • по умолчанию — актуальное состояние;
  • по дате;
  • по номеру ревизии.


Второе требование было интереснее: нам сказали, что могут с нами работать по объектам, но придётся программировать много на Java, а они этого не хотят. Предложили пригнать вперемешку сразу 100 объектов (или 10), а нам это дело обрабатывать (потому что умеем). Что значит вперемешку? Например, пришло 10 колонок. Они имеют ссылку на идентификатор таблицы, а самой таблицы у нас нет — она в хвосте JSON«а пришла. «Вы придумайте и обработайте — надо, чтобы вы это смогли»!

В порядке увеличения интереса — третье: «Мы хотим иметь возможность не просто пользоваться API, который вы нам сделаете, а хотим сами понимать…» И в произвольном порядке говорить: «Дайте нам объединение с этого объекта на тот через третий объект. И пусть у вас система сама поймёт, как это всё делать, спросит у базы и вернёт результат в JSON«е».

Такую историю мы имели на входе.

Приблизительные расчёты


nje3jgkluyviljufojbo92jkd48.jpeg

АЦ:  — По нашим приблизительным расчётам, чтобы всю эту концепцию претворить в жизнь, нужно каждый объект управления участвовал в семи интерфейсах: простые (симпловые), на пообъектную запись / чтение и удаление…

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

Ещё одно — на построение иерархии, чтобы мы системе могли указать — «Верни нам с объекта по объект»; и она возвращает дерево вложенных объектов.

Сложность реализации


СЦ:  — Помимо технических требований, которые пришли нам на вход в момент старта этой истории, мы имели дополнительные трудности.

geh4pgnrayjpjtko_vryyvoco2y.jpeg

Во-первых, это некоторая неопределённость требований. Не всякая команда и не всегда могла чётко сформулировать, что им нужно от сервиса, и зачастую момент истины рождался в момент прототипирования какой-то истории на контуре def. И пока это доходила до прома, могло быть и несколько циклов.

АЦ:  — Это и есть та самая турбулентность, которую анонсировали вначале.

СЦ:  — Далее…

Был запредельный дедлайн, потому что даже на момент старта от нас зависело более пяти команд. Классика жанра: результат нужен был вчера. Вариант работы — в режиме ошпаренной лошади, чем мы и занимались.

Третье — это большой объём разработки. Настя на своём слайде показала, что, когда мы посмотрели требования, что и как делать, то поняли: 1 объект требует семи API (либо под него, либо участие в семи API). Это означает, что, если у нас патч (6 объектов, модельный, 42 API) выходит за недельку…

Стандартный подход


АЦ:  — Да, на самом деле 42 API за неделю — это лишь верхушка айсберга. Мы прекрасно понимаем: чтобы обеспечить работу этих 42 API, нам необходимо:

  • во-первых, создать структуру хранения под объект;
  • во-вторых, обеспечить логику его обработки;
  • в-третьих, написать тот самый API, в котором объект участвует (либо настроен конкретно под него);
  • в-четвёртых, было бы неплохо в идеале обложить всё это контурами тестирования, протестировать и сказать, что всё хорошо;
  • в-пятых (та самая вишенка на торте), задокументировать всю эту историю.


rvltc_6nldjheqzq5hnju6eqhiq.jpeg

Естественно, первое, что нам пришло в голову (на старте мы вам показывали примерную схему) — мы имели около 35 объектов. С ними нужно было что-то делать, всё это нужно было выводить, а времени было очень мало. И первая идея, которая пришла к нам в голову — это сесть, закатить рукава и начать кодить.

Даже пару дней поработав в таком режиме (нас было три команды), мы достигли такой температуры накала… Все были нервные… И мы поняли, что нам нужно искать другой подход.

Нестандартный подход


Мы начали обращать внимание на то, чем мы занимаемся. Идея этого подхода всегда была у нас перед глазами, потому что мы очень давно занимаемся метаданными. Как-то сразу она нам в голову не пришла…

Как вы догадались, суть этой идеи в том, чтобы использовать метаданные. Она заключается в том, что мы собираем структуру нашего хранилища (это и есть определённые метаданные), один раз создаём шаблон какого-то кода (например, несколько API или процедур обработки логики, скриптов создания структур). Один раз создаём этот шаблон, а потом прогоняем через все метаданные. По тегам в код подставляются свойства (имена объектов, поля, важные характеристики), и на выходе получаем готовый код.

hyrlxt2uztvqeakue0olbbmctzs.jpeg

То есть достаточно один раз заморочиться — создать шаблон, а потом всю эту информацию использовать как для существующих, так и для новых объектов. Здесь мы введём ещё одно понятие — #META_META. Объясню зачем, чтобы вас не запутать.

Наша система занимается управлением метаданными, а подход, который мы используем, описывает систему управления метаданными, т. е. две меты. «МетаМета» — мы так назвали у себя, внутри команды. Чтобы и остальных дальше не путать, мы будем использовать именно этот термин.

Механизм обеспечения историзации и ревизии


СЦ:  — Ты кратко изложила остальную часть нашего выступления. Расскажем более подробно.

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

Сперва о том, как мы решили вопрос историзации и ревизии. Возможно, это похоже на то, как делают многие. Рассмотрим это на примере метаданных, которые описывают одно поле в таблице проводок (в качестве примера):

ny7mddafiwemxmigli7who-mee8.jpeg

У неё есть id — »7», имя — carry_note, есть ссылка id_table 73, а также поле — 255. Мы вводим в первичный и альтернативный ключ некое поле (типа date) с временной точки, с которой эта запись становится валидной — valid_from. И ещё одно поле — по какую дату эта запись валидна (valid_to). В данном случае они заполнены по умолчанию — видно, что эта запись в принципе валидна всегда. И так происходит до тех пор, пока мы не захотим изменить, скажем, длину поля.
Как только мы хотим это сделать, мы закрываем запись valid_to (фиксируем временную метку, в которую событие произошло). Одновременно с этим делаем новую запись (»300»). Несложно заметить, что при таком раскладе, если обратиться к базе данных с какой-то временной точки по «битвину» (between) между valid_from и valid_to, то мы получим одну-единственную, но актуальную на тот момент времени запись. И при этом мы одновременно вели некоторый журнал ревизии:

zt-ks9u2di9jbuzxgyd-dpnqllc.jpeg

В нём мы фиксировали возрастающие по сиквенсу (sequence) id ревизии, и временную точку, которая соответствует этому id ревизии. Так мы и смогли закрыть первое требование.

АЦ:  — В принципе да! Здесь подход один и тот же. Мы понимаем, что у каждого объекта в системе есть эти два обязательных поля, и мы один раз заморочились — скодировали логику обработки этого шаблона, а потом (при формировании динамического кода) просто подставляем имена соответствующих объектов. Так каждый объект в нашей системе становится ревизионным, и это всё может обрабатываться — мы вообще больше не пишем ни единой строки кода.

Пакетное обновление


СЦ:  — Второе требование для меня было чуть более интересным. Честно говоря, когда оно пришло на вход, я сначала просто стал в ступор. Но решение пришло!

Я напомню, это тот самый кейс, когда к нам, допустим, пришёл JSON с пакетом на n-ое количество объектов, которые нужно вставить в систему. При этом у нас в начале идут 10 колонок, ссылающихся на несуществующую таблицу, а таблица шла в хвосте JSON. Что делать?

pdjann3lit7axfk1infab47y8rm.jpeg

Мы нашли выход в использовании механизма рекурсивных иерархических запросов — это наверняка всем хорошо известная конструкция connect by prior. Сделали это следующим образом: здесь — фрагмент нашего кода на продакшн:

skzzi7gzikn81bg2lmlk4fzd9wq.jpeg

В этом месте (участок кода, обведённый красным овалом) — основная суть, которая даёт идею. И здесь объект линкуется на другой объект, связанный по foreign key, который есть в системе.

Для понимания: если кто-то пишет код на Oracle, там есть таблицы All_columns, All_all_ tables, All_constraint — это и есть словарь, который обрабатывается обрабатывается скриптами (как те, что отражены на слайде выше).

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

Например, поле — как оно могло бы выглядеть? Это код платформы: oracle КП., production. КП, my_scheme.КП, my_table. КП и т.д., где КП — это код поля. Вот и будет такой дескриптор.

АЦ:  — Какая здесь проблематика? У нас есть объекты в системе и нам очень важен порядок их вставки. Например, колонки мы не можем вставить перед таблицами, потому что колонка должна ссылаться на определённую таблицу. Как мы делаем стандартно: сначала вставляем таблицы, в ответ получаем массив id, по этим айдишникам раскидываем колонки и делаем вторую операцию вставки.

В реальности, как показывал Стас, длина этой цепочки доходит до 8–9 объектов. Пользователю, используя стандартный подход, нужно выполнять все эти операции поочерёдно (все эти 9 операций) и чётко осознавать их порядок, чтобы не случилось никакой ошибки.

Насколько я верно интерпретирую Стаса, мы можем передать системе все эти объекты в любом порядке и вообще не заморачиваться о том, как нам нужно производить эту вставку — мы просто кинул в систему суповой набор, а она сама всё распределила, в каком порядке вставлять.
Единственное, у меня возникает вопрос:, а если мы вставляем объект в первый раз? Мы таблицу до этого вставляли, не знаем её id. Как нам указать (чисто гипотетический пример), что нужно вставить две таблицы, у каждой из которых есть по колонке? Как нам указать, что в этом JSON«е колонка ссылается на таблицу1, а не на таблицу2?

СЦ:  — Дескриптор! Дескриптор, который мы указали на том слайде (предыдущем).
А на этом слайде собственно и дано то самое решение:

au_vo2urh2ur5fllwuhuvgfaw78.jpeg

Дескрипторы используются в системе как некое мнемоническое поле, которое не существует, но заменяет id. В тот момент, когда вначале система поймёт, что нужно вставить таблицу — вставит, получит id; и уже на этапе генерации SQL-запроса вставки и колонки она будет оперировать id. Пользователь может не париться: «Дай дескриптор и выполни!». Система сделает.

Универсальный запрос по группе связанных объектов


Пожалуй, мой любимый кейс. Это самое любимое техническое требование, которое у нас было. К нам пришли и сказали: «Ребята, сделайте, чтобы система могла всё! С объекта на объект, пожалуйста. Догадайтесь, как это всё джойнить между собой. Верните нам, JSON, пожалуйста. Мы не хотим много программировать, используя ваш сервис»…

Вопрос: «Как?!»

Мы на самом деле пошли тем же путём. Ровно та же самая конструкция:

xmyarmfesokkzn31kval5xnbvnu.jpeg

Она использована для того, чтобы решить и эту задачу. С одной лишь разницей: там был допускной фильтр, который раскручивал это иерархическое дерево только для тех историй, где требовался дескриптор. Условно говоря, он был уникальный для каждого объекта. Здесь же раскручиваются все возможные связи в системе (у нас порядка 50 объектов).

Все возможные связи между объектами лежат заранее подготовленными. Если у нас объект участвует в трёх связях, соответственно, три строчки будет подготовлено для того, чтобы алгоритм мог понять. И как только к нам приходит JSON-запрос, мы идём туда, где у нас заранее в «МетеМете» подготовлена эта история, ищем нужный нам путь. Если не находим — это одна история, если находим — формируем запрос в БД. Выполняется — возвращаем JSON (что и просили).

АЦ:  — Как результат, мы можем системе передать, с какого по какой объект мы хотим получить. И если можно очертить чёткую связь между двумя объектами, значит, система сама разберётся, на каком уровне вложенности объект, и вернёт вам дерева:

Это очень гибко! Пользователи у нас, ещё раз повторю, находится в состоянии «турбулентности»: сегодня им нужно одно, завтра — другое. А данное решение позволяет нам очень гибко адаптировать структуру. Это были три ключевых кейса, которые применены у нас на стороне ядра.

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

Мы не смогли сделать этот код 100% динамическим, а это означает, что с любыми созданными объектами (неважно — уже созданные или которые будут созданы потом; главное, чтобы были созданы по правилам) система работать умеет — ничего не надо дописывать, переписывать. Достаточно просто протестировать. Всю эту историю мы припарковали на три универсальных метода. На мой взгляд, их достаточно для того, чтобы решить практически любую бизнес-задачу:

  • во-первых, этот тот самый универсальный «обновлятор» — метод, который умеет делать update/insert/delete (delete — это закрытие записи) по одному или группе объектов, переданных в произвольном порядке.
  • второе — это метод, который умеет возвращать универсальную информацию только по одному объекту;
  • третье — это тот самый метод, который может возвращать Join-информацию, соединённую по группам объектов.


Вот так у нас получилось, и мы сделали ядро. А дальше мы перейдём к твоей самой любимой части.

Точка входа в Application


АЦ:  — Да, это моя любимая часть, потому что это моя зона ответственности — Application Server. Чтобы понять, в какой ситуации я находилась, снова попытаюсь погрузить вас в проблему.
Стас хорошо поработал и передал мне эти три стандартных метода, которые манипулировали этими объектами. Это чисто схематичное описание — в реальности их намного больше:

5odkmo2v_zex8mki419gtahlk3c.jpeg

Вернёмся к самому началу, чтобы вас погрузить… Как у нас будут представлены метаданные в системе?

kcb1yk6tgcqfrnez3dm_umstpzu.jpeg

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

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

wvbgne8ly7inu5jwunv3f8xnmne.jpeg

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

СЦ:  — Извини, а как я могу тебе передавать в каком-то плоском ответе колонки, например, которые ещё не созданы, когда-то будут созданы, и бог знает какими они могут быть?… Поэтому единственный вариант в условиях динамического кода настроить взаимодействие между ядром и application, передать тебе эту информацию — только такой, какой мы видим. Я считаю, что с моей точки зрения это решение было гениальным, потому что оно исходило как раз от тебя.

АЦ:  — Сейчас мы не будем об этом спорить. За две недели до конца дедлайна я осталась с тем, что у меня на руках были эти три метода (слева на предыдущем слайде), которые манипулировали универсальной структурой (справа на том же слайде).

Моей первой мыслью было просто завернуть всё на уровне API и пойти с этим к пользователю, сказав: «Вот, смотрите, какая гениальная вещь! Вы можете делать что угодно! Передавать любые объекты, и даже не существующие. Круто, да»?!

mliqqpbuethupkpzjgr-tstqgm4.jpeg

А они говорят: «Но вы понимаете, что у вас сервис совсем не специализирован? Я как пользователь не понимаю, какие объекты я могу передать в систему, как я могу ими манипулировать… Для меня это — чёрный ящик, я вообще боюсь, что покорёжу данные; я могу ошибиться — мне страшно. Сделайте так, чтобы я мог чётко следовать инструкциям и видеть, какие объекты есть в системе и какие способы манипуляции я могу использовать».

Спека. Подход


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

wevzfoicvrvvkasgu3ihlkbcv7a.jpeg

Посмотрев структуры «Сваггера», я поняла, что нужно где-то взять структуру объектов, которые есть в системе. От ядра я получила только три стандартных метода и перевёртыш в виде таблицы. Больше ничего. Для меня тогда казалось невыполнимой задачей вытащить всю структуру, которая есть в хранилище, из этих четырёх стандартных полей. Я искренне не понимала, где меня взять всё описания объектов, все допустимые значения, всю логику…

СЦ:  — Что значит где? У нас с тобой есть «МетаМета», которая обеспечивает работу ядра в режиме Real-time. Ядро в режиме реального времени исполнения генерирует SQL-запрос, который общается с базой. Там всё есть, не только то, что тебе нужно. Там есть ещё и связи между объектами.

АЦ:  — По совету Стаса тогда я пошла в «МетаМету» и удивилась, потому что весь необходимый джентльменский набор для генерации стандартной спеки там присутствовал. Тогда появилась идея, что нужно создать шаблон и расписать всё по семи возможным сценариям — 7 стандартных API для каждого объекта.

Спека. OAS + Handlebars


Итак, здесь нетрудно заметить, из чего состоит спека:

0wy5wtfsnvh9bpunt-fpdtgwce4.jpeg

Можно зайти на сайт OAS и Handlebars (внизу слайда) и посмотреть, из чего она должна состоять — там набор Endpoints, набор методов, а в конце идут модели. Код повторяется из раза в раз. Для каждого объекта мы должны писать get, put. delete; для группы объектов мы это должны написать и так далее.

Фишка состояла в том, чтобы всю эту историю написать один раз и больше не париться. На слайде представлен пример реального кода. Синие объекты — это теги в Handlebars, это шаблонизатор; довольно-таки гибкий, всем советую — его можно настраивать под себя, писать кастомные обработчики тегов…

На место этих синих тегов, когда этот шаблон пробегается по всем-всем метаданным, подставляются все значимые свойства — имя объекта, его описание, какая-то логика (например, что нам нужно добавить дополнительный параметр, в зависимости от свойства) и так далее. В конце — ссылка на модель, которую он интерпретирует.

Код application. Swagger Codegen + Handlebars


Всё это мы закодировали, записали, составили спеку. Всё было очень классно и хорошо. Мы получили все 7 возможных сценариев для каждого объекта.

Отдали это пользователю. Тот сказал: «Вау! Круто! Теперь мы хотим этим пользоваться»! В чём проблема, опять-таки?

У нас появилась спека, которая подробно описывает каждый метод, что с ним делать, какими объектами манипулировать. И есть три стандартных метода ядра, которые на вход принимают описанную выше перевёрнутую таблицу.

Тогда нужно было просто одно скрестить с другим (сейчас мне это кажется лёгким). То есть, когда пользователь вызывает в интерфейсе какой-то метод, нам нужно было правильно и корректно пробросить его в ядро, перевернув модель (где у нас есть красивые спецификации) в эти четыре стандартных поля. Вот и всё, что нужно было сделать.

ql20bmpw34giiqlmn73ndf3thyy.jpeg

Для того чтобы всё это претворить в жизнь, нам потребовались «назначительные» преобразования…

Преобразования


У «Сваггера» изначально есть такой инструмент — Swagger Codegen. Если вы хоть раз заходили в спеку, составляли, то там есть кнопка «Сгенерировать серверную часть». Нажимаете, выбираете язык — вам генерируется готовый проект.

Генерируется замечательно: там есть все описания классов, все описания эндпойнтов… — он работающий. Можете запустить у себя локально — он будет работать. Беда одна: он возвращает заглушки — каждый метод не инкрементирован.

Была идея в том, чтобы дописать логику, исходя из этих семи сценариев в кодогенераторе — «испортить» один из стандартных шаблонов, настроить его под себя. Здесь как раз пример реального кода, который мы используем в шаблонизаторе и список тех действий, которые нам необходимо было выполнить, чтобы настроить этот кодогенератор под себя:

ttyelscqkjzd0js6dfnzpr_4opg.jpeg

Самое главное, что сделали — подключили необходимые библиотеки, прописали классы общения с ядром, интерпретировали (в зависимости от сценария) вызов одного или нескольких методов на стороне ядра. Также переворачивали модель: из красивой, что указана в спеке — в четыре поля, а потом обратно трансформировали.

Наверное, самый сложный кейс здесь заключался в том, чтобы отдать пользователю дерево, потому что ядро нам в ответ также возвращает четыре строки — поди разбери, на каком уровне иерархии находится объект. Мы воспользовались механизмом внешних связей, который есть в IDE, то есть мы пошли в «МетаМету», посмотрели все пути от одного к другому и по ним динамически генерируем дерево. Пользователь может у нас запросить с любого объекта по любой, какой желает — ему на выходе вернётся красивое дерево, в котором всё уже структурно разложено.

СЦ:  — Я на секунду тебя остановлю, поскольку уже начинаю теряться. Спрошу тебя в стиле «Правильно ли я понимаю, что»…

Ты хочешь сказать, что мы вычислили всё самое сложное, самый сложный код, который необходимо было бы написать для какого-то нового объекта. И для того, чтобы сэкономить время, не делать этого, мы сумели всё это запихнуть в ядро и сделать эту историю динамической… Но этот API (как пошутили, «упоротый») настолько «может всё», что его страшно отдавать наружу: неумело обращаясь с ним, можно повредить метаданные. Это с одной стороны.

С другой стороны, мы поняли, что не можем общаться с нашими заказчиками-клиентами, если не дадим им API, который будет однозначно проекцией тех объектов управления метаданными, которые задеплоины в систему (по сути выполнять некий контракт на наш сервис). Казалось бы, всё — мы попали: если объекта нет — его ещё нет, а когда он появляется — появляется расширение контракта, уже новый код.

Мы вроде бы попали избегаемое ручное кодирование, но ты здесь предлагаешь делать этот код по кнопке. Снова нам удаётся уйти от истории, когда что-то надо писать руками. Это так?

АЦ:  — Да, это действительно так. Вообще, моя идея была завязать с программированием раз и нав

© Habrahabr.ru