Модель C4 в Structurizr: шаблоны для системного аналитика

bfdc6ebcaa749ef526b240b476d99559.png

Привет Хабр! Меня зовут Татьяна Ошуркова, я разработчик, аналитик и автор телеграм-канала IT Talks. Большой объем документации и её классическое представление сегодня нередко уходят на второй план, а популярность подхода «Документация как код» (Docs as Code) растет с каждым днем. Поэтому сейчас особенно актуально использование инструментов для текстового описания различных диаграмм.

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

Что такое Structurizr?

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

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

Structurizr — это инструмент для моделирования архитектуры программного обеспечения, основанный на C4-модели (Context, Container, Component, Code). Он позволяет создавать диаграммы архитектуры на разных уровнях детализации, используя текстовое описание на языке Structurizr DSL (Domain-Specific Language) или программный API (например, на Java, TypeScript, C#).

DSL (Domain-Specific Language) — это язык, предназначенный для решения задач в конкретной предметной области. В отличие от универсальных языков программирования, DSL упрощает работу с определенным видом данных или процессов, предоставляя удобный синтаксис и команды. В контексте Structurizr DSL — это текстовый формат, который позволяет описывать архитектурные диаграммы C4-модели. Он используется для создания схем путем текстового описания компонентов системы, их взаимодействий и визуальных представлений.

Structurizr поддерживает генерацию диаграмм не только с помощью DSL, но и через программные API для Java, TypeScript, C#, Python. Это значит, что можно интегрировать создание диаграмм в код. В Java существует официальный API Structurizr, позволяющий создавать модели архитектуры программным способом и экспортировать их в DSL, а в Python можно генерировать DSL-файлы или отправлять модели в Structurizr через API.

Постановка задачи

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

У нас есть система управления заказами, которая взаимодействует с несколькими внешними API:

  • Платежный сервис (например, Stripe) для обработки платежей.

  • Аналитическая система для сбора и анализа данных о заказах.

  • CRM-система для передачи данных о клиентах и заказах.

Мы построим три диаграммы:

  1. Общая схема интеграции — показывает все внешние взаимодействия системы.

  2. Разбиение на контейнеры — детализация внутренней архитектуры системы.

  3. Детализация взаимодействия с API — описание конкретного API (например, платежного сервиса).

Общая схема интеграции

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

workspace {
    model {
        customer = person "Пользователь" "Оформляет заказы."
        orderSystem = softwareSystem "Система управления заказами" "Обрабатывает заказы."
        paymentAPI = softwareSystem "Платежный сервис" "Обрабатывает платежи."
        analyticsAPI = softwareSystem "Аналитическая система" "Анализирует данные о заказах."
        crmSystem = softwareSystem "CRM-система" "Управляет клиентами."

        customer -> orderSystem "Оформляет заказ"
        orderSystem -> paymentAPI "Отправляет платежные данные"
        orderSystem -> analyticsAPI "Передает данные о заказах"
        orderSystem -> crmSystem "Передает информацию о клиенте"
    }
    
    views {
        systemContext orderSystem {
            include *
            autolayout lr
        }
        theme default
    }
}
  1. Определяем основные элементы: создаем пользователя (person) и систему управления заказами (softwareSystem).

  2. Добавляем внешние API: создаем три новых softwareSystem для платежного сервиса, аналитической системы и CRM-системы.

  3. Устанавливаем связи: определяем взаимодействие с помощью →, указывая передаваемые данные.

  4. Создаем представление: добавляем views с systemContext, включаем все элементы, активируем autolayout lr.

f844d41b35a6bcb1efb5566253457751.png

Разделение на контейнеры

Эта диаграмма соответствует уровню Container в C4-модели и предназначена для детализации внутренней структуры системы. Она демонстрирует, какие контейнеры (основные программные компоненты) составляют систему, как они взаимодействуют между собой и какие внешние зависимости у них есть. В отличие от уровня Context, который фокусируется на границах системы и её внешних связях, уровень Container показывает внутреннее устройство системы, её ключевые сервисы и их взаимодействие.

workspace {
    model {
        orderSystem = softwareSystem "Система управления заказами" "Обрабатывает заказы." {
            webApp = container "Web-приложение" "Интерфейс для клиентов." "Web Application"
            apiGateway = container "API Gateway" "Обрабатывает запросы клиентов." "Server-side Application"
            orderService = container "Order Service" "Управляет заказами." "Server-side Application"
            analyticsService = container "Analytics Service" "Обрабатывает данные заказов." "Server-side Application"
            paymentService = container "Payment Service" "Обрабатывает платежи." "Server-side Application"
        }
        paymentAPI = softwareSystem "Платежный сервис"
        analyticsAPI = softwareSystem "Аналитическая система"
        crmAPI = softwareSystem "CRM-система"
        
        webApp -> apiGateway "Отправляет запросы"
        apiGateway -> orderService "Передает запрос на заказ"
        orderService -> paymentService "Вызывает оплату"
        orderService -> analyticsService "Передает данные о заказах"
        orderService -> crmAPI "Передает данные о клиентах"
        paymentService -> paymentAPI "Отправляет платежные данные"
        analyticsService -> analyticsAPI "Передает агрегированные данные"
    }
    
    views {
        container orderSystem {
            include *
            autolayout lr
        }
        
        theme default
    }
}
  1. Определяем основную систему: создаем softwareSystem для системы управления заказами.

  2. Разбиваем систему на контейнеры: добавляем Web-приложение, API Gateway, Order Service, Analytics Service, Payment Service и CRM Service.

  3. Добавляем внешние API: создаем softwareSystem для платежного сервиса, аналитической системы и CRM-системы.

  4. Определяем связи между контейнерами: указываем, как Web-приложение взаимодействует с API Gateway, как сервисы обмениваются данными, как передаются запросы в внешние API.

  5. Создаем представление диаграммы: добавляем views с container orderSystem, включаем все элементы и активируем autolayout lr.

7629bfa2540de6e9cac00e8374ef8794.png

Детализация взаимодействия

Эта диаграмма соответствует уровню Component в C4-модели и предназначена для детализации конкретных сервисов системы. В отличие от уровня Container, который показывает основные программные блоки и их связи, уровень Component фокусируется на внутренних элементах каждого контейнера и их взаимодействиях. Эта детализация позволяет глубже понять логику работы сервисов и их интеграцию. Рассмотрим построение диаграммы на примере paymentService и orderService.

workspace {
    model {
        orderSystem = softwareSystem "Система управления заказами" "Обрабатывает заказы." {
            orderService = container "Order Service" "Управляет заказами." "Server-side Application" {
                orderController = component "Order Controller" "Обрабатывает входящие запросы"
                orderProcessor = component "Order Processor" "Обрабатывает заказы"
                orderRepository = component "Order Repository" "Доступ к базе заказов"
                eventDispatcher = component "Event Dispatcher" "Рассылка событий"
            }
            
            paymentService = container "Payment Service" "Обрабатывает платежи." "Server-side Application" {
                paymentController = component "Payment Controller" "Принимает платежные запросы"
                paymentProcessor = component "Payment Processor" "Обрабатывает платежи"
                paymentValidator = component "Payment Validator" "Проверяет корректность платежей"
                transactionManager = component "Transaction Manager" "Управляет транзакциями"
            }
            
            orderDB = container "Order DB" "Хранит данные о заказах" "Database"
            paymentDB = container "Payment DB" "Хранит данные о платежах" "Database"
        }

        paymentAPI = softwareSystem "Платежный сервис"

        # Взаимодействие внутри Order Service
        orderController -> orderProcessor "Передает заказ на обработку"
        orderProcessor -> orderRepository "Запрашивает данные заказов"
        orderProcessor -> eventDispatcher "Отправляет события"
        orderRepository -> orderDB "Сохраняет и получает данные заказов"

        # Взаимодействие внутри Payment Service
        orderProcessor -> paymentController "Вызывает оплату"
        paymentController -> paymentProcessor "Обрабатывает платеж"
        paymentProcessor -> paymentValidator "Проверяет платеж"
        paymentProcessor -> transactionManager "Создает транзакцию"
        transactionManager -> paymentDB "Сохраняет данные о платежах"
        transactionManager -> paymentAPI "Отправляет платежные данные"
    }
    
    views {
        container orderSystem {
            include *
            autolayout lr
        }
        
        component orderService {
            include *
            autolayout lr
        }

        component paymentService {
            include *
            autolayout lr
        }

        theme default
    }
}
  1. Создаем рабочую область workspace с моделью системы и представлениями.

  2. В model определяем систему orderSystem, содержащую два сервиса (Order Service и Payment Service), базы данных (Order DB и Payment DB) и внешнюю систему paymentAPI.

  3. Внутри сервисов создаем компоненты (Controller, Processor, Repository и др.), определяя их роли и связи.

  4. Устанавливаем взаимодействия между элементами системы: обработка заказов, работа с базой данных, вызовы платежей и интеграция с paymentAPI.

  5. В views создаем три диаграммы: container orderSystem для контейнеров, component orderService для компонентов Order Service, component paymentService для компонентов Payment Service.

  6. Добавляем autolayout lr для удобного расположения элементов и применяем стандартную тему.

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

Structurizr позволяет создавать несколько представлений одной и той же системы. В данном случае формируются 3 диаграммы, в том числе и диаграмма контейнеров. Это происходит из-за структуры кода в views и того, как Structurizr DSL интерпретирует вложенность элементов. Разберем подробнее:

  1. container orderSystem создает диаграмму контейнеров. Она включает все элементы внутри orderSystem, то есть сервисы (Order Service, Payment Service), базы данных (Order DB, Payment DB) и внешний paymentAPI.

    6bca5fbe557694bc88d7616d35de3a4a.png
  2. component orderService и component paymentService должны бы создавать диаграммы компонентов для каждого сервиса, но внутри них используется include *. Это приводит к тому, что в диаграмму компонентов попадает не только уровень компонентов (Controller, Processor, Repository и т. д.), но и вложенные контейнеры (если они есть).

f4ce8724e5b5348fb8d1e471d99ec31b.png052a9b187fd68552cc54215361f17541.png

Подведем итоги

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

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

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

Удачи в работе!

IT Talks | Ошуркова Татьяна
t.me

© Habrahabr.ru