Keep your diagrams updated with continuous delivery

Keep your diagrams updated with continuous delivery

How to use PlantUML to design diagrams as code and deploy then from your CI/CD Pipeline

Introduction

Changing software diagrams is hard. The simple act of adding a new box may require us to drag all the existing boxes and reorganize the diagram. This is one of the main reasons to why software diagrams are constantly left deprecated after the first stages of the development process.

In this post I'll show how defining diagrams as code can help in designing and updating software diagrams, and how to automate the process of updating the documentation with those diagrams.

Why create diagrams as code?

  • Easy to change: Just change the code and the elements of the diagram are rendered in a good position (sometimes it may need some tweeking);
  • Reuse of code: Components, sprites, and functions can be defined and shared to be used in other diagrams. We can use loops and conditions to make those pieces of code even more reusable. Details here;
  • History of changes: Because it is code, their versions can be tracked and compared with version control systems, like Git, for example;
  • Single style in the whole diagram: Unless explicited, all the elements of the diagram will have the same style, no need to copy style from one element to another or having to resize all boxes after change the size of one;
  • Inclusive: Everybody in the team can checkout the code and change it without fear because changes can be tracked and the style is the same for everyone.

PlantUML

PlantUML is a highly customizable open-source tool that allow us to create diagrams using code. Despite de name, it supports many types of diagrams besides UML diagrams. It has it's own language and some extensions to other languages like AsciiMath, Creole and LaTeX.

PlantUML is a Java Command Line tool. We can run it from the command line, but the best experience is with a Visual Studio Code extension.

Rendering from Visual Studio Code

There is an extension that integrates PlantUML with Visual Studio Code.

It offers syntax hightlighting and a preview of the diagram on the side while editing, and options to export the current or all project diagrams, besides other features.

Syntax highlighting and preview of the diagram

After installing the extension, open the command pallete and search for PlantUML to see the available options.

PlantUML options

Rendering from the command line

First, download the compiled JAR from the downloads page or from the GitHub releases page.

ℹ️ You may want to include the path to the plantuml.jar file in the PATH environment variable to be able to use it in any directory.

Then, to generate the diagram for one source file, just run the following command:

java -jar plantuml.jar Sequence.puml

We can also generate the diagrams for more than one file using glob patterns:

java -jar plantuml.jar *.puml

C4 Diagrams

The C4 model is a different approach to designing software architecture diagrams. I've talked about it in my previous post.

PlantUML has native support for C4 Diagrams. We just need to include the library and use its elements.

Here are some examples:

System Context diagram

System Context diagram

@startuml C4_SystemContext

!include <C4/C4_Context>

left to right direction

Person(user, "User", "Company employee who has access to the HR system")
System(hrSystem, "HR System", "Allows users to manage personal data and contract of the company employees")
System_Ext(emailSystem, "E-mail System", "Responsible for queueing and sending e-mails")

Rel(user, hrSystem, "Create and change employee personal and contract information", "")
Rel(hrSystem, emailSystem, "Sends notification e-mails using", "")

@enduml

Containers diagram

Containers diagram

@startuml C4_HRSystem_Containers

!define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist

!include <C4/C4_Container>
!include DEVICONS/msql_server.puml
!include DEVICONS/dotnet.puml
!include AWSPuml/AWSCommon.puml
!include AWSPuml/ApplicationIntegration/SimpleQueueServiceQueue.puml

left to right direction

Person(user, "User", "Company employee who has access to the HR system")

System_Boundary(hrSystem, "HR System") {
    Container(webApp, "Web Application", "ASP.NET 7 Application", "Provides the system functionalities through the web browser", $sprite="dotnet")

    Container(backgroundService, "E-mail service", ".NET 7 Application", "Background service that reads a queue for employee data changes and sends notification e-mails to the employees", $sprite="dotnet")

    ContainerDb(database, "Database", "SQL Server 2022", "Holds employee and contract data", $sprite="msql_server")

    ContainerQueue(emailQueue, "Queue", "AWS SQS", "Holds employee data changes", $sprite="SimpleQueueServiceQueue")
}

System_Ext(emailSystem, "E-mail System", "Responsible for queueing and sending e-mails")

Rel(user, webApp, "Create and change employee personal and contract information", "")

Rel(webApp, database, "Reads / Writes", "")
Rel(webApp, emailQueue, "Writes notifications to", "")

Rel(backgroundService, emailQueue, "Reads notifications from", "")
Rel(backgroundService, database, "Reads employee data from", "")
Rel(backgroundService, emailSystem, "Sends e-mails using", "")

@enduml

Components diagram

Components diagram

@startuml C4_HRSystem_WebApp_Components

!define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist

!include <C4/C4_Component>
!include DEVICONS/msql_server.puml
!include AWSPuml/AWSCommon.puml
!include AWSPuml/ApplicationIntegration/SimpleQueueServiceQueue.puml

left to right direction

Container(webApp, "Web Application", "ASP.NET 7 Application", "Provides the system functionalities through the web browser", $sprite="dotnet")

Container_Boundary(webApp, "Web Application") {
    Component(employeesController, "Employees Controller", "Provides access to the employees related functionalities")

    Component(registerEmployeesUseCase, "Register Employee Use Case", "Orchestrate the use case of registering a new employee")

    Component(employeeDataQueueService, "Employee Data Queue Service", "Provides functionalities to communicate with the queue")

    Component(employeeRepository, "Employee Repository", "Provides functionalities to communicate with the employee database table")

    Component(loginController, "Login Controller", "ASP.NET Core Controller", "Allow users to authenticate in the web application")

    Rel(employeesController, registerEmployeesUseCase, "Uses")
    Rel(registerEmployeesUseCase, employeeDataQueueService, "Uses")
    Rel(registerEmployeesUseCase, employeeRepository, "Uses")
}

ContainerDb(database, "Database", "SQL Server 2022", "Holds employee and contract data", $sprite="msql_server")

ContainerQueue(emailQueue, "Queue", "AWS SQS", "Holds employee data changes", $sprite="SimpleQueueServiceQueue")

Rel(employeeRepository, database, "Writes employee information", "")
Rel(employeeDataQueueService, emailQueue, "Writes notifications to", "")

@enduml

More examples

Here are more examples of what can be done with PlantUML.

Variables and colors

In this example, I created a sequence diagram and used variables for reusing the formatted HTTP verbs in the messages:

Sequence diagram

@startuml SequenceDiagram

!$get_method = "<font color=lime><b>GET</b></font>"
!$post_method = "<font color=blue><b>POST</b></font>"

participant "Frontend" as Frontend
participant "BFF" as BFF
participant "PokéAPI" as PokeAPI
database "Cache" as Cache

Frontend -> BFF : $get_method /pokemondata/{name}
BFF -> Cache : $get_method Search for data in the cache
BFF <-- Cache : Cached data

alt data not found in cache
    BFF -> PokeAPI : $get_method /pokemon/{name}
    BFF <-- PokeAPI : Pokemon data
    BFF -> Cache : $post_method Save data in the cache
end

Frontend <-- BFF : Return pokémon data

@enduml

Visualization of JSON data

One cool diagram that PlantUML can generate is the JSON diagram, showing the properties and data of a JSON. To generate a JSON diagram, just use the @startjson and @endjson symbols and paste the JSON content between them.

JSON diagram

@startjson JSONDiagram
{
  "abilities": [
    {
      "ability": {
        "name": "blaze",
        "url": "https://pokeapi.co/api/v2/ability/66/"
      },
      "is_hidden": false,
      "slot": 1
    },
    {
      "ability": {
        "name": "solar-power",
        "url": "https://pokeapi.co/api/v2/ability/94/"
      },
      "is_hidden": true,
      "slot": 3
    }
  ],
  "base_experience": 267,
  "forms": [
    {
      "name": "charizard",
      "url": "https://pokeapi.co/api/v2/pokemon-form/6/"
    }
  ],
  "height": 17,
  "held_items": [],
  "id": 6,
  "is_default": true,
  "location_area_encounters": "https://pokeapi.co/api/v2/pokemon/6/encounters",
  "name": "charizard",
  "order": 7,
  "past_types": [],
  "species": {
    "name": "charizard",
    "url": "https://pokeapi.co/api/v2/pokemon-species/6/"
  },
  "types": [
    {
      "slot": 1,
      "type": {
        "name": "fire",
        "url": "https://pokeapi.co/api/v2/type/10/"
      }
    },
    {
      "slot": 2,
      "type": {
        "name": "flying",
        "url": "https://pokeapi.co/api/v2/type/3/"
      }
    }
  ],
  "weight": 905
}
@endjson

Importing custom elements

PlantUML is extensible, so we can create or import custom elements to use in our diagrams. One example is the AWS Icons for PlantUML. It has elements to represent most of the main AWS Services.

To use it, we need to import the custom elements with the !import command.

In this example, I used the !define command to define a variable AWSPuml with the base URL and used it in all imports. This helps when we need to update the version of the objects and also makes the code cleaner.

Diagram with custom elements

@startuml InfrastructureDiagram

left to right direction

!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist

!include AWSPuml/AWSCommon.puml
!include AWSPuml/NetworkingContentDelivery/CloudFront.puml
!include AWSPuml/Compute/Lambda.puml
!include AWSPuml/Storage/SimpleStorageService.puml
!include AWSPuml/Database/ElastiCache.puml

actor "User" as User

CloudFront(CloudFront, "CloudFront", "")
SimpleStorageService(S3, "S3 Bucket", "Angular App")
Lambda(Bff, "BFF", "ASP.NET Core")
ElastiCache(Redis, "Cache", "Redis")

User --> CloudFront
CloudFront --> S3
S3 --> Bff
Bff --> Redis

@enduml

Themes

PlantUML support some themes by default. Just use the !theme command followed by the theme name:

@startuml
!theme materia
...
@enduml

Here is the previous sequence diagram with the Materia theme:

Sequence diagram with Materia theme

Here we can see a gallery with the available themes.

Automating the diagram publication

I've created a documentation site as an example of how to generate PlantUML diagrams in the CI/CD pipeline. When the diagrams are commited to the repository, the pipeline renders then as images and the documentation is automatically updated.

Documentation site updated by the CI/CD pipeline

dgenezini.github.io/docs-sample

It uses:

To render the diagrams as images and commit then to repository, configure a new job generate_plantuml with the code below:

jobs:
  generate_plantuml:
    runs-on: ubuntu-latest
    name: plantuml
    steps:
    - name: checkout
      uses: actions/checkout@v1
      with:
        fetch-depth: 1
    - name: plantuml
      id: plantuml
      uses: grassedge/generate-plantuml-action@v1.5
      with:
        path: diagrams
        message: "Render PlantUML files"
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Then, just reference the images in the documentation files:

![](/docs-sample/diagrams/2-containers.svg)

The full pipeline configuration can be seen here.

The source repository is here.

Diagram as code 2.0

Simon Brown, creator of the C4 model, has a very interesting concept called Diagram as code 2.0 in which one model code can generate multiple diagrams. More details in his blog post.

He built a tool called Structurizr for this.