Если у вас есть большая система, состоящая из множества микросервисов, то вы наверняка задавались вопросом: «Что сделать, чтобы архитектурная схема этой махины была на 100% актуальной?» Это необходимо, чтобы понимать:
Какие сервисы есть в системе, за что они отвечают и какая команда ими владеет?
Какие БД и файловые хранилища они используют?
Как сервисы взаимодействуют синхронно?
Как сервисы взаимодействуют асинхронно (какие сообщения и в какие топики Kafka отправляют)?
Меня зовут Кирилл Ветчинкин, я архитектор в СберМаркете. Наша система состоит более чем из 300 микросервисов, и с увеличением их количества связи между ними стали неочевидны.
Обычно, в компаниях есть свои практики формирования архитектурных схем и ведения документации, что частично решает поставленный вопрос. Но проблема такова, что часто схемы со временем начинают расходиться с реальностью. Новые интеграции добавляются, старые — уходят, а актуализация схем вручную происходит не всегда своевременно.
Обычно эта проблема решается архитектурным аудитом, который происходит время от времени. Но это достаточно длительная, дорогая и тяжелая работа, требующая большого ресурса архитекторов или системных аналитиков.
Мы в СберМаркете пробовали решить эту проблему таким способом. За каждым департаментом был закреплен архитектор, который следил за актуальностью схем. Но схемы всё равно, хоть и немного, отставали от реальности. А ещё процесс актуализации тратил много ресурса архитекторов.
В итоге мы автоматизировали отрисовку схем, опираясь на метаданные IT-систем — создали отдельный микросервис Architect. О том как это происходит и как работает наш сервис, я и расскажу в этой статье. А также дам несколько советов, которые помогут внедрить то же самое у вас в компании.

Этот и другие технологические кейсы актуальные для e-com мы разбирали на конференции E-community. Посмотреть выступления можно в плейлисте.
Зачем всё это нужно?
В чём, собственно, состоят проблемы с неактуальными схемами:
Архитектору сложно принимать решения. Растёт риск совершить ошибку из-за того, что данные ложные или неполные.
Разработчики и аналитики начинают не доверять архитектурным схемам. Как только они понимают, что та схема, на которую они опирались, стала неактуальна, они просто перестают ей пользоваться. А это негативно влияет на инженерную культуру.
Внешним аудиторам сложно анализировать общее состояние системы. Иногда требуется показать схемы кому-то извне компании. Например, перед добавлением новых интеграций или систем. Если мы предоставим неактуальные архитектурные схемы, то можем получить проблемы при интеграции.
Администраторы и поддержка не могут полагаться на схемы при решении инцидентов. Из-за неактуальных схем у специалистов складывается ложная картина: они думают что система работает одним образом, когда на деле всё совсем иначе.
Всё это приводит к тому, что в компании принимаются ошибочные решения. Соответственно, система развивается некорректно и не так хорошо, как могла бы.
Как решить эти проблемы?
Есть 2 варианта:
Актуализировать вручную — это дорого, мучительно и долго.
Генерировать архитектурные схемы из метаданных систем — быстро, дешево, но есть технические нюансы.
Мы выбрали 2-ой вариант, именно о нем и расскажу в этой статье. Обо всём по порядку.
#1 Выбираем подход для отрисовки схем
Если посмотреть на мировые тенденции, то можно заметить, что некоторое время назад было популярно преимущественно ручное тестирование, но спустя время тесты стали кодом. А после популяризации DevOps ручная настройка инфраструктурных систем так же сменилась на настройку с помощью кода. Мы описываем Doker-файлы, пишем Ansible-скрипты, создаем конфигурации в Terraform и так далее. Инфра также стала кодом.
Архитектура не отстает от тенденции — такой подход называться Architecture as Code. Мы решили его применять при описании наших архитектурных схем. В качестве нотации взяли C4 model и UML. В качестве инструментов использовали такую связку: PlantUML, C4-PlantUML, GitLab.
На рисунки ниже показан пример кода, который отрисовывает архитектурную схему с помощью C4-PlantUML в нотации C4 model.

Удобство подхода в том, что вы описываете компоненты и их связи с помощью кода, затем движок C4-PlantUML производит рендеринг и получается схема.
C4 model — это довольно простая нотация, ориентированная на то, что схему могут создать и понимать любые IT-специалисты, не обязательно только архитекторы.
Нотация предлагает 4 уровня детализации для описания системы. Это чем-то схоже с принципом работы географических карт в онлайне: сначала мы смотрим на планету Земля, потом на континент, потом на город и в конце концов на конкретную улицу. То есть мы зумим от общего к частному.
Тоже самое происходит и в C4 model. Мы описываем общую схему системы, потом более детально какую-то ее часть, потом компонент и можем опуститься дальше вплоть до описания классов.

С4 model нотация состоит из 4-х видов схем:
Context diagram
Container diagram
Component diagram
Code diagram
При генерации схем мы отрисовываем только Container diagram, показывая какие у сервиса есть зависимости от баз данных, топиков Kafka и от других сервисов. Для решения нашей задачи, этого уровня достаточно.
#2 Выбираем метафайлы для генерации схем
Первое, что нам нужно понять — откуда мы будем брать метаданные сервиса, и что это вообще такое.
У нас в компании используется подход PaaS. Фактически, это заготовка сервиса с некими дополнительными библиотеками и настройками Kubernetes, позволяющая за 15 минут запускать сервис в прод с мониторингом и получением конфигураций.
Подробнее о том, как устроен наш PaaS, можно прочитать в статье моего коллеги Димы Лукиянчука: PaaS два года спустя. Обзор инструментов и какие задачи они решают.
Если быть кратким, то с точки зрения деплоя PaaS — это обертка над Helm. Поэтому всё, что вы прочитаете дальше в статье, можно применить относительно ваших Helm-чартов и брать метаданные из них.

В нашей системе PaaS, чтобы сервис развернулся и запустился, разработчик должен заполнить 2 файла:
App.toml — это, можно сказать, паспорт сервиса, в нем описывается вся ключевая информация:
название;
описание (что делает, за что отвечает);
технологии, на которых сервис реализован;
ссылка на GitLab репозиторий с исходным кодом;
данные о команде, которая поддерживает сервис.
Из App.toml файла мы будем брать данные для описания Container (сервиса) на Container diagram. Из него мы возьмем «Название» и «Описание».

Values.yaml — это файл, из которого сервис берет конфигурации для подключения к базам данных, топикам Kafka, обращения к другим сервисам. Анализируя этот файл, можно понять:
в какие сервисы наш сервис обращается;
в какие топики он отправляет данные;
из каких топиков данные получает;
какие базы данных у сервиса;
в какие API Gateway выставляет свои API.
То есть в Values.yaml хранятся все зависимости сервиса, из него мы их возмем для отрисовки интеграций между сервисами в Container diagram.

Structure.sql — это схема баз данных сервиса. Анализируя её, мы можем понять, из каких таблиц и каких полей в этих таблицах состоит БД. Этот файл никак не используется при развертывании, но он создается из миграций при каждой сборке сервиса, а значит что он отражает реальную структуру БД сервиса. Считав и преобразовав данный файл, мы сможем построить ER diagram в нотации UML.

Почему этим файлам можно доверять?
Как уже говорилось выше, при развертывании сервиса в прод он опирается на файлы Values.yaml и App.toml. Если, например в Values.yaml, не указана какая-то зависимость, то в проде этой зависимости у сервиса не будет. И наоборот: если сервис в Values.yaml укажет какую-то зависимость, то в проде он ее получит.
А значит, что эти файлы содержат 100% достоверную информацию — поэтому если мы опираемся на них как на данные при генерации схемы, то мы получаем по-настоящему актуальную архитектурную схему, которая отображает сервисы и их зависимости.
Какие схемы мы будем генерировать?
Для решения нашей задачи нам хватит двух схем, на них мы и сконцентрируемся:
Container diagram, которая показывает зависимости сервиса;
ER diagram, которая показывает структуру его БД.
Остальные схемы тоже можно сгенерировать, но это требует анализа кода, что пока находится за рамками текущей задачи.
#3 Генерация схем. Как это происходит?
Разберём процесс на примере. Допустим, в одном сервисе произошли изменения. Вот что следуюет за этим:
Разработчики вносят настройки и описания сервиса в файлы Values.yaml и App.toml. Structure.sql генерируется автоматически из миграций.
Изменения фиксируются в GitLab репозитории сервиса.
Сервис собирается и развертывается в проде. Там самым мы убеждаемся, что файлы заполнены корректно и с ними все в порядке. Если в них ошибка, то сервис просто не развернется, либо будет ошибка во время его работы.
Далее начинается процесс генерации схемы. Он состоит из двух этапов:
Получение данных о сервисах. Cпециальный микросервис Architect каждый день в 10:00 утра по Москве опрашивает GitLab репозитории всех сервисов компании (все 300), считывает Values.yaml, App.tom и Structure.sql файлы и помещает их к себе в базу данных.

Происходит обработка полученных данных и генерация схем.
После получения данных обо всех сервисах запускается логика обработки данных файлов и они преобразуются в объекты. Верхний уровень иерархии —это сам сервис. В нем могут быть вложенные объекты (БД, S3, синхронные/асинхронные зависимости и т.п).
Затем алгоритм анализируя объекты сервисов генерирует PlantUML код.
На последнем шаге с помощью библиотеки PlantUML код преобразуется в архитектурную схему в виде SVG картинки. Она сохраняется в базе данных микросервиса Architect.
Frontend (Hugin) обращаясь по API к микросервису Architect и может получить как сам PlantUML код, так и архитектурную схему в виде SVG и отобразить клиенту.

В итоге мы получаем 2 схемы:
Container diagram — на ней отражен сервис и его зависимости. Кроме того, мы видим, в какой gateway выставлен его API.

ER diagram — на ней отражена структура БД сервиса. Здесь мы сразу плучаем всё, что нужно.

#4 Обогащение схемы
Сгенерировав контейнерную диаграмму, мы получили схему, но она получилась неполной. В ней есть несколько проблем:
На схеме показано, в какие сервисы наш сервис обращается синхронно, но не отражено какие сервисы обращаются в него.
На схеме есть топики Kafka, в которые сервис стримит данные, но нет информации о том какие сервисы из них читают.
На схеме есть топики Kafka, из которых сервис получает данные, но нет информации кто в них стримит.

Без этой информации схема выглядит неполной и нам нужно решить эти проблемы.
Эти проблемы связаны с тем что в файле Values.yaml есть информация только об исходящих синхронных зависимостях, но нет информации о входящих. Также в файле есть информация о топиках, из которых сервис читает и в которые пишет, но нет информации о том какие сервисы связаны с этими топиками.
Итак, как нам понять, что конкретный сервис имеет входящую синхронную интеграцию с другим сервисом? Это происходит на этапе, который на рисунке ниже подсвечен красным.

Каждый сервис в Architect App — это объект. Внутри каждого объекта описаны его зависимости от топиков Kafka, БД и от других сервисов.

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

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

Итак, мы решили четвертую проблему и реализовали алгоритм генерации метаданных.
Такт генерации схем (CRON)
Осталось последнее — выбрать частоту генерации схем. Я выбрал раз в сутки в 10:00 по Москве. Таким образом каждый новый день мы начинаем с актуализауии схем.

Результат — 100% актуальные схемы
Развертывание сервисов в продакшен и построение архитектурных схем теперь опирается на одни и те же метаданные. За счет этого схема всегда соответствует действительности.
Как применить наш подход
Напоследок оставляю небольшой список советов на случай, если вы захотите применить такой же подход у себя.
Начните описывать архитектуру в ручную с помощью любого движка: например, C4-PlantUML или Mermaid. Это нужно чтобы набить руку и разобраться в нотации и тонкостях рендеров.
Используйте метаданные сервисов как для конфигурирования Production, так и для построения схем. Вы можете анализировать Helm чарты или просто конфиги систем.
Собирайте с помощью специального алгоритма метаданные о сервисах и создавайте архитектурные схемы системы.
Буду рад ответить на вопросы в комментариях!
Tech-команда Купера (ex СберМаркет) ведет соцсети с новостями и анонсами. Если хочешь узнать, что под капотом высоконагруженного e-commerce, следи за нами в Telegram и на YouTube. А также слушай подкаст «Для tech и этих» от наших it-менеджеров.