Привет, меня зовут Владимир Голубев, я системный архитектор. Последние несколько лет я занимался системной архитектурой в финтехе. За это время у меня накопилось много опыта и понимание того, чтобы я хотел изменить в работе архитекторов. Я хочу рассказать, как я пришёл к подходу "архитектура как код" и почему в итоге начал создавать свой инструмент для работы с живой архитектурной моделью.

Как возникла потребность

Началось всё с необходимости защитить спроектированную системную архитектуру системы перед архитектурной комиссией.

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

Я начал с Drawio, знакомый и удобный инструмент, пока я не стал рисовать в нём подробную системную архитектуру. После обсуждения с коллегами формата, решили отображать на схеме относительно полную картину, как выглядит кластер, что в нём развёрнуто на уровне namespaces и сервисов. Чтобы по одной схеме можно было понять, что происходит и на инфраструктурном, и на уровне разрабатываемого ПО, отвечает ли всё это требованиям по надёжности, безопасности и так далее.

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

Поэтому, я решил посмотреть, а что нам предлагает опенсорс и коммерческие решения, возможно уже есть то что мне нужно?

Что есть на рынке?

Я разделил для себя все инструменты на три лагеря - "рисовалка", диаграммы кодом и инструменты для энтрепрайз архитекторов(да, это разделение не идеальное, но мне этого хватило чтобы понять основные направления). Список ниже не претендует на полноту. Более широкий перечень инструментов можно найти тут https://softwarearchitecture.tools/

Ого! Подумал я и решил что я сейчас выберу что-то полезное и дельное! Из всех инструментов отдельно хотелось бы выделить несколько:

Draw.io С ним я провёл больше всего времени. В итоге сделал один файл, но разбил систему на модули — разные страницы с перекрёстными ссылками между ними, чтобы можно было переходить от уровня к уровню. Плюс эмуляция кнопки «назад» на верхний уровень. Но есть проблема с масштабированием, каждое обновление это ручная работа, двигаешь стрелочки, поправляешь подписи, пересобираешь layout.

Structurizr Популярный инструмент, есть DSL, но визуальная составляющая — на достаточно низком уровне(адепты проекта не судите меня строго). Переходы между элементами есть, но реализация примитивная. Проблемы с layout и координатами, велком в issues на гитхабе. Идея классная, но для меня это выглядело как инструмент, с которым постоянно надо договариваться.

LikeC4 Визуальная часть — отличная, по сути это Structurizr на максималках, но со своими DSL.

IcePanel Вот тут я был реально в восторге. Визуальная часть проработана отлично. Особенно зацепили flows — анимированные потоки, когда можно наглядно показать, как запросы проходят через систему. Но высокий ценник, плюс проблемы с оплатой, не судьба.

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

Тяжёлые EA-инструменты (Sparx, Archi, Сфера.Архитектура) Не рассматривал. ArchiMate мощная штука, особенно если любишь структуру и разбираешься в связях. Но количество уровней, типов связей и сущностей зашкаливает.

Ilograph - отдельно отмечу этот проект, свой DSL на YAML, интересно сделана визуализация, однако через пару недель описания архитектуры, я понял, что с YAML можно жить, а вот с подходом к визуализации нет.

Попробовал ещё D2, Diagrams, несколько проектов помельче. У Diagrams, например, на локальной машине картинки просто не рендерились. В целом, после опыта с Draw.io хотелось именно живых схем как у IcePanel, но без привязки к кастомному DSL, этот проект стал моим фаворитом в визуальном плане. Чтобы можно было показать всем и бизнесу и командам и на арх комитеты носить.

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

Как я начал делать свой инструмент

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

Что хотел получить на выходе, IcePanel/LikeC4 в визуальном плане, наглядные потоки данные, sequence диаграммы, но без кастомного DSL, не совсем, но схемы нужно описывать кодом.

Первый подход к снаряду

Поскольку у меня опыт связан с C# и TypeScript, первая мысль была — а что если схему описывать в формате обычного кода на C#? Сказано - сделано, библиотека готова, с ней можно через Fluent API описать C4-подобную схему. Один файл, одна схема, блоки, потоки данных.

var system = new ComputerSystem("Online Banking");

var frontend = new ReactApp("Customer Portal",
    description: "SPA для клиентов банка",
    belongsTo: system);

var api = new RestApi("Accounts API",
    description: "Балансы, платежи, история",
    belongsTo: system);

var db = new Postgres("Ledger DB",
    description: "Счета и транзакции",
    belongsTo: system);

var user = new User("Customer");

user.SendsRequest(frontend, "открывает приложение")
    .Then(frontend).SendsRequest(api, "загружает баланс")
    .Then(api).GetDataFrom(db, "получает баланс из бд");

Параллельно смотрел какие есть варианты на дотнете, как оказалось, примеров на GitHub хватает, я не первый, кто столкнулся с такой задачей. Попробовал несколько, API для описания схем в них казался неоптимальным(много кода, синтаксис странноват, тяжело читать. Хотелось найти баланс, чтобы не только был хороший визуал, но и код был простым и понятным. Попутно я решил посмотреть как лучше всего это дело визуализировать, поискал библиотеки для визуализации графов и пошерстив issues пришло понимание, что расположение элементов должен быть детерминированным(чтобы элементы не прыгали по схеме при рендере). Элементы на схеме нужно автоматически раскладывать, ведь мышкой их уже не потаскаешь(в целом можно и то и то, но это уже другой вопрос). В итоге я остановился на Graphviz, чтобы можно было контролировать направление графа, группировку по блокам и маршрутизацию связей.

Второй подход к снаряду

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

Какую нотацию выбрал?

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

Заработало!

Вот что получилось в итоге(прошу прощения за качество скриншота)

Рабочий вариант меня в какой-то степени устраивал, и я решил его попробовать в деле.

И тут упёрся в те же самые ограничения, что и с Draw.io. Да, стало легче обновлять и поддерживать, исходники в Git, полная поддержка TypeScript в редакторе, ошибки подсвечиваются. Но потребность в правках никуда не делись(опечатки, новые элементы и тд), чтобы это показать нужен экспорт в svg или png, которую нужно куда-то загружать. Плюс были ограничения Confluence на отображение контента.

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

Схема — это побочный артефакт

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

Я не претендую на истину в последней инстанции, но ценность не в картинке, а в том что за ней. А картинка молчит. Реальная ценность появляется, когда архитектуру можно спросить:

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

  • Есть ли циклические зависимости? Безусловно, можно разглядеть это на схемах, но это сложнее.

  • Что изменилось с момента последнего релиза?

  • А то что нарисовано и что фактически развёрнуто совпадает?

Да, с подходом architecture as code у тебя есть машиночитаемое описание системы — но само по себе оно мало что даёт, нужны инструменты, чтобы извлекать из него пользу. Когда такие инструменты появляются, архитектурный артефакт перестаёт быть просто файлом и становится живой моделью — по ней можно искать, визуализировать зависимости. А если еще и задавать вопросы через LLM, то у архитектуры появляется свой голос.

Вдохновившись своим осознанием (хотя для кого-то это очевидно), я начал смотреть, а куда мы идем, как развивается подход architecture as code, что пишут другие архитекторы, стал узнавать мнение коллег и чем занимаются люди в других компаниях — возможно, этот вопрос уже решён? Мое осознание подтвердилась, архитектура действительно должна быть живой. Об этом написана масса статей и куча докладов — например, раз, два, три.
Результат не схема, а живая модель, которой можно задать вопрос.
Поэтому вместо ещё одного Structurizr мой проект должен помогать в создании живых queryable архитектурных моделей.

А дальше возник следующий вопрос - если модель может говорить, то почему ей можно верить?

От модели к живой архитектуре

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

Fitness functions

В 2017 году Нил Форд, Ребекка Парсонс и Пэт Куа опубликовали книгу «Building Evolutionary Architectures», где перенесли термин fitness function на архитектуру. По сути это функция, которая принимает на вход решение и возвращает числовую оценку, насколько это решение хорошее.

В контексте ИТ архитектуры, фитнес-функция - это механизм, который обеспечивает объективную оценку целостности одной или нескольких архитектурных характеристик системы. Это не обязательно код. Фитнес-функция может быть реализована как:

  • Юнит-тест

  • Метрика из мониторинга

  • Результат статического анализа

  • Что угодно, лишь бы оценка была объективна

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

Источники данных

Самый очевидный источник — инфраструктура и исходный код. Проведя анализ конфигов инфраструктуры (привет IaC) и исходного кода приложения можно получить достаточно полную картину того, как работает система. Инфраструктура нам показывает где и как это все работае(namespaces, services, очереди, базы данных, параметры сети и другие зависимости). Исходный код дополняет это внутренней семантикой системы — API эндпойнты, импорты между модулями, вызовы к БД, публикацию и потребление событий, зависимости от внешних систем. Вместе это уже не просто схема развёртывания и не карта исходников, а гораздо более полная модель того, как система действительно собрана.

Есть и третий источник - runtime-поведение. Сервис может и реализован как задуман и корректно развёрнут в кластере. Но метрики, логи и трейсы дополняют картину.

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

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

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

А что мы будем делать с этими источниками?

Автоматизировать! У нас же есть, по сути, те самые views, которые мы описываем в схемах, но из разных источников. И вот тут architecture as code раскрывается по-настоящему. Если модель описана кодом(в любом виде), с перечислением сервисов, доменов, связей, контрактов, то мы можем проверить модель. Не на уровне классов(класс X не должен наследоваться от класса Y, хотя и такие проверки нужны), а сервис из домена Orders не должен иметь прямых зависимостей от внутренних API домена HR, например.

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

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

На практике это приведет к снижение объёма ручного ревью со стороны архитектора, потому что не нужно вручную следить за соблюдением стандартов. В результате меньше времени уходит на надзор и бюрократию, а больше на проектирование и развитие систем. Так же, важно отметить то, что техдолг перестаёт быть чем-то абстрактным, он превращается в набор конкретных сигналов при автоматизированных проверках. А раз он становится видимым и измеримым, им уже можно управлять.

К чему я пришел?

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

Как описывается архитектурная модель?

За основу я взял прототип редактора, сделанный ранее, и расширил его. Добавил раздел с аналитикой, настройками и обвес для проверок и уведомлений об изменениях, плюс интеграцию с ADR.

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

const system: ComputerSystem = { name: "Online Banking" };

const portal: ReactApp = {
  name: "Customer Portal",
  description: "SPA для клиентов банка",
  belongsTo: system,
};

const api: RestApi = {
  name: "Accounts API",
  description: "Балансы, платежи, история",
  belongsTo: system,
};

const db: Postgres = {
  name: "Ledger DB",
  description: "Счета и транзакции",
  belongsTo: system,
};

const user: User = { name: "Customer" };

user.sendsRequestTo(portal, "открывает приложение")
  .then(portal).sendsRequestTo(api, "загружает баланс")
  .then(api).getDataFrom(db, "получает данные из бд");

В планах есть плагин для IDE, но пока браузер полностью устраивает — коммитить можно прямо из него.

Как модель наполняется информацией?

FlowConsole получает данные из Git — можно настроить, какие именно репозитории система отслеживает. По webhook запускается извлечение, прогон через парсеры, приведение к формату метамодели и обновление графа. В результате мы можем строить запросы, отслеживать изменения и выполнять архитектурные проверки. Через плагины можно подключить новые источники данных, добавить поддержку нового языка или инфраструктуры. Плагины не привязаны к .NET и могут быть написаны на любом языке, главное соблюдать stdio контракт.

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

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

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

LLM, запросы к модели и ADR как часть архитектурной модели

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

Но отвечать на вопросы о текущем состоянии системы мало. Дальше почти сразу возникает другой вопрос, а почему именно это решение когда-то было принято. В FlowConsole ADR встроены в саму модель и привязаны к логическим сущностям. Поэтому при просмотре арх модели видно не только её текущее состояние, но и какие решения были приняты. Плюс через LLM можно собрать черновик ADR по diff связей или по найденному нарушению.

Что дальше?

FlowConsole — в активной разработке, если вы хотите потестировать или предложить фичу — буду рад обсудить, Пишите в Telegram