Keep your diagrams updated with continuous delivery
Tags: architecture, plantuml, devops, c4
Published: 2022-11-29 11:20:00

## 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](https://plantuml.com/preprocessing);
- **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](https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml) 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](https://blog.genezini.com/p/keep-your-diagrams-updated-with-continuous-delivery/images/VSCodePlantUML.png)

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

![PlantUML options](https://blog.genezini.com/p/keep-your-diagrams-updated-with-continuous-delivery/images/VSCodePlantUML2.png)

### Rendering from the command line

First, download the compiled JAR from the [downloads page](https://plantuml.com/download) or from the [GitHub releases page](https://github.com/plantuml/plantuml/releases/latest).

> ℹ️ 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:

```bash
java -jar plantuml.jar Sequence.puml
```

We can also generate the diagrams for more than one file using [glob patterns](https://en.wikipedia.org/wiki/Glob_(programming)):

```bash
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](https://blog.genezini.com/p/cleaner-representation-of-software-architectures-with-the-c4-model/).

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](https://blog.genezini.com/p/keep-your-diagrams-updated-with-continuous-delivery/images/SystemContext.png)

```java
@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](https://blog.genezini.com/p/keep-your-diagrams-updated-with-continuous-delivery/images/Containers.png)

```java
@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](https://blog.genezini.com/p/keep-your-diagrams-updated-with-continuous-delivery/images/Components.png)

```java
@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](https://blog.genezini.com/p/keep-your-diagrams-updated-with-continuous-delivery/images/Sequence.png)

```java
@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](https://blog.genezini.com/p/keep-your-diagrams-updated-with-continuous-delivery/images/Json.png)

```json
@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][AWSForPlantUML]. 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](https://blog.genezini.com/p/keep-your-diagrams-updated-with-continuous-delivery/images/Infrastructure.png)

```java
@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](https://blog.genezini.com/p/keep-your-diagrams-updated-with-continuous-delivery/images/SequenceMateria.png)

[Here](https://bschwarz.github.io/puml-themes/gallery.html) 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](https://blog.genezini.com/p/keep-your-diagrams-updated-with-continuous-delivery/images/DocPage.png)

https://dgenezini.github.io/docs-sample/

It uses:
- ***[GitHub Pages][GitHubPages]*** for Hosting;
- ***[GitHub Actions][GitHubActions]*** for CI/CD Pipeline (Generate diagrams and deploy static site);
- ***[Generate PlantUML][GeneratePlantUml]*** GitHub Action to generate the diagrams and commit them to the repository;
- ***[Just the docs][JustTheDocs]*** template as template for the site.

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

```yaml
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:

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

The full pipeline configuration can be seen [here](https://github.com/dgenezini/docs-sample/blob/main/.github/workflows/pages.yml).

The source repository is [here](https://github.com/dgenezini/docs-sample).

## 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](https://dev.to/simonbrown/diagrams-as-code-2-0-82k).

He built a tool called [Structurizr](https://structurizr.com/) for this.

## Liked this post?

I post extra content in my personal blog. [Click here](https://blog.genezini.com/) to see.

## Follow me

- [On Feedly](https://feedly.com/i/subscription/feed/https://blog.genezini.com/index.xml)
- [RSS](https://blog.genezini.com/index.xml)
- [Linkedin](https://www.linkedin.com/in/danielgenezini/)
- [Twitter](https://twitter.com/GeneziniDaniel)

## References and Links

- https://plantuml.com/
- https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml
- https://github.com/plantuml-stdlib/C4-PlantUML
- https://github.com/awslabs/aws-icons-for-plantuml
- https://github.com/marketplace/actions/generate-plantuml
- https://just-the-docs.github.io/just-the-docs/

[JustTheDocs]: https://just-the-docs.github.io/just-the-docs/
[GeneratePlantUml]: https://github.com/marketplace/actions/generate-plantuml
[GitHubActions]: https://github.com/features/actions
[GitHubPages]: https://pages.github.com/
[AWSForPlantUML]: https://github.com/awslabs/aws-icons-for-plantuml