UP | HOME

2020-06-11

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

first-puml-diagram.png

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

styled-puml-diagram.png

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.

sequenceDiagram_internal.png

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 have INTERNAL in their name. This if condition skips the sequence interaction if a variable named INTERNAL 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 the INTERNAL 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.

sequenceDiagram.png

Footnotes:

Vanshdeep Singh • 2020 • Emacs 26.3 (Org mode 9.3.6)

Dividers Designed by Freepik