Документирование архитектуры программного обеспечения

Maxim Grechushnikov (Maxyc Webber)
13 min readMar 12, 2020

https://herbertograca.com/2019/08/12/documenting-software-architecture/

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

Однако, когда нам нужно объяснить кому-то (новому разработчику, владельцу продукта, инвестору,…), как работает приложение, нам нужно нечто большее… нам нужна документация.

Но какие у нас есть варианты документации, которые могут выразить целые строительные блоки приложения и как это работает ?!

В этом посте я собираюсь написать о:

  • UML
  • 4 + 1 Архитектурный вид модели
  • Отчеты о решениях по архитектуре
  • Модель C4
  • Диаграммы зависимостей
  • Карта приложения

UML

Есть несколько диаграмм, которые мы можем создать с помощью UML, и мы можем разделить их на две категории:

Поведенческая UML-диаграмма

Структурная диаграмма UML

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

В целом, UML — это хороший вариант для быстрого прототипирования идей и обсуждения их с коллегами.

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

Примером правильного использования диаграммы классов UML является документирование шаблонов проектирования:

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

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

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

Но тогда остается вопрос: как нам оформить полную картину ?!

Модель представления архитектуры “4 + 1”

Модель представления архитектуры 4+1 была создана Филиппом Крухтеном и опубликована в 1995 г. в его статье « Architectural Blueprints — The “4+1” View Model of Software Architecture».

Этот способ визуализации архитектуры программного приложения основан на 5 представлениях / ракурсах приложения, сообщающих нам, какие диаграммы можно использовать для документирования каждого из этих представлений.

  1. Логическое / Структурное устройство (Logical/Structural view)
    Отображает функциональность, предоставляемую системой, и то, как код разработан для обеспечения такой функциональности;
  2. Реализация (Implementation/Developer view)
    Отображает статическую организацию кода, компонентов, модулей и пакетов;
  3. Поведение (Process/Behaviour view)
    Фокусируется на поведении системы во время выполнения, на том, как системные процессы взаимодействуют, параллелизме, синхронизации, производительности и т. д.;
  4. Физическое устройство (Deployment/Physical view)
    Иллюстрирует физическую организацию приложения с точки зрения того, «какой код работает на каком оборудовании»;
  5. Сценарии использования (Use Case/Scenario view)
    Архитектура в целом объясняется с помощью нескольких вариантов использования, которые представляют собой просто последовательность действий. Часть архитектуры развивается из таких вариантов использования.

Важно отметить, что “4+1" не требует, чтобы мы использовали все упомянутые диаграммы и даже не все типы диаграмм. Мы всегда должны выбирать инструменты по задаче, а не наоборот.

Отчеты о решениях по архитектуре

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

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

Для начала, есть несколько моментов, которые нам нужно знать:

  • Архитектурно-значимое требование (Architecturally-Significant Requirement (ASR)) : требование, которое оказывает ощутимое влияние на архитектуру программной системы;
  • Архитектурное решение (Architecture Decision (AD)) : выбор архитектуры программного обеспечения, который отвечает требованиям;
  • Журнал принятия решения об архитектуре (Architecture Decision Record (ADR) ) : документ, который фиксирует важное архитектурное решение, принятое вместе с его контекстом и последствиями;
  • Журнал принятия решений об архитектуре (Architecture Decision Log (ADL)) : коллекция всех ADR, созданных и поддерживаемых для конкретного проекта (или организации);
  • Архитектура управления знаниями (Architecture Knowledge Management (AKM)) : высшая сфера всех предыдущих концепций.

Я видел несколько шаблонов для создания ADR. На основе этого опыта я создал свой собственный шаблон. Вы можете создать свой, который имеет смысл в вашем проекте и команде.

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

Лучший способ использовать ADR — это не просто документ, написанный после обсуждения и принятия решения. Лучше всего использовать его в качестве отправной точки для обсуждения, в качестве RFC (Request For Comments), который представляет собой идею / предложение, которое мы представляем другим членам команды / отдела, запрашивая их мнение / одобрение. Намерение действительно состоит в том, чтобы использовать его, чтобы начать обсуждение, провести мозговой штурм, принять наилучшее возможное решение и использовать сам документ предложения в качестве записи в журнале решений (ADR). Тот факт, что ADR написан заранее, не означает, что он является неизменным, его необходимо обновлять / улучшать по мере развития обсуждения. Я считаю особенно важным, чтобы все рассматриваемые варианты были записаны с указанием их плюсов и минусов, чтобы спровоцировать дискуссию и принять четкое решение.

Итак, вот шаблон, который я придумал:

Не стесняйтесь копировать его из Google Docs .

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

Модель C4

Модель C4 была представлена ​​Саймоном Брауном, и это лучшая идея в документации по архитектуре программного обеспечения, с которой я когда-либо сталкивался. Я быстро объясню основную идею своими словами, хотя использую собственные примеры диаграмм.

Идея состоит в том, чтобы использовать 4 различных уровня гранулярности (или масштабирования) для документирования архитектуры программного обеспечения:

  • Уровень 1: Диаграмма контекста системы (System Context diagram)
  • Уровень 2: Контейнерная диаграмма (Container diagram)
  • Уровень 3: Диаграмма компонентов (Component diagram)
  • Уровень 4: Кодовая диаграмма (Code diagram)

Уровень 1: Диаграмма контекста системы

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

Уровень 2: Контейнерная диаграмма

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

Уровень 3: Диаграмма компонентов

Диаграмма компонентов показывает нам компоненты внутри одного контейнера. В этом контексте каждый компонент является модулем приложения, не ограниченным доменными модулями (т. е. Биллинг, пользователи,…), но также включающим в себя чисто функциональные модули (т. е. Электронная почта, смс,…). Итак, эта диаграмма показывает нам основные шестеренки контейнера и отношения между этими шестернями .

Уровень 4: Код

Наиболее точная диаграмма, предназначенная для описания структуры кода внутри компонента . Для этого уровня мы используем диаграмму UML с элементами уровня класса.

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

Чего еще не хватает ?!

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

На этих диаграммах я вижу три ограничения:

  1. За исключением некоторых исключений, таких как structurizr Саймона Брауна , их нужно создавать вручную, а не автоматически или извлекать непосредственно из кода, что означает, что они могут отражать не реальный код, а наше текущее представление этого кода;
  2. Они совсем не помогают нам увидеть проблемы в нашем коде приложения, с точки зрения разнородных отношений кода и плохой архитектуры, которая влияет на модульность и инкапсуляцию, довольно важную для разработки любого продукта;
  3. Они не помогают нам понять наш код в целом. Как между собой взаимодействуют шестеренки.

Я нашел две категории диаграмм, которые могут помочь нам в этом.

Диаграммы зависимостей

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

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

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

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

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

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

Диаграмма зависимостей слоя

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

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

Диаграмма зависимостей классов

Диаграмма зависимостей Layer анализирует зависимости между слоями, но внутри слоя все еще есть зависимости, которые не должны возникать.

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

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

Диаграмма зависимостей компонентов

Компонент является доменным модулем, который содержит как прикладной, так и доменный уровни. Компонентом может быть, например, «Биллинг», содержащий все его варианты использования и доменную логику.

Компоненты могут быть сопоставлены с контекстами и / или микросервисами, ограниченными DDD, что означает, что они должны быть полностью отделены, физически и временно, от других компонентов. Если у нас есть монолитное приложение с полностью отделенными компонентами, будет довольно легко (с точки зрения кода) преобразовать его в микросервисную архитектуру.

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

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

Обратите внимание, что на диаграмме ниже показано, как модули одного слоя (узлы одного цвета) не знают друг друга, по крайней мере, напрямую.

Особенно важно, что эти два компонента (Пользователь и Блог, в синем цвете) отделены. Если бы это приложение имело микросервисную архитектуру, эти два компонента были бы микросервисами.

Карта приложения

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

Поэтому я предложил новую диаграмму документации, которую я называю картой приложения , которая может заменить диаграмму компонентов модели C4.

Карта Заявки направлена на быть действительно картой приложения, определяя его «города» (Components), свои «местные дороги» (случаи использования), «шоссе» (события), и т.д.

Разница между модулями и компонентами , что модулем является любой модульной частью приложения, в то время как компонент представляет собой домен мудрой модуль приложения. Таким образом, хотя ORM является модулем приложения, он не является компонентом, поскольку занимается только техническими вопросами. С другой стороны, модуль «Биллинг» является компонентом, потому что он занимается проблемами домена.

Карта приложения начинается с определения компонентов приложения, доменных модулей, таких как «Выставление счетов», «Пользователь», «Компания», «Заказы», ​​«Продукты» и так далее. В случае простого блогового приложения у нас может быть два компонента, «Пользователь» и «Блог»:

В каждом из этих компонентов мы определяем, какие команды им могут быть даны. Компонент «Пользователь» может создавать и удалять пользователей, а компонент «Блог» может создавать и удалять сообщения, а также создавать комментарии к сообщению.

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

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

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

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

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

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

Однако это все еще не говорит нам, как все эти возможности связаны друг с другом, например, «что происходит в результате того, что пользователь создает сообщение в блоге?».

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

На изображении ниже мы видим, что удаление сообщения («DeletePost») вызовет метод deletePost () в PostService, который также запускается слушателем, прослушивающим событие, которое уведомляет, что пользователь был удален. Это говорит нам о том, что наше приложение удаляет сообщения в результате прямой команды пользователя или после удаления автора сообщения.

В пользовательском компоненте мы видим, что при создании сообщения его автор автоматически подписывается на темы сообщения (теги).

Теперь у нас есть информация о потоке внутри компонента, но нам все еще не хватает информации о межкомпонентном потоке, поэтому давайте добавим события, которые запускаются и прослушиваются:

Мы можем видеть, например, что:

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

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

Но при использовании в большом приложении эта диаграмма все равно будет иметь проблемы, общие для ранее упомянутых диаграмм:

  1. Это артефакт, который потребует много усилий и времени, чтобы сделать это, а также просто поддерживать его в актуальном состоянии;
  2. У нас все еще будет большая диаграмма с множеством линий, которая не самая читаемая.

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

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

Итак, нам нужен инструмент … которого нет … пока!

Или это ?!

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

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

--

--