Как рисовать Sequence без боли и страданий в PlantUML

от автора

Привет! Меня зовут Настя, я старший системный аналитик в X5 Tech. Я рисую sequence-диаграммы каждый день на протяжении четырёх лет. За это время я прошла все круги ада по Данте, то есть попробовала разные инструменты для рисования этих самых диаграмм. Пока не встретила его – PlantUML. 

Что удивительно, инструмент довольно не новый, но тем не менее лучше него я пока не встречала. А ещё удивительно, что он не особо популярный. Когда мы запустили в управлении системного анализа первый воркшоп по PlantUML, за 3 минуты после анонса пришли 12 заявок от аналитиков разных грейдов – от Junior до Lead. 

В процессе подготовки материалов к воркшопу мы искали статьи и литературу, которые помогли бы дополнительно изучить sequence-диаграммы в PlantUML. Ничего интересного мы не нашли. 

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

Поэтому и появилась эта статья. 

Почему PlantUML?

Первый раз я увидела PlantUML, когда работала на аутстаффе. Я была на проекте в банке, где занималась проектированием взаимодействия бэкенда и смежных систем. 

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

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

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

Почему я так быстро переключилась на PlantUML? 

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

Как-то я даже рисовала sequence в Miro. Там мне понравилось больше всего из всех подобных платформ, потому что в Miro всё красивенько получается. 

Потом я узнала, что PlantUML – не единственный язык, который позволяет рисовать Sequence. Например, есть ещё D2 или MermaidJS. Есть ещё штука https://sequencediagram.org/, но, как по мне, она совсем на любителя. 

Я использую PlantUML по следующим причинам: 

  1. Не надо рисовать стрелочки.

  2. Мы ведём документацию в Confluence, и там есть плагин, в котором я всё и пишу. Это удобно. 

  3. Ничего лишнего. В PlantUML можно сделать диаграмму, которая будет соответствовать стандартам sequence и при этом не будет ничем перегружена. 

  4. Неплохая документация. В ней описано всё, что можно сделать, да ещё и с примерами.

Мой алгоритм построения диаграмм

Дополнительно всё можно найти в этих ваших Интернетах. 

Для примера возьму заказ кофе в кофейне. Это будет простая диаграмма, которые обычно мы не рисуем. В Sequence важно показать взаимодействие между компонентами системы или между несколькими системами. У меня задача другая — я не хочу писать, как правильно рисовать диаграммы. Я хочу показать, что PlantUML классный инструмент. 

Первый шаг – плагин в Confluence. Ищете PlantUML, выбираете, добавляете заголовок и у вас на странице появляется полотно для творчества. 

Второй шаг – написать @startuml и @enduml. Я не нашла сокровенной тайны, почему так надо делать (если знаете, напишите в комментариях). Я их всегда пишу в знак уважения к синтаксису, но без них диаграмма прекрасно будет отображаться.

@startuml   @enduml

Третий шаг – определение участников. Обычно конструкция определения участника выглядит так: 

<тип участника> <название> <опции>

Чаще всего я использую следующие типы участников: 

  1. actor – рисуется в виде человечка, обозначает действующее лицо.

  2. participant – рисуется в виде прямоугольника, обозначает какой-то компонент системы.

  3. database – рисуется в виде значка базы данных и обозначает эту самую базу данных.

  4. queue – рисуется в виде значка очереди и обозначает эту самую очередь.

Название можно писать на любом языке, но есть нюанс – если в вашем названии есть пробелы, то тогда всю конструкцию надо заключить в кавычки. Или между словами добавить нижнее подчёркивание, но этот вариант мне не очень нравится. 

Опции, которые я использую: 

  1. псевдонимы as;

  2. сортировка order.

Псевдонимы – очень удобная штука. Если у вас есть длинные названия или названия на русском языке, то вы можете их использовать. Выглядит так: 

<тип участника> <название> as <сокращенное_название>

Это сокращённое название нужно потом использовать в коде. Это удобно по двум причинам: 

  1. если название на русском, то не придётся постоянно переключать раскладку;

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

Есть важная деталь – в каком порядке вы напишете участников, в том порядке они и будут отрисованы. Если вам это неудобно, то можете использовать ключевое слово 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. асинхронное сообщение обозначается ->>

  3. ответное (или возвращаемое) сообщение обозначается —>

  4. собственное сообщение обозначается комбинацией участник -> участник

Ответное (или возвращаемое) сообщение можно оформить двумя способами. 

Первый способ: 

участник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

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

  1. ключевыми словами activate <участник> и deactivate <участник> – там, где вы их указываете, начинается и заканчивается активация.

  2. конструкциями ++ и —

Ключевые слова 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. 

Второе – разделители. Очень удобно, когда большая диаграмма, и нужно поделить её на части. Конструкция такая: 

==<заголовок>==

Третье – задержка. Нужна, чтобы обозначить, например, что сообщение нужно отправить на следующий день. Конструкция такая: 

…<заголовок>…

Четвёртое – разделение сообщений. Если у вас есть большие сообщения, которые  удлиняют вашу диаграмму, то их можно разделить. Тоже есть два способа: 

  1. автоматический – с помощью команды skinparam maxMessageSize <число>, указывается в начале диаграммы и действует на все сообщения;

  2. ручной – с помощью команды \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

PlantUML и ChatGPT

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/.


ссылка на оригинал статьи https://habr.com/ru/articles/821687/