Неочевидный PlantUML
https://t.me/humane_analystЭто продолжение статьи.
6. Другие полезные возможности
Настало время рассмотреть возможности, которые помогут повысить качество и осознанность в построении наиболее популярных диаграмм.
6.1. Для ценителей спецификации UML
В данном подразделе я собрал информацию, объединённую общей идеей более строгого следования требованиям спецификации языка UML. Обычно это необязательно, но может пригодиться.
6.1.1. Рамка для границы системы
В диаграммах вариантов использования очень удобно группировать имеющиеся варианты использования с помощью рамки, которая символизирует границы моделируемой системы.
Чтобы не повторяться, рекомендую обратиться к п. 4.2, где данный вопрос разбирался довольно подробно.
6.1.2. Стиль стрелок в диаграмме последовательности
Умолчательный вид стрелок в PlantUML не соответствует требованиям спецификации UML. Для того, чтобы исправить ситуацию, необходимо выполнить следующее: для синхронного запроса следует использовать стрелку ->, для асинхронного — ->>, а для ответных сообщений — -->>. Также потребуется включение инструкции skinparam style strictuml.
Поскольку этот вопрос рассматривался в п. 2.1, отдельно на этом останавливаться не буду, а лишь отмечу, что использование skinparam style strictuml приводит дополнительно к тому, что прямоугольники, представляющие участников на линиях жизни, перестают дублироваться внизу диаграмм. На больших «сиквенсах» это может быть не очень удобно, хотя это и соответствует требованиям UML.
@startuml ' Закомментируйте следующую строку для сравнения skinparam style strictuml Alice -> Bob: Синхронное сообщение Bob -->> Alice: Ответное сообщение Alice ->> Bob: Асинхронное сообщение Alice -> Alice: Рефлексивное сообщение @enduml

6.1.3. Изображение компонентов
В UML 2.X компоненты принято обозначать в виде прямоугольника со стереотипом <<component>>. Дополнительно в правом верхнем углу может быть изображена необязательная пиктограмма компонента (изображение в форме прямоугольника, в левую сторону которой вставлены 2 прямоугольника поменьше). При этом в случае использования пиктограммы указание стереотипа можно опустить. Иными словами, у графического изображения компонента обязательно должен быть задан стереотип и/или пиктограмма.
PlantUML по умолчанию выводит пиктограмму, а стереотип задаётся только вручную. В примере ниже как раз представлена такая ситуация.
@startuml ' Раскомментируйте следующие строки поочерёдно и сравните результат 'skinparam componentStyle rectangle 'skinparam componentStyle uml1 component [Заказ] as order <<component>> component [Компания] as company <<component>> order .> company @enduml

Что интересно, PlantUML позволяет отключить отрисовку пиктограммы компонента. Это делается с помощью инструкции skinparam componentStyle rectangle. Однако, сделав это, стоит убедиться, что у всех компонентов вручную задан стереотип. В противном случае компоненты будут выглядеть как графическое изображение классов в форме прямоугольника.
Для полноты картины отмечу, что PlantUML также поддерживает графическое изображение компонентов, которое было принято в UML 1.X. Чтобы этого добиться, достаточно указать инструкцию skinparam componentStyle uml1. На практике же эта возможность вряд ли может понадобиться.
6.1.4. Видимость членов класса
При отображении диаграммы классов PlantUML маркирует видимость членов класса (атрибутов и операций) с помощью незатейливых цветных иконок: незакрашенные используются для атрибутов (полей), а закрашенные — для операций (методов).

Если не задавать видимость, то эти иконки не будут отображаться. Но что делать, если зафиксировать видимость нужно, но вы не являетесь поклонником «Игры в кальмара»? В такой ситуации поможет инструкция skinparam classAttributeIconSize 0. Она отключит использование цветных иконок и вернёт символы видимости языка UML.
6.1.5. Модификаторы членов класса
При детальной проработке классов может потребоваться акцентировать внимание на том, что те или иные члены класса являются статическими или абстрактными. PlantUML позволяет это сделать, для чего надо перед описанием соответствующего члена класса указать в квадратных скобках слово «static» или «abstract» соответственно.
Фрагмент кода для примера:
{static} String id
{abstract} void methodA()
6.1.6. Метки классов
PlantUML по умолчанию отображает собственные метки для классов и иных поддерживаемых им видов классификаторов (интерфейсов, перечислений, абстрактных классов, метаклассов, структур и пр.). Метка представляет собой символ, вписанный в окружность и расположенный слева от названия классификатора.
Для классов используется метка «C», для интерфейсов — «I», для перечислений — «E», а для абстрактных классов — «A», для метаклассов — «M» и т.д. Также можно задавать и свои собственные метки, но что делать, если хочется избавиться от них вообще? В такой ситуации можно воспользоваться одной из двух инструкций: skinparam style strictuml или hide circle.
6.1.7. Стереотипы
Ещё одно украшательство, широко распространённое в PlantUML, — это использование типографских кавычек («ёлочек») для отображения стереотипов. Однако и его можно отключить, заставив отображать стереотипы строго с применением простых угловых скобок. Это выполняется с помощью инструкции skinparam guillemet false.
О чём стоит сказать: упомянутая инструкция универсальна, её можно применять на различных видах диаграмм (вариантов использования, классов, последовательности и пр.). Однако она почему-то не отрабатывает на зависимостях (связях, стрелках). К примеру, если вы используете стереотипы <<extend>> и <<include>> на диаграммах вариантов использования (в п. 4.3.4 разбиралась подобная ситуация), то на диаграмме у вас всегда будет написание «extend» и «include». Возможно, в будущем этот момент будет доработан, но пока так.
Отключать ли кавычки-«ёлочки», решать вам, но я предпочитаю этого не делать. Лично мне предлагаемая PlantUML визуализация мне нравится больше.
Ниже представлен ещё один пример работы со стереотипами. Заодно здесь можно посмотреть, как можно выкрутиться в ситуации, когда нужно на одной диаграмме показать отношения между компонентами и классами.
@startuml ' Раскомментируйте следующую строку и сравните результат 'skinparam guillemet false ' На стереотип в строке ниже инструкция повлияет component [Заказ] as order <<component>> rectangle "Заголовок заказа" as hdr rectangle "Строка товара" as line ' На стереотипы в строках ниже инструкция не повлияет hdr .up.> order : <<implement>> line .up.> order : <<implement>> @enduml

6.1.8. Сокрытие пустых членов класса
При концептуальном проектировании с помощью диаграмм классов UML зачастую нет необходимости прорабатывать атрибутивный состав и, особенно, перечень операций классов. Достаточно понять перечень сущностей и отношений между ними. В таком случае будет хорошей идеей указать инструкцию hide empty members. Это позволит скрыть пустые секции из графических изображений классов (прямоугольников). Да и к тому же это вполне нормальный с точки зрения UML подход.
Ниже приведён код, с помощью которого можно наглядно увидеть, как работает эта и ряд других, рассмотренных выше, инструкций. Всё в одном месте.
@startuml
' Поочерёдно раскомментируйте следующие строки
'skinparam style strictuml
'skinparam classAttributeIconSize 0
'skinparam guillemet false
'hide empty members
'hide circle
interface INumerable
abstract class AbstractClass {
-field1
#field2
' Абстрактные методы
~{abstract} method1()
+{abstract} method2()
}
class SomeClass {
-field3
#field4
~method1()
+method2()
#method3()
-method4()
' Обратный слэш позволяет гарантировать символ ~ в названии деструктора
+\~SomeClass()
}
' Вот так можно задать метку (буква и цвет)
class SystemClass << (S, #FF7700) Singleton >> {
' Следующее поле является статическим
-{static} staticField
+getInstance()
}
AbstractClass <|-left- SomeClass
@enduml

6.2. Легенды к диаграммам
Не все знают, но PlantUML позволяет добавлять к диаграммам заголовки и легенды (подробнее в документации). Хотя, когда смотришь примеры из стандартной документации, может показаться, что оба эти элемента не сильно-то и полезны. И в большинстве случаев это действительно так, но иногда польза может быть.
Предположим, что мы разрабатываем диаграмму состояний UML и хотим цветом выделить дополнительные, но важные для нас сведения (примеры такой техники разбирались в моей статье). Путь это будет информация о наличии неавтоматизированных участков бизнес-процесса работы с договорами, а также сведения о покрытии бизнес-метриками операций, выполняемых в рамках соответствующих состояний. Диаграмма для такой задачи могла бы выглядеть следующим образом.
@startuml
' Немного красок
skin rose
skinparam legend {
BackgroundColor Snow
FontColor Grey
}
' Чтобы пустые секции состояний не отрисовывались
hide empty description
' При создании состояний задаём цвета, которые будут поясняться в легенде
state Created as "Cоздан" #LemonChiffon
state Negotiation as "На согласовании" #LemonChiffon
state Revision as "На доработке" #LemonChiffon
state Rejected as "Отвергнут" #Pink
state Signed as "Подписан" #PaleGreen
state Closed as "Завершён" #PaleGreen
[*] --> Created
Created --> Negotiation
Negotiation -left-> Revision
Revision -right-> Negotiation
Negotiation --> Rejected
Negotiation --> Signed
Signed --> Closed
Rejected --> [*]
Closed --> [*]
' Далее формируется легенда к диаграмме
legend
**Условные обозначения**
|= |= Пояснение |
|<back:#LemonChiffon> </back>| Автоматизировано, метрик нет |
|<back:#PaleGreen> </back>| Автоматизировано, есть метрики |
|<back:#Pink> </back>| Неавтоматизировано |
endlegend
@enduml

Как можно заметить, было применено цветовое кодирование, а расшифровка использованных цветов была вынесена в легенду, причём легенда выполнена в форме таблицы с цветовой заливкой ячеек первой колонки.
Легенду также можно использовать и для других целей. К примеру, можно показать, за какой участок работ отвечает сотрудник (или пользователь системы) с той или иной ролью. Чтобы было более красиво, сделаем это с использованием подключения внешней библиотеки со спрайтами (о спрайтах в стандартной документации можно прочитать здесь, а о конкретной библиотеке из примера — здесь).
@startuml
' Немного красок
skin rose
skinparam legend {
BackgroundColor Snow
FontColor Grey
}
' Подключение библиотеки со спрайтами (иконками)
!$ICONURL = "https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/v3.0.0/icons"
!includeurl $ICONURL/common.puml
!includeurl $ICONURL/font-awesome-5/user_cog.puml
!includeurl $ICONURL/font-awesome-5/user_edit.puml
!includeurl $ICONURL/font-awesome-5/user_check.puml
' Объявление переменных для удобного доступа к импортированным иконкам, а также их уменьшение в размерах
!$user_cog = "<$user_cog{scale=0.4}>"
!$user_edit = "<$user_edit{scale=0.4}>"
!$user_check = "<$user_check{scale=0.4}>"
' Чтобы пустые секции состояний не отрисовывались
hide empty description
state Created as "Cоздан"
state Negotiation as "На согласовании"
state Revision as "На доработке"
state Rejected as "Отвергнут"
state Signed as "Подписан"
state Closed as "Завершён"
' Обращение к иконкам выполняется через объявленные переменные
[*] --> Created : Создать\n$user_cog
Created --> Negotiation : Передать на ознакомление\n$user_cog
Negotiation -right-> Revision : Передать на доработку\n$user_cog
Revision -left-> Negotiation : Продолжить согласование\n$user_check
Revision -> Revision : Редактура\n$user_edit
Negotiation -left-> Rejected : Пометить к удалению\n$user_cog
Negotiation --> Signed : Зарегистрировать\n$user_cog
Signed --> Closed : Передать в архив\n$user_cog
Rejected --> [*]
Closed --> [*]
' Далее формируется легенда к диаграмме
legend
**Условные обозначения**
|= |= Пояснение |
|$user_cog| Документопроизводитель |
|$user_edit| Редактор |
|$user_check| Контролёр службы качества |
endlegend
@enduml

Стоит сказать, что я рассмотрел только 2 случая применения легенд, но спектр задач, в которых они могут быть полезны, точно шире.
6.3. Препроцессинг
PlantUML поддерживает препроцессинг, напоминающий тот, что используется в языках C/C++. Что это означает? А это означает, что можно формировать фрагменты кода, которые будут разворачиваться в конечные инструкции PlantUML. Это могут быть общие функциональные возможности или, наоборот, что-то ситуативное.
Это позволяет, к примеру, единожды объявить какую-то настройку или общее значение, а потом многократно её использовать. Так, при рассмотрении примера в п. 6.2 общий путь до библиотеки со спрайтами был вынесен в переменную $ICONURL, что в дальнейшем позволило не повторяться при импорте библиотек (инструкция !includeurl); а в примере в п. 7.1 благодаря мною написанным функциям $primary_key и $mandatory стало возможным единообразно оформлять все первичные ключи и обязательные поля для всей диаграмме.
Другая польза от использования препроцессинга в том, что после выделения общих параметров их в будущем можно будет очень легко изменить: правится только значение параметра, а все ссылки на диаграмме остаются обновятся автоматически.
Более подробно с вопросом можно и нужно ознакомиться в следующих разделах документации:
А напоследок я отмечу, что импорт внешних файлов (библиотек) через инструкции !include и !includeurl может не сработать в вашей корпоративной сети, если соответствующие адреса из-под неё не доступны.
7. Прочие виды диаграмм
Эта статья начиналась со слов о доступных в PlantUML диаграммах, и теперь, дойдя до финала, предлагаю вернуться к этому же вопросу, но посмотреть на него с несколько иного ракурса. Мы поговорим про не самые широко применяемые виды диаграмм и доступные в них возможности.
7.1. Диаграмма «сущность-связь»
Как известно, в мире существует достаточно большое число нотаций для построения ER-диаграмм. Но на момент написания этих строк PlantUML поддерживает только следующие:
- нотация Чена;
- нотация Мартина («Воронья лапка»).
Вы можете ознакомиться с этими нотациями самостоятельно, я же ниже предлагаю более подробно остановиться на последней из них.
Для задания связей используется один из следующих вариантов.
- ноль или один:
|o-- - один и только один:
||-- - ноль или более:
}o-- - один или более:
}|--
Однако есть несколько аспектов, о которых не говорится в документации, но которые могут пригодиться. В PlantUML «Воронья лапка» является расширением диаграммы классов, которая, в свою очередь, как говорилось в п. 4.3.3, базируется на GraphViz. Как следствие, нам доступны следующие возможности.
- Вместо дефисов можно использовать точки, в результате чего линия связи станет пунктирной. Это может быть полезным, чтобы указать в явном виде на то, что та или иная связь является неидентифицирующей (по аналогии с IDEF1x или ERwin).
- Можно управлять направлением связей, рангами и стилями. Примеры:
||--righto{— направленная вправо связь со сплошным начертанием,|o..upo{— направленная вверх связь с пунктирным начертанием,|o...o{— направленная вниз удлинённая на 1 ранг пунктирная связь,||--right[hidden]||— направленная вправо скрытая связь,||--right[#fuchsia]||— направленная вправо связь цвета фуксии со сплошным начертанием. - С помощью инструкций
!skinparam nodesep Xи!skinparam ranksep Yможно раздвинуть сущности на экране, чтобы графические элементы связей не теснились и не накладывались друг на друга.
Вооружившись этим знанием, можно рассмотреть пример ER-диаграммы. За основу примера был взят код из документации к PlantUML (отсюда), но для иллюстрации различных возможностей я его расширил и немного стилизовал.
@startuml
!function $primary_key(fld) !return "<color:DarkGoldenRod><&key></color> " + fld
!function $mandatory(fld) !return "* " + fld
skin rose
hide circle
skinparam roundcorner 10
skinparam linetype ortho
skinparam nodesep 100
entity "Entity01" as e01 {
$primary_key(id) : number
--
name : text
description : text
}
entity "Entity02" as e02 {
$primary_key(id) : number
--
$mandatory(e1_id) : number <<FK1>>
$mandatory(e3_id) : number <<FK3>>
other_details : text
}
entity "Entity03" as e03 {
$primary_key(id) : number
--
e1_id : number <<FK1>>
other_details : text
}
entity "Entity04" as e04 {
$primary_key(id) : number
--
$mandatory(e1_id) : number <<FK1>>
other_details : text
}
e01 ||--right[hidden]|| e04
e01 ||--{ e02
e01 |o..{ e03
e01 |o...o{ e04
e02 }|--{ e03
e04 ||..o{ e04
@enduml

Если остановиться на стилизации, то выполнено следующее.
Для упрощения объявления ключевых и обязательных полей были добавлены две функции ($primary_key и $mandatory соответственно). Как уже отмечалось в п. 6.3 при рассмотрении препроцессинга, если в дальнейшем возникнет необходимость внести изменение в графическое оформление таких полей, то достаточно будет внести правки только в тексты этих функций, а остальной код модифицировать не придётся.
Затем применён скин rose (skin rose), отключены метки классов, скруглены углы сущностей; линии связей сделаны ортогональными (ortho), а расстояние между вершинами графа (сущностями) одного ранга увеличено до 100. В остальном всё довольно стандартно.
7.2. Диаграмма коммуникации
В документации к PlantUML вы не найдёте упоминаний диаграмм коммуникации UML (к слову, в UML 1.X они назывались диаграммами кооперации), но в некоторых задачах этот вид диаграмм может оказаться полезным. И вот тут на помощь снова приходят знания, полученные ранее.
Участников взаимодействия изображаем с помощью прямоугольников (ключевое слово rectangle) и человечков-акторов (ключевое слово actor). Расстановка участников на экране выполняется с помощью задания направленных связей (см. 4 раздел), причём некоторые из них могут быть скрытыми или с указанием опции [norank].
Для представления стрелок на сообщениях, передаваемых между участниками, будет разумным воспользоваться стандартными иконками (см. п. 3.3), стилизуемыми для единообразия с помощью вызова функции $arrow (см. п. 6.3). Логика этой функции предельно простая: она принимает в качестве аргумента текст с направлением стрелки, формирует на основе этого идентификатор иконки (так, к примеру, "right" превращается в <&arrow-right>) и далее устанавливает нужный размер.
@startuml
!function $arrow($param)
!local $res = "\n<size:20><&arrow-" + $param + "></size>"
!return $res
!endfunction
skin rose
actor Покупатель as buyer
rectangle "UI маркетплейса" as mp
rectangle "Каталог товаров" as cat
rectangle "Корзина" as cart
rectangle "Управление заказами" as ctrl
rectangle "Заказ" as order
buyer - mp : 1: Выбрать товар\n2: Добавить товар в корзину\n3: Подтвердить заказ $arrow("right")
mp -up- cat : 1.1: Получить товары $arrow("top")
mp - cart : 2.1: Добавить товар в корзину $arrow("right")
mp -- ctrl : 3.1: Подтвердить заказ $arrow("bottom")
ctrl -[norank]- cart : 3.2: Получить содержимое корзины $arrow("top")\n\n3.3: Товары из корзины $arrow("bottom")
ctrl -left- order : 3.5: Создать заказ $arrow("left")
ctrl - ctrl : 3.4: Проверить доступность $arrow("bottom")
@enduml

Визуально результат, конечно, далёк от идеала, да и с ростом числа элементов управлять их позиционированием на диаграмме становится всё сложнее, но для бесплатного инструмента получается вполне неплохо. К тому же мало какие инструменты вообще поддерживают «из коробки» диаграммы коммуникации.
7.3. Диаграмма пригодности
Если вы привыкли применять диаграммы пригодности ICONIX (Robustness diagram), то при работе с PlantUML вам не придётся от них отказываться.
Хотя этот вид диаграмм формально не поддерживается «плантом», но все графические элементы в нём есть. Ниже представлен слегка доработанный пример с этой страницы.
@startuml skin rose actor "Учитель" as teacher entity "БД учеников" as student_database boundary "Главное меню" as main_menu_screen boundary "Экран добавления ученика" as add_student_screen control "Вывести экран добавления ученика" as display_add_student_screen control "Вывести главное меню" as display_main_menu control "Вывести уведомление об успешном добавлении ученика" as display_notification_student_added_successful control "Проверить добавления ученика" as check_student control "Добавить ученика" as add_student control #ff6644 "Очистить формы" as clear_forms control #ff6644 "Вывести уведомление 'Логин занят'" as login_busy teacher -- main_menu_screen teacher -- add_student_screen add_student_screen -- display_main_menu main_menu_screen --> display_add_student_screen : "Нажата кнопка 'Добавить'" display_add_student_screen --> add_student_screen display_main_menu --> main_menu_screen add_student_screen --> check_student : "Нажата кнопка 'Далее'" add_student_screen --> clear_forms : "Нажата кнопка 'Назад'" check_student --> add_student : "Успешно" add_student --> student_database check_student --> display_main_menu : "Успешно" check_student --> display_notification_student_added_successful : "Успешно" check_student --> login_busy : "Логин занят" @enduml

7.4. Диаграммы C4 и ArchiMate
В предыдущих подразделах было продемонстрировано, как можно расширять стандартные возможности PlantUML и реализовывать недостающие типы диаграмм. Используя абсолютно такие же механизмы, различными авторами была разработана поддержка ещё некоторых нотаций, которые могут быть полезны в работе. Прежде всего стоит обратить внимание на следующие.
В частности, предлагаю посмотреть на один из примеров диаграммы контейнеров в нотации C4 (код взят по ссылке выше).
@startuml !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml !define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons !define FONTAWESOME https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/font-awesome-5 !include DEVICONS/angular.puml !include DEVICONS/java.puml !include DEVICONS/msql_server.puml !include FONTAWESOME/users.puml LAYOUT_WITH_LEGEND() Person(user, "Customer", "People that need products", $sprite="users") Container(spa, "SPA", "angular", "The main interface that the customer interacts with", $sprite="angular") Container(api, "API", "java", "Handles all business logic", $sprite="java") ContainerDb(db, "Database", "Microsoft SQL", "Holds product, order and invoice information", $sprite="msql_server") Rel(user, spa, "Uses", "https") Rel(spa, api, "Uses", "https") Rel_R(api, db, "Reads/Writes") @enduml

Код данного примера примечателен тем, что в нём можно разглядеть многое из того, что было разобрано в данной статье ранее. Здесь есть инструкции препроцессинга, среди которых различимы обращения к той же библиотеке спрайтов, что и была задействована в п. 6.2. Здесь также есть легенда с пояснением цветового кодирования. А последние строки, хотя и завуалировано, но управляют позиционированием элементов (см. п. 4).
7.5. Графы и диграфы
При использовании PlantUML можно дотянуться и до возможностей GraphViz. Это позволит рисовать как неориентированные графы, так и ориентированные (диграфы). В первом случае надо объявить диаграмму как graph, а во втором — как digraph.
graph MyGraph {
ready -- steady
steady -- "go!"
}

digraph MyGraph {
work -> stop
work -> freeze [dir=both]
}

Если задаться вопросом, зачем может потребоваться изображать графы (ведь поддерживается же UML и другие рассмотренные выше нотации), то ответ может быть следующим: иногда бывает полезным просто взять и быстро что-то накидать, зафиксировать в моменте то, что «наштормили», не сковывая себя формальными рамками и семантикой графических элементов. К тому же в отличие от большинства традиционных графических средств, расстановка элементов и отрисовка связей между ними в PlantUML выполняются на автомате.
В общем, лучше знать про такую возможность. А за более сложными примерами можно обратиться к следующим ресурсам:
- https://github.com/futherus/dumpy;
- https://ncona.com/2020/06/create-diagrams-with-code-using-graphviz/.
Заключение
PlantUML — добротный инструмент, который постоянно развивается. И то, что казалось невозможным ещё несколько лет назад, сегодня в нём вполне можно реализовать.
Конечно, не всё в PlantUML сделано одинаково хорошо: здесь есть свои сложности, баги и не всегда предсказуемое поведение. Но на практике многое из этого можно преодолеть или обойти, а при должной сноровке ещё и красиво оформить.
Также стоит признать, что для потенциал PlantUML раскрывается только после освоения ряда продвинутых возможностей, а это требует времени, практики и экспериментов. Надеюсь, данная статья — это как раз то, что поможем вам на этом пути.