paint-brush
Автоматизация диаграмм архитектуры приложений: как я создал инструмент для сопоставления кодовых баз из исходного кодак@vladimirf
29,401 чтения
29,401 чтения

Автоматизация диаграмм архитектуры приложений: как я создал инструмент для сопоставления кодовых баз из исходного кода

к Vladimir Filipchenko6m2024/07/30
Read on Terminal Reader

Слишком долго; Читать

Вам когда-нибудь хотелось, чтобы существовал инструмент, который мог бы мгновенно превратить ваш код в четкую, наглядную диаграмму? Что ж, именно это и делает NoReDraw! Этот инструмент, созданный в результате разочарования инженера-программиста, идентифицирует ключевые компоненты, такие как артефакты и конфигурации, и связывает их вместе для создания комплексной диаграммы архитектуры. Он спроектирован так, чтобы его можно было легко настраивать и легко расширять, что гарантирует актуальность вашей документации без необходимости перерисовывать диаграммы каждый раз, когда что-то меняется.
featured image - Автоматизация диаграмм архитектуры приложений: как я создал инструмент для сопоставления кодовых баз из исходного кода
Vladimir Filipchenko HackerNoon profile picture
0-item

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


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


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


Перво-наперво

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


Сначала я создал пример Java- приложения с помощью Gradle, Docker и Terraform. Конвейер действий GitHub развертывает приложение в Amazon Elastic Container Service. Этот репозиторий будет исходным кодом для инструмента, который я создам (код здесь ).


Во-вторых, я нарисовал очень общую диаграмму того, что я хотел увидеть в результате:



Я решил, что будет два типа ресурсов:

Реликвия

Термин «артефакт» показался мне слишком перегруженным, поэтому я выбрал Relic . Так что же такое Реликвия? Это 90% всего, что вы хотите увидеть. В том числе, но не ограничивается:

  • Артефакты (синие прямоугольники на схеме, т.е. Jars, Docker-образы),
  • Конфигурации ресурсов Terraform (розовые квадратики на схеме, т.е. инстансы EC2, ECS, очереди SQS),
  • ресурсы Кубернетеса,
  • и многое-многое другое


Каждая реликвия имеет имя (например, my-shiny-app), необязательный тип (например, Jar) и набор пар ключ → значение (например, путь → /build/libs/my-shiny-app.jar), которые полностью описывает Relic. Они называются определениями . Чем больше определений будет у Relic – тем лучше.

Источник

Второй тип — это Source . Источники определяют, создают или предоставляют Реликвии (например, желтые прямоугольники выше). Источник описывает Реликвию в каком-то месте и дает представление о том, откуда она взялась. Хотя источники — это компоненты, из которых мы получаем больше всего информации, на диаграмме они обычно имеют второстепенное значение. Вероятно, вам не понадобится много стрел, идущих от Terraform или Gradle к любой другой реликвии.


Relic и Source имеют отношения многие-ко-многим.


Разделяй и властвуй

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


Хотя я большой поклонник невероятно подключаемой архитектуры поставщиков Terraform, я решил построить то же самое, хотя и упрощенное:

Провайдеры


У Поставщика есть одна четкая обязанность: создание Реликвий на основе запрошенных исходных файлов. Например, GradleProvider читает файлы *.gradle и возвращает Jar , War или Gz Relics. Каждый поставщик создает реликвии тех типов, о которых он знает. Провайдеры не заботятся о взаимодействии между Реликвиями. Они строят Реликвии декларативно, полностью изолированно друг от друга.


При таком подходе можно легко проникнуть настолько глубоко, насколько захотите. Хорошим примером являются действия GitHub. Типичный YAML-файл рабочего процесса состоит из десятков шагов с использованием слабосвязанных компонентов и сервисов. Рабочий процесс может создать JAR-файл, затем образ Docker и развернуть его в среде. Каждый шаг рабочего процесса может быть охвачен его поставщиком. Итак, разработчики, скажем, Docker Actions создают Provider, связанный только с теми шагами, которые им интересны.


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


Соединять или не объединять

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


AmazonECSProvider анализирует JSON определения задачи и создает Relic с типом AmazonECSTAsk . В рабочем процессе действий GitHub также есть шаг, связанный с ECS, поэтому другой поставщик создает реликвию AmazonECSTaskDeployment . Теперь у нас есть дубликаты, потому что оба провайдера ничего друг о друге не знают. Более того, неверно считать, что кто-то из них уже создал Реликвию. И что?


Реликвии сливаются


Мы не можем удалить ни один из дубликатов из-за определений (атрибутов), которые есть у каждого из них. Единственный способ — объединить их. По умолчанию следующая логика определяет решение о слиянии:


 relic1.name() == relic2.name() && relic1.source() != relic2.source()


Мы объединяем две реликвии, если их имена одинаковы, но они определены в разных источниках (как в нашем примере, JSON в репозитории, а ссылка на определение задачи находится в действиях GithHub).


При слиянии мы:

  1. Выберите одно имя
  2. Объединить все определения (пары ключ → значение)
  3. Создайте составной источник, ссылаясь на оба исходных источника.


Нарисуйте линию

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


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


Реликвии совпадают


Пример. В Dockerfile есть инструкция ENTRYPOINT.


 ENTRYPOINT java -jar /app/arch-diagram-sample.jar


С некоторой уверенностью мы можем сказать, что Docker контейнеризирует все, что указано в ENTRYPOINT . Итак, Dockerfile Relic имеет простую функцию Matcher: entrypointInstruction.contains(anotherRelicsDefinition) . Скорее всего, некоторые Jar Relics с arch-diagram-sample.jar в Определениях будут соответствовать ему. Если да, появится стрелка между Dockerfile и Jar Relics.


После определения Matcher процесс связывания выглядит довольно простым. Служба связывания перебирает все реликвии и вызывает их функции Matcher. Соответствует ли Реликвия А какому-либо из определений Реликвии Б? Да? Добавьте ребро между этими реликвиями в полученный график. Край также может быть назван.


Визуализация

Последний шаг — визуализировать наш окончательный график предыдущего этапа. Помимо очевидного PNG, инструмент поддерживает дополнительные форматы, такие как Mermaid , Plant UML и DOT . Эти текстовые форматы могут показаться менее привлекательными, но их огромное преимущество состоит в том, что вы можете вставлять эти тексты практически в любую вики-страницу ( GitHub , Слияние , и многое другое).


Вот как выглядит окончательная диаграмма примера репозитория:

Итоговая диаграмма


Кастомизация

Возможность подключать собственные компоненты или настраивать существующую логику очень важна, особенно когда инструмент находится на начальной стадии. Реликвии и источники по умолчанию достаточно гибки; вы можете положить в них все, что захотите. Любой другой компонент настраивается. Существующие поставщики не покрывают необходимые вам ресурсы? Легко реализуйте свои собственные. Не устраивает описанная выше логика слияния или связывания? Без проблем; добавьте свою собственную LinkStrategy или MergeStrategy . Упакуйте все в JAR-файл и добавьте при запуске. Подробнее читайте здесь .


Аутро

Создание диаграммы на основе исходного кода, вероятно, получит распространение. И в частности инструмент NoReDraw (да, это название инструмента, о котором я говорил). Соавторы приветствуются !


Самым замечательным преимуществом (вытекающим из названия) является отсутствие необходимости перерисовывать диаграмму при изменении компонентов. Из-за отсутствия инженерного внимания документация в целом (и диаграммы в частности) устаревает. С такими инструментами, как NoReDraw , это больше не должно быть проблемой, поскольку их легко подключить к любому конвейеру PR/CI. Помните, жизнь слишком коротка, чтобы перерисовывать схемы 😉