Привет! Меня зовут Настя, я старший системный аналитик в X5 Tech. Я рисую sequence-диаграммы каждый день на протяжении четырёх лет. За это время я прошла все круги ада по Данте, то есть попробовала разные инструменты для рисования этих самых диаграмм. Пока не встретила его – PlantUML.
Что удивительно, инструмент довольно не новый, но тем не менее лучше него я пока не встречала. А ещё удивительно, что он не особо популярный. Когда мы запустили в управлении системного анализа первый воркшоп по PlantUML, за 3 минуты после анонса пришли 12 заявок от аналитиков разных грейдов – от Junior до Lead.
В процессе подготовки материалов к воркшопу мы искали статьи и литературу, которые помогли бы дополнительно изучить sequence-диаграммы в PlantUML. Ничего интересного мы не нашли.
На самих воркшопах участники часто говорили о том, что они пытались самостоятельно изучить PlantUML, но их пугало то, что нужно писать какой-то код и учить какой-то синтаксис. Документация достаточно обширная, но информации о том, как последовательно строить sequence почти нет.
Поэтому и появилась эта статья.
Первый раз я увидела PlantUML, когда работала на аутстаффе. Я была на проекте в банке, где занималась проектированием взаимодействия бэкенда и смежных систем.
Я работала на проекте второй день, когда мне дали задачу отрисовать диаграмму уже реализованного функционала по передаче данных пользователя государственному органу.
Я пошла к ПМу, чтобы узнать, как построено взаимодействие между компонентами системы. Предвкушая ваши вопросы, сразу обозначу, что ПМ на том проекте был сильно подкован технически и лучше всех в команде понимал, как работает система.
В общем, он сам нарисовал ту диаграмму, которую должна была сделать я. И делал он это в PlantUML. Я узнала, что это за инструмент и пошла его изучать. И изучала я его параллельно с тем, как рисовала диаграммы.
Почему я так быстро переключилась на PlantUML?
Среди очевидных инструментов я пробовала разные платформы для рисования. И для меня они никогда не были удобными, просто я не знала о существовании других. Перетаскивание элементов, выравнивание и соединение. У меня ещё сильно развит перфекционизм, и я всегда рисую красивые и ровные стрелки, стремлюсь к однообразию размеров шрифтов и даю пространство элементам. У меня больше времени уходило не на рисование сути диаграммы, а на её идеальный вид.
Как-то я даже рисовала sequence в Miro. Там мне понравилось больше всего из всех подобных платформ, потому что в Miro всё красивенько получается.
Потом я узнала, что PlantUML – не единственный язык, который позволяет рисовать Sequence. Например, есть ещё D2 или MermaidJS. Есть ещё штука https://sequencediagram.org/, но, как по мне, она совсем на любителя.
Я использую PlantUML по следующим причинам:
Не надо рисовать стрелочки.
Мы ведём документацию в Confluence, и там есть плагин, в котором я всё и пишу. Это удобно.
Ничего лишнего. В PlantUML можно сделать диаграмму, которая будет соответствовать стандартам sequence и при этом не будет ничем перегружена.
Неплохая документация. В ней описано всё, что можно сделать, да ещё и с примерами.
Дополнительно всё можно найти в этих ваших Интернетах.
Для примера возьму заказ кофе в кофейне. Это будет простая диаграмма, которые обычно мы не рисуем. В Sequence важно показать взаимодействие между компонентами системы или между несколькими системами. У меня задача другая - я не хочу писать, как правильно рисовать диаграммы. Я хочу показать, что PlantUML классный инструмент.
Первый шаг – плагин в Confluence. Ищете PlantUML, выбираете, добавляете заголовок и у вас на странице появляется полотно для творчества.
Второй шаг – написать @startuml и @enduml. Я не нашла сокровенной тайны, почему так надо делать (если знаете, напишите в комментариях). Я их всегда пишу в знак уважения к синтаксису, но без них диаграмма прекрасно будет отображаться.
@startuml
@enduml
Третий шаг – определение участников. Обычно конструкция определения участника выглядит так:
<тип участника> <название> <опции>
Чаще всего я использую следующие типы участников:
actor – рисуется в виде человечка, обозначает действующее лицо.
participant – рисуется в виде прямоугольника, обозначает какой-то компонент системы.
database – рисуется в виде значка базы данных и обозначает эту самую базу данных.
queue – рисуется в виде значка очереди и обозначает эту самую очередь.
Название можно писать на любом языке, но есть нюанс – если в вашем названии есть пробелы, то тогда всю конструкцию надо заключить в кавычки. Или между словами добавить нижнее подчёркивание, но этот вариант мне не очень нравится.
Опции, которые я использую:
псевдонимы as;
сортировка order.
Псевдонимы – очень удобная штука. Если у вас есть длинные названия или названия на русском языке, то вы можете их использовать. Выглядит так:
<тип участника> <название> as <сокращенное_название>
Это сокращённое название нужно потом использовать в коде. Это удобно по двум причинам:
если название на русском, то не придётся постоянно переключать раскладку;
если название длинное, его можно заменить одной или несколькими буквами и сэкономить кучу времени.
Есть важная деталь – в каком порядке вы напишете участников, в том порядке они и будут отрисованы. Если вам это неудобно, то можете использовать ключевое слово order и порядок участника. Опция order идёт после опции as.
@startuml
actor Покупатель as cus order 1
actor Бариста as bar order 2
participant "Кассовый аппарат" as cash order 3
database Холодильник as ref order 5
participant "Рабочее место" as work order 4
@enduml
Четвёртый шаг – добавляем в диаграмму стрелки сообщений. Тут всё просто – берём участников, добавляем между ними стрелки и называем сообщение, которым они обмениваются. Конструкция такая:
<участник><стрелка><участник>:<сообщение>
Стрелки тоже бывают разных видов, все они используются в диаграммах:
синхронное сообщение обозначается ->
асинхронное сообщение обозначается ->>
ответное (или возвращаемое) сообщение обозначается -->
собственное сообщение обозначается комбинацией участник -> участник
Ответное (или возвращаемое) сообщение можно оформить двумя способами.
Первый способ:
участник1 -> участник2
участник1 <-- участник2
Второй способ:
участник1 -> участник2
участник2 --> участник1
На диаграмме они будут выглядеть одинаково, но в коде будут обозначаться по-разному. Я пользуюсь первым способом, чтобы улучшить читабельность кода.
@startuml
actor Покупатель as cus order 1
actor Бариста as bar order 2
participant "Кассовый аппарат" as cash order 3
database Холодильник as ref order 5
participant "Рабочее место" as work order 4
cus -> bar: сделать заказ
bar -> ref: проверить наличие продуктов
bar <-- ref: продукты в наличии
bar -> cash: выставить оплату
cash -> cash: сформировать оплату
bar <-- cash: оплата готова
cus <-- bar: просьба оплатить заказ
cus -> bar: оплата
bar -> cash: оплата
bar <-- cash: принято
bar -> ref: нужно молоко
bar <- ref: вот молоко
bar -> work: приготовление кофе
bar <-- work: кофе готов
cus <-- bar: вот ваш кофе
@enduml
Пятый шаг – добавляем альтернативные и опциональные сценарии. Можно ещё добавлять циклы и блоки, которые выполняются параллельно.
Все такие блоки начинаются с ключевого слова, обозначающего вид блока, и заканчиваются ключевым словом end. Конструкция такая:
<вид_блока><название_блока>
<что-то происходит>
end <вид_блока>
Первый вид блока – alt. Это альтернативный сценарий, обязательно с одной веткой else. Будет выглядеть примерно так:
alt альтернативный сценарий
участник1 -> участник2: сообщение
else другая ветка
участник1 -> участник2: сообщение
end alt
Если вам нужен просто alt без else, то можете использовать второй вид блока – opt. Это опциональный сценарий, который может выполниться при определённом условии. Выглядит так:
opt какое-то действие
участник1 -> участник2: сообщение
end opt
Третий вид блока – loop. Действия в нём повторяются несколько раз или какой-то период времени. Выглядит аналогично opt:
loop 3 раза
участник1 -> участник2: сообщение
end loop
Ещё есть четвёртый вид, который я редко использую. Это group. По сути, он группирует какое-то количество сообщений в рамочку. Это бывает нужно в тех случаях, когда у вас большая диаграмма и вам надо выделить отдельно, например, процесс оплаты. Конструкция такая же:
group какая-то группа
участник1 -> участник2: сообщение
end group
Кстати, в операторе end не обязательно писать вид блока. Можно ограничиться одним словом end. Но я люблю указывать, какой блок надо завершить, чтобы мне самой было понятно, какую группу сообщений я закрыла.
@startuml
actor Покупатель as cus order 1
actor Бариста as bar order 2
participant "Кассовый аппарат" as cash order 3
database Холодильник as ref order 5
participant "Рабочее место" as work order 4
cus -> bar: сделать заказ
bar -> ref: проверить наличие продуктов
bar <-- ref: продукты в наличии
bar -> cash: выставить оплату
cash -> cash: сформировать оплату
bar <-- cash: оплата готова
cus <-- bar: просьба оплатить заказ
loop пока не будет оплачен заказ
cus -> bar: оплата
bar -> cash: оплата
bar <-- cash: принято
end loop
group приготовление кофе
alt заказали кофе на альтернативном молоке
bar -> ref: нужно альтернативное молоко
bar <- ref: вот молоко
else заказали кофе на обычном молоке
bar -> ref: нужно обычное молоко
bar <- ref: вот молоко
end alt
bar -> work: приготовление кофе
bar <-- work: кофе готов
end group
cus <-- bar: вот ваш кофе
opt покупатель оставил чаевые
cus -> bar: вот чаевые
cus <-- bar: большое спасибо
end opt
@enduml
Шестой шаг – активации. Это те самые плашечки, которые обозначают экземпляры выполнения операции. Их можно обозначать двумя способами:
ключевыми словами activate <участник> и deactivate <участник> – там, где вы их указываете, начинается и заканчивается активация.
конструкциями ++ и --
Ключевые слова activate и deactivate указываются после строки с сообщением, которую вы хотите активировать или деактивировать.
Конструкции ++ и -- выглядят так:
участник1 -> участник2++: сообщение
участник2 -> участник 3--: сообщение
Как вы заметили, плюсы и минусы указываются только на втором участнике. Это важный нюанс. Нельзя указывать так:
участник1++ -> участник2++: сообщение
Для такой конструкции будет ошибка. Если вам нужно активировать участник1, то после сообщения нужно указать activate участник1 или участник1++.
Я в своих диаграммах использую только плюсы и минусы. Ключевые слова занимают много места.
@startuml
actor Покупатель as cus order 1
actor Бариста as bar order 2
participant "Кассовый аппарат" as cash order 3
database Холодильник as ref order 5
participant "Рабочее место" as work order 4
cus -> bar++: сделать заказ
cus++
bar -> ref++: проверить наличие продуктов
bar <-- ref--: продукты в наличии
bar -> cash++: выставить оплату
cash -> cash: сформировать оплату
bar <-- cash--: оплата готова
cus <-- bar: просьба оплатить заказ
loop пока не будет оплачен заказ
cus -> bar: оплата
bar -> cash++: оплата
bar <-- cash--: принято
end loop
group приготовление кофе
alt заказали кофе на альтернативном молоке
bar -> ref++: нужно альтернативное молоко
bar <- ref: вот молоко
else заказали кофе на обычном молоке
bar -> ref: нужно обычное молоко
bar <- ref--: вот молоко
end alt
bar -> work++: приготовление кофе
bar <-- work--: кофе готов
end group
cus <-- bar: вот ваш кофе
opt покупатель оставил чаевые
cus -> bar: вот чаевые
cus <-- bar--: большое спасибо
cus--
end opt
Седьмой шаг – украшения. Их очень много в PlantUML, я расскажу только про те, которыми пользуюсь я.
Первое – autonumber. Указываете это слово до всего описания сообщений, и они будут пронумерованы автоматически. В общем виде эта команда выглядит так:
autonumber <число_с_которого_начнется_нумерация><шаг>
Если вы не указываете два параметра, то автонумерация по умолчанию начинается с цифры 1 с шагом 1.
Второе – разделители. Очень удобно, когда большая диаграмма, и нужно поделить её на части. Конструкция такая:
==<заголовок>==
Третье – задержка. Нужна, чтобы обозначить, например, что сообщение нужно отправить на следующий день. Конструкция такая:
…<заголовок>...
Четвёртое – разделение сообщений. Если у вас есть большие сообщения, которые удлиняют вашу диаграмму, то их можно разделить. Тоже есть два способа:
автоматический – с помощью команды skinparam maxMessageSize <число>, указывается в начале диаграммы и действует на все сообщения;
ручной – с помощью команды \n, пишите в то место, где нужно поделить сообщение.
Пятое – промежутки. Из названия понятно, что они нужны для обозначения промежутков в диаграмме. После сообщения на следующей строке указываете ||| и будет сделан отступ. Особенно это актуально, когда много альтернативных сценариев и блоков.
@startuml
actor Покупатель as cus order 1
actor Бариста as bar order 2
participant "Кассовый аппарат" as cash order 3
database Холодильник as ref order 5
participant "Рабочее место" as work order 4
autonumber
cus -> bar++: сделать заказ
cus++
bar -> ref++: проверить наличие продуктов
bar <-- ref--: продукты в наличии
==оплата==
bar -> cash++: выставить оплату
cash -> cash: сформировать оплату
bar <-- cash--: оплата готова
cus <-- bar: просьба\nоплатить заказ
|||
loop пока не будет оплачен заказ
cus -> bar: оплата
bar -> cash++: оплата
bar <-- cash--: принято
end loop
==готовка==
group приготовление кофе
|||
alt заказали кофе на альтернативном молоке
bar -> ref++: нужно альтернативное молоко
bar <- ref: вот молоко
else заказали кофе на обычном молоке
bar -> ref: нужно обычное молоко
bar <- ref--: вот молоко
end alt
bar -> work++: приготовление кофе
bar <-- work--: кофе готов
end group
...через 3 минуты...
cus <-- bar: вот ваш кофе
opt покупатель оставил чаевые
cus -> bar: вот чаевые
cus <-- bar--: большое спасибо
cus--
end opt
@enduml
ChatGPT прекрасно может работать с PlantUML. Тут работает классическое правило – хотите хороший результат, напишите детальный запрос. Для примера можем попросить просто написать код для PlantUML с sequence-диаграммой работы бариста.
Запрос: Напиши код для plantuml с sequence-диаграммой работы бариста.
@startuml
actor Customer
actor Barista
participant Queue
participant Database
Customer -> Queue: Заказать кофе
Queue -> Barista: Передать заказ
Barista -> Database: Записать заказ
Database --> Barista: Подтверждение записи
Barista -> Barista: Проверить наличие ингредиентов
Barista -> Database: Обновить количество ингредиентов
Database --> Barista: Подтверждение обновления
Barista -> Barista: Приготовить кофе
Barista -> Queue: Сообщить о готовности заказа
Queue --> Customer: Оповестить о готовности
Customer -> Barista: Забрать заказ
Barista -> Customer: Передать кофе
Customer -> Barista: Оставить чаевые
@enduml
Результат плохой: непонятно, что это за участники, не хватает активаций. Расположение участников тоже странное, если очередь – это физическая очередь, то зачем она вообще здесь.
Но можно попробовать сформировать более конкретно.
Запрос: Мне нужен код sequence-диаграммы для plantuml. В диаграмме должно быть 5 участников: покупатель, бариста, касса, рабочее место и холодильник. Обязательно должны быть активации и деактивации, один альтернативный сценарий с выбором молока (альтернативное или обычное) и опциональный блок для оставления чаевых. Хорошо бы добавить нумерацию сообщений.
@startuml
autonumber
actor Customer
participant Barista
participant CashRegister as "Cash Register"
participant WorkStation as "Work Station"
participant Fridge as "Fridge"
Customer -> Cash Register: 1. Сделать заказ
activate Cash Register
Cash Register -> Barista: 2. Передать заказ
deactivate Cash Register
activate Barista
Barista -> Fridge: 3. Проверить наличие ингредиентов
activate Fridge
Fridge --> Barista: 4. Подтверждение наличия ингредиентов
deactivate Fridge
alt Выбор молока
Customer -> Barista: 5. Выбрать обычное молоко
Barista -> Fridge: 6. Достать обычное молоко
Fridge --> Barista: 7. Обычное молоко
else
Customer -> Barista: 5. Выбрать альтернативное молоко
Barista -> Fridge: 6. Достать альтернативное молоко
Fridge --> Barista: 7. Альтернативное молоко
end
Barista -> WorkStation: 8. Приготовить кофе
activate WorkStation
WorkStation -> Customer: 9. Передать кофе
deactivate WorkStation
deactivate Barista
opt Оставить чаевые
Customer -> Cash Register: 10. Оставить чаевые
activate Cash Register
Cash Register -> Barista: 11. Сообщить о чаевых
deactivate Cash Register
end
@enduml
В результате мы получим ошибку, потому что ChatGPT в коде у участника Cash Register оставил пробел. Можно попросить исправить.
Запрос: Исправь ошибку с Cash Register, там не должно быть пробела.
@startuml
autonumber
actor Customer
participant Barista
participant CashRegister as "Cash Register"
participant WorkStation as "Work Station"
participant Fridge as "Fridge"
Customer -> CashRegister: 1. Сделать заказ
activate CashRegister
CashRegister -> Barista: 2. Передать заказ
deactivate CashRegister
activate Barista
Barista -> Fridge: 3. Проверить наличие ингредиентов
activate Fridge
Fridge --> Barista: 4. Подтверждение наличия ингредиентов
deactivate Fridge
alt Выбор молока
Customer -> Barista: 5. Выбрать обычное молоко
Barista -> Fridge: 6. Достать обычное молоко
Fridge --> Barista: 7. Обычное молоко
else
Customer -> Barista: 5. Выбрать альтернативное молоко
Barista -> Fridge: 6. Достать альтернативное молоко
Fridge --> Barista: 7. Альтернативное молоко
end
Barista -> WorkStation: 8. Приготовить кофе
activate WorkStation
WorkStation -> Customer: 9. Передать кофе
deactivate WorkStation
deactivate Barista
opt Оставить чаевые
Customer -> CashRegister: 10. Оставить чаевые
activate CashRegister
CashRegister -> Barista: 11. Сообщить о чаевых
deactivate CashRegister
end
@enduml
В целом даже похоже на то, что сделала я сама. Большое отличие в том, что для ChatGPT покупатель взаимодействует с кассой сам. Хотя в таком случае логичнее назвать этого участника как-то иначе.
Забавно, что ChatGPT применил команду autonumber и сам пронумеровал сообщения.
Надеюсь, что эта статья помогла вам в освоении PlantUML. И в понимании того, что PlantUML – лёгкий инструмент.
Мне кажется, что для первых диаграмм достаточно остановиться на участниках, сообщениях и активациях. И уже по мере освоения добавлять альтернативные сценарии и блоки, а потом и разные украшения.
Если хотите узнать больше возможностей инструмента, то рекомендую использовать документацию: https://plantuml.com/ru/sequence-diagram. И ещё у них есть неплохой редактор, который сразу будет вам показывать всё то, что вы напишете: http://www.plantuml.com/plantuml/uml/.