Sequence flow documentation with PlantUML
Table of Contents
Motivation
After working and developing production systems I have realized the importance of documentation. Not that I didn't know that documentation was necessary but there is a difference between knowing and understanding. When you know something its just knowledge but when you understand, that knowledge becomes actionable. To that effect, this document details how to use PlantUML for creating diagrams as code for enriching software documentation.
Why PlantUML
There are plenty of tools for generating workflow documentation out there,
These tools are mostly web based and offer a rich feature set when creating workflows. I prefer PlantUML primarily because it allows creating workflow documentation as code with a "reasonably" clear syntax i.e. the code for the workflow can live within the project or service repository. Furthermore, it offers substantial preprocessing 1 primitives which enables expressing complex diagram generation and exporting scenarios.
PlantUML basics
Setup PlantUML
Download the plantuml.jar
file from the PlantUML website. Now you can install
Java locally to run .jar
file but I find GraalVM much more easy to setup and
fast to use for working with JVM
ecosystem. When using Linux its quite easy to
setup with instructions shared here. After the setup is complete and you have
java
available in your PATH
and JAVA_HOME
set we are ready to generate our
first diagram using PlantUML.
NOTE: Another convenient alias
which I use to ease diagram generation is,
alias plantuml="java -jar /path/to/plantuml.jar"
Generate the first diagram
Save the code below in a file first-puml-diagram.pum
and execute plantuml
first-puml-diagram.pum
This will generate first-puml-diagram.png
in your
current directory and the result will look as follows,
@startuml Alice -> Bob: test @enduml
Styling PlantUML
PlantUML is quite flexible with its styling configuration for the generated
images. Some would say a bit too flexible. In order to style a PlantUML diagram
skinparam
directive can be used,
@startuml scale 150 width skinparam { Shadowing false DefaultFontName Fira Sans Compressed RoundCorner 5 TitleBorderRoundCorner 5 DefaultFontSize 10 Padding 0 } Alice -> Bob: test @enduml
The usage is quite similar to how a lay backend dev would write CSS
! To list
all properties which can be styled, execute plantuml -language
. And just like
one would separate out the stylesheet into independent file, herein it would be
a good practice to do the same and separate skinparam
into it's own file
skinparam.pum
.
Practical PlantUML
In order to understand how to use PlantUML to create practical diagrams we take an example API service whose flow shall be described via PlantUML. Below is how the final exported sequence flow generated using PlantUML. In the remaining sections we go over the various elements to understand how to generate such a sequence flow diagram and how to manage PlantUML code.
Sequence elements
The primary component of a sequence flow diagram are the participants.
@startuml 'PARTICIPANTS participant "<&globe>User" as Client participant Server database RDBMS internal_participant("3rd Party", "External") @enduml
Notice database
is a special kind of participant which would be the logical
way to represent storage in the flow. Another think to notice is
internal_participant()
which will be discussed later. For more details
regarding participants refer the plantuml sequence flow docs.
Sequence interactions
Once the set of participants for a sequence diagram has been defined the
sequence flow can be described using these participants. The most basic set of
interaction seen previously is Alice -> Bob
which creates a unidirectional
arrow originating from Alice
and pointing to Bob
.
- PlantUML Procedures
Based on the context of a sequence diagram is a good practice to define interactions as a set of procedures which can then be used to define the interactions between participants, following are the procedures defined the PlantUML diagram generated above (
definitions.pum
),@startuml !procedure HTTP_REQUEST(source, destination, method, uri) autonumber resume source -> destination: <b>method</b> uri !endprocedure !procedure HTTP_REQUEST_INTERNAL(source, destination, method, uri) !if %variable_exists("INTERNAL") HTTP_REQUEST(source, destination, method, uri) !endif !endprocedure !procedure HTTP_RESPONSE(source, destination, code, payload) autonumber stop source -> destination: <font size=8 color=green><b>code</b></font> <size:9> payload !endprocedure !procedure HTTP_RESPONSE_INTERNAL(source, destination, code, payload) !if %variable_exists("INTERNAL") HTTP_RESPONSE(source, destination, code, payload) !endif !endprocedure !procedure DB_QUERY(source, destination, query) autonumber stop source ->> destination: query !endprocedure !procedure internal_participant(name, reference) !if %variable_exists("INTERNAL") participant "name" as reference !endif !endprocedure @enduml
!procedure ... !endprocedure
defines a procedure (i.e. macro) and herein we define the following procedures,HTTP_REQUEST
,HTTP_RESPONSE
HTTP_REQUEST_INTERNAL
,HTTP_RESPONE_INTERNAL
DB_QUERY
internal_participant
Notice, the
!if %variable_exists("INTERNAL") ... !endif
block in procedures which haveINTERNAL
in their name. Thisif
condition skips the sequence interaction if a variable namedINTERNAL
is not defined. This allows to generate multiple PlantUML diagrams wherein some components and interactions are removed. This might be desirable based on the expected audience of the diagram.Following previously,
internal_participant
is just another participant which is conditionally added to the exported diagram if theINTERNAL
variable is set.
Skinparams
Skinparams allow styling of the generated PlantUML diagram. There are few a lot
of styling options available, following are the skinparams used for the
generated PlantUML diagram (skinparam.pum
),
@startuml scale 1024 width skinparam { Shadowing false DefaultFontName Fira Sans Compressed RoundCorner 5 TitleBorderRoundCorner 5 DefaultFontSize 10 Padding 0 } skinparam Participant { FontSize 12 BorderColor #969896 BackgroundColor #ffffff Padding 10 } skinparam Arrow { Color #3971ed MessageAlignment left } skinparam Sequence { MessageAlign center ParticipantBorderThickness 0.8 DividerBorderThickness 2 DividerBorderColor #fff DividerBackgroundColor #cfd0c2 DividerFontSize 12 ReferenceBorderThickness 0.5 ReferenceBorderColor #969896 ReferenceHeaderBackgroundColor #f0c674 } skinparam Footer { FontSize 8 } skinparam Header { FontSize 8 } skinparam Database { BackgroundColor #fff BorderColor #969896 } skinparam Note { BackgroundColor #fffdee BorderColor #969896 BorderThickness 0.3 } @enduml
Herein we define skinparam only for the components used in our diagram i.e.
Sequence
elements, Arrow
, Database
etc. To get a list of all the
skinparams which can be configured use plantuml -language
. Refer here for more
details regarding this topic.
Organizing PlantUML Code
As seen above sequence diagrams definitions can grow quite quickly so it is good practice to organize the plantuml code in a modular fashion to avoid "spaghetti" code. The most basic form of layout can be as following,
puml/ ├── definitions.pum ├── sequenceDiagram.pum └── skinparam.pum
A more complicated setup can be used in based on requirements and can involve `Makefile` to generate different diagram targets.
Final take
Once the above definitions and skinparams have been properly defined the
following sequenceDiagram.pum
brings it all together,
@startuml !include skinparam.pum !include definitions.pum header Simple sequence flow center footer Vanshdeep Singh • 2020 • PlantUML %version() !INTERNAL = %true 'PARTICIPANTS participant "<&globe>User" as Client participant Server database RDBMS internal_participant("3rd Party", "External") 'VARIABLES !if %variable_exists("INTERNAL") !SERVER = "Server" !EXTERNAL = "External" !else !SERVER = "Server" !EXTERNAL = "Server" !endif autonumber 10 10 "<font size=7 color=indigo>0" /' ' DIAGRAM '/ == Flow A == ref over Client, EXTERNAL This is refernece for this flow. Following is some explaination # Some __point for the flow__ # Another point with ""inline code"" Example of Tree |_ First line |_ **Bom(Model)** |_ prop1 |_ prop2 |_ prop3 |_ Last line end ref HTTP_REQUEST("Client", SERVER, "POST", "/api/resource") DB_QUERY(SERVER, "RDBMS", "INSERT INTO tbl ...") HTTP_RESPONSE(SERVER, "Client", "CREATED", "JSON {key: value}") note over Client, SERVER This is a note which will span across two participants. end note == Flow B == HTTP_REQUEST("Client", SERVER, "GET", "/api/resource/:id") HTTP_REQUEST_INTERNAL(SERVER, EXTERNAL, "GET", "/v1/resource/:id") HTTP_RESPONSE_INTERNAL(EXTERNAL, SERVER, "OK", "JSON {key: value}") rnote over SERVER This is a note which will span across one participant. end note HTTP_RESPONSE(SERVER, "Client", "OK", "JSON {key: value}") @enduml
Notice how we have !include
directive is used to include the definitions and
skinparams. There are various other components that have used here to create
sections (== Flow A ==
), notes etc 2 .
Disable components
The last outstanding item to discuss is enabling/disabling components of the
above PlantUML so that the sequence diagram can be tailored for its audience.
Notice in the above PlantUML definition a variable named INTERNAL
is defined.
In the previous section we saw some procedures which check the existence of this
variable before adding components to the diagram. We can delete this variable
from the PlantUML definition and generate the sequence diagram which will
exclude any components which have been marked internal.