Введение

В данной статье содержится вольный перевод документации по Structurizr.

Из-за большого объема информация разделена на три статьи:

Пример

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

workspace {

    model {
        user = person "User"
        softwareSystem = softwareSystem "Software System"

        user -> softwareSystem "Uses"
    }

    views {
        systemContext softwareSystem {
            include *
            autolayout lr
        }
    }
    
}

Данному коду соответствует следующая диаграмма:

Основы

Правила DSL

  • переносы строк учитываются: длинные строки можно разделять символом “\”;

  • строки обрабатываются по порядку;

  • токены должны быть разделены пробелами, но количество пробелов и отступов не имеет значения;

  • ключевые слова не чувствительны к регистру;

  • двойные кавычки необязательны, если свойство/выражение не содержит пробелов;

  • открывающая фигурная скобка “{” должна находиться в одной строке с выражением (то есть в последнем символе инструкции, а не в отдельной строке);

  • закрывающая фигурная скобка “}” должна располагаться на отдельной строке;

  • открывающие и закрывающие фигурные скобки требуются только при добавлении дочернего содержимого;

  • используйте "" в качестве значения по умолчанию для необязательных свойств;

  • теги разделяются запятыми.

Правила рабочего пространства

На рабочие пространства распространяются следующие правила:

  • представление имеет уникальный “ключ” (генерируется, если не указан; автоматически сгенерированные ключи не гарантируют стабильности с течением времени);

  • наименования ПО и имена сотрудников должны быть уникальными;

  • наименования контейнеров должны быть уникальными в контексте программной системы;

  • наименования компонентов должны быть уникальными в контексте контейнера;

  • наименования узлов развертывания должны быть уникальными для их родительского контекста;

  • наименования узлов инфраструктуры должны быть уникальными для их родительского контекста;

  • все связи от исходного элемента к целевому элементу должны иметь уникальное описание.

Подразумеваемые связи

!impliedRelationships <true|false>

Ключевое слово !impliedRelationships предоставляет способ включить или отключить создание подразумеваемых связей. Значение false отключает создание подразумеваемых отношений, в то время как true создает подразумеваемые отношения между всеми допустимыми комбинациями родительских элементов, если только между ними уже не существует каких-либо отношений.

Константы

Ключевое слово !constant используется для определения константы. Константы нужны для выполнения подстановок (описание ниже).

!constant <name> <value>

Имена констант должны состоять из символов: a-zA-Z0-9-_.

Подстановки

Подстановки производятся в любом тексте, указанном в токене, используя синтаксис ${NAME}, где NAME — наименование константы или переменной окружения.

!constant ORGANISATION_NAME "Organisation"
!constant GROUP_NAME "Group"

workspace {

    model {
        group "${ORGANISATION_NAME} - ${GROUP_NAME}" {
            user = person "User"
        }
    }

}

Если соответствующая константа или переменная окружения не найдены, подстановка не будет выполнена.

Комментарии

/*
    multi-line comment
*/
/* single-line comment */
# single line comment
// single line comment

Значения по умолчанию

DSL спроектирован максимально компактным. При использовании в сочетании со Structurizr Lite или Structurizr CLI для следующего фрагмента:

  • будет создана подразумеваемая взаимосвязь между user и softwareSystem;

  • создан набор представлений по умолчанию;

  • добавлены несколько стилей элементов по умолчанию из темы.

workspace {

    model {
        user = person "User"
        softwareSystem = softwareSystem "Software System" {
            webapp = container "Web Application"
            database = container "Database"
         }

        user -> webapp "Uses"
        webapp -> database "Reads from and writes to"
    }
    
    views {
    	theme default
    }

}

Идентификаторы

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

person "User"
softwareSystem "Software System"

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

p = person "User"
ss = softwareSystem "Software System"

Теперь можно использовать эти идентификаторы при создании связей, указывая, какие элементы следует включать/исключать из представлений и так далее.

p -> ss "Uses"

Связи также может быть присвоен отдельный идентификатор:

rel = p -> ss "Uses"

Идентификаторы нужны там, где планируются ссылки на элемент/связь.

При определении идентификатора можно использовать символы: a-zA-Z_0-9.

Область действия идентификатора

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

workspace {

    model {
        softwareSystem1 = softwareSystem "Software System 1" {
            api = container "API"
        }

        softwareSystem2 = softwareSystem "Software System 2" {
            api = container "API"
        }
    }
}

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

workspace {

    !identifiers hierarchical

    model {
        softwareSystem1 = softwareSystem "Software System 1" {
            api = container "API"
        }

        softwareSystem2 = softwareSystem "Software System 2" {
            api = container "API"
        }
    }
}

Теперь на два контейнера API можно ссылаться через идентификаторы softwareSystem1.api и softwareSystem2.api соответственно.

Выражения

Structurizr DSL поддерживает “выражения” для использования при включении или исключении элементов/связей в представлениях (за исключением динамических представлений). Выражения следует заключать в кавычки, если они содержат пробелы. Например:

include "element.tag==Tag 1" // (correct)
include element.tag=="Tag 1" // (incorrect)

Выражения для элементов

-><identifier|expression> // указанный элемент и принимающий элемент
<identifier|expression>-> // указанный элемент и передающий элемент
-><identifier|expression>-> // указанный элемент плюс передающий и принимающий элемент
element.type==<type> // элемент указанного типа
// (Person, SoftwareSystem, Container, Component, DeploymentNode,
// InfrastructureNode, SoftwareSystemInstance, ContainerInstance, Custom)
element.parent==<identifier> // элемент с указанным родительским элементом
element.tag==<tag>[,tag] // все элементы, имющие все указанные теги
element.tag!=<tag>[,tag] // все элементы, которые не имеют всех указанных тегов
element==-><identifier> // указанный элемент и приниающая связь
element==<identifier>-> // указанный элемент и передающая связь
element==-><identifier>-> // указанный элемент плюс передающая и принимающая связь

Выражения для связей

*-> // все связи
<identifier>->* // все связи с указанным исходным элементом
*-><identifier> // все связи с указанным целевым элементом
relationship==* // все связи
relationship==*->*: // все связи
relationship.tag==<tag>[,tag] // связи, имеющие все указанные теги
relationship.tag!=<tag>[,tag] // все связи, которые не имеют всех указанных тегов
relationship.source==<identifier> // все связи с указанным исходным элементом
relationship.destination==<identifier> // все связи с указанным целевым элементом
relationship==<identifier>->* // все связи с указанным исходным элементом
relationship==*-><identifier> // все связи с указанным целевым элементом
relationship==<identifier>-><identifier> // все связи между двумя указанными элементами

Включение файлов

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

!include <file|directory|url>

Для включения можно использовать:

  • file: отдельный локальный файл с относительным путем, расположенный в том же каталоге, что и родительский файл, или в его подкаталоге;

  • directory: локальный каталог, содержащий один или несколько файлов DSL, с относительным путем, расположенный в том же каталоге, что и родительский файл, или в его подкаталоге;

  • url: URL-адрес (HTTPS), указывающий на один DSL-файл.

Примеры использования:

!include people.dsl
!include model/people.dsl
!include model
!include https://example.com/model/people.dsl

Документация

Ключевое слово !docs используется для присоединения документации Markdown/AsciiDoc к родительскому контексту (рабочей области, программной системе или контейнеру).

!docs <path> [fully qualified class name]

Путь должен быть относительным, расположен в том же каталоге, что и родительский файл, или в его подкаталоге. Например:

!docs subdirectory

Класс по умолчанию использоваться для импорта документации следующим образом:

  1. Все файлы Markdown и AsciiDoc в указанном каталоге будут импортированы в алфавитном порядке в соответствии с именем файла.

  2. Все изображения в данном каталоге (и подкаталогах) также импортируются в рабочую область.

  3. Заголовки разделов и нумерация обрабатываются особым образом.

Описанное выше поведение можно настроить, указав полное имя класса вашей собственной реализации DocumentationImporter, которое должно быть указано в пути к классу DSL или установлено в виде JAR-файла в каталоге plugins рядом с вашим DSL-файлом.

Архитектурные решения (ADR)

Ключевое слово !adrs используется для присоединения Markdown/AsciiDoc ADR к родительскому контексту (рабочей области, программной системе или контейнеру).

!adrs <path> [fully qualified class name]

Путь должен быть относительным, расположенным в том же каталоге, что и родительский файл, или в его подкаталоге. Например:

!adrs subdirectory

Класс по умолчанию использоваться для импорта ADR следующим образом:

  1. Все файлы Markdown в этом каталоге будут импортированы в алфавитном порядке в соответствии с именем файла.

  2. Файлы должны быть созданы с помощью adr-tools или, по крайней мере, соответствовать тому же формату.

  3. Все изображения в данном каталоге (и подкаталогах) также импортируются в рабочую область.

Описанное выше поведение можно настроить, указав полное имя класса вашей собственной реализации DocumentationImporter, которое должно быть указано в пути к классу DSL или установлено в виде JAR-файла в каталоге plugins рядом с вашим DSL-файлом.

Скрипты

Скрипты похожи на плагины, но их не нужно компилировать перед использованием. JavaScript (Nashorn движок JavaScript JVM считается устаревшим), Kotlin, Groovy и Ruby поддерживаются "из коробки", и вы можете добавить дополнительные языки с помощью Java Scripting API. Скрипты можно использовать в любой точке DSL.

Следующие переменные доступны из скриптов:

  • context;

  • workspace;

  • element: объект текущего элемента, если сценарий используется в рамках представления элемента;

  • relationship: объект текущего отношения, если сценарий используется в рамках представления отношения;

  • view.

Встроенные скрипты

Для подключения скрипта используется ключевое слово !script, за которым следует выбранный язык (groovy, kotlin, ruby или javascript). Например, следующий скрипт Groovy создаст набор представлений по умолчанию без включенной автоматической компоновки:

!script groovy {
    workspace.views.createDefaultViews()
    workspace.views.views.findAll { it instanceof com.structurizr.view.ModelView }.each { it.disableAutomaticLayout() }
}

Встроенные скрипты не могут содержать строку, содержащую только закрывающий символ }.

Внешние скрипты

Чтобы использовать внешний скрипт, создайте файл скрипта рядом с DSL-файлом (например, script.kts):

workspace.views.createDefaultViews()
workspace.views.views.forEach { it.disableAutomaticLayout() }

Затем можно подключить скрипт, используя ключевое слово !script:

!script script.kts

Поддерживаемые расширения файлов:

  • .groovy (Groovy),

  • .kts (Kotlin),

  • .rb (Ruby),

  • .js (JavaScript).

Параметры для внешних скриптов можно задать следующим образом:

!script script.kts {
    name value
}

Плагины

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

Чтобы написать плагин, создайте класс Java, который реализует интерфейс com.structurizr.dsl.StructurizrDslPlugin (вам нужно будет добавить зависимость от библиотеки DSL, которую можно найти в Maven Central через com.structurizr:structurizr-dsl).

package com.example;

import com.structurizr.Workspace;

public class TestPlugin implements StructurizrDslPlugin {

    @Override
    public void run(StructurizrDslPluginContext context) {
        Workspace workspace = context.getWorkspace();
        workspace.setName("Name set by plugin");
    }

}

Скомпилированный плагин, упакованный в виде JAR-файла (плюс любые другие зависимости JAR), должен быть помещен в каталог с именем plugins рядом с вашим DSL-файлом. Затем вы можете использовать свой плагин из DSL, с помощью ключевого слова !plugin.

workspace {

    !plugin com.example.TestPlugin

}

Параметры могут быть указаны, например, в теле плагина:

workspace {

    !plugin com.example.TestPlugin {
			name value      
    }

}

Именованные параметры становятся доступными с помощью метода getParameter(name) объекта StructurizrDslPluginContextobject.

Комментарии (1)


  1. shuchkin
    15.01.2024 09:48

    Отзовитесь кто пользуется, может есть какая киллер фича в новых версиях? Кроме поддержки легаси продуктов