Enhancing Software Design with Diagrams as Code
Episode #14: Leverage Diagrams as Code tools like PlantUML for creating clear, visual representations of complex systems and workflows in cloud environments.
Introduction
This is the second of a series of articles about System design.
In this article, we are going for a different approach. Instead of discussing system design concepts, we will introduce an indispensable tool for everyone to document a system design solution.
We will discuss the concept of "Diagrams as Code" and the common tools and practices used to draw diagrams such as AWS cloud components, Kubernetes services, and Sequence diagrams.
The article provides many examples of how to use PlantUML, which is my choice of Diagrams as Code tools for system architecture diagrams.
Such an article should appeal to a wide range of professionals including software developers, DevOps, system architects, data scientists, academics, and technical writers, due to its relevance in automating and enhancing the visualisation of complex systems and workflows.
The use of Diagrams as Code is not limited to documentation use cases, but it can also be used for live system design interviews.
Disclaimer
The code examples are only for illustrative purposes.
I copied them from the official documentation of the respective GitHub projects.
The purpose of those examples is just to illustrate what you can achieve with PlantUML.
Diagrams as code
Diagrams as Code is an approach where diagrams are defined and managed using code, rather than traditional manual drawing tools.
This method enables the diagrams to be scripted, version-controlled, and integrated into build pipelines for automatic documentation generation.
It leverages text-based tooling, familiar to most software developers, making diagrams easily maintainable.
This approach is particularly beneficial for creating software architecture diagrams and supports a range of cloud providers and infrastructure elements.
Between the tools available we can find:
PlantUML - DSL (Domain Specific Language) written in Java. It supports a wide range of diagram types including sequence, use case, class, object, activity, component, deployment, state, and network diagrams.
Mermaid - supports a more limited range of diagram types compared to PlantUML. It is written in Javascript and supported by Github.
Diagrams - write cloud system diagrams in Python code.
UML diagrams
Here it is an example of a simple Sequence diagram created in PlantUML with attached code and graphical representation.
@startuml
actor User
participant "First Component" as A
participant "Second Component" as B
User -> A: initiates process
activate A
A -> B: forwards request
activate B
B --> A: returns response
deactivate B
A --> User: displays result
deactivate A
@enduml
The below diagram has been generated with an online tool called PlantText and the "Sketchy outline" theme.
Kubernetes support for PlantUML
In this section, we are going to provide a more complex example, that involves drawing a component diagram for a Kubernetes web service served by two pods behind a load balancer.
This PlantUML diagram discussed here uses the Kubernetes PlantUML extension, which provides custom icons for Kubernetes concepts like Pods, Load Balancers and much more.
@startuml kubernetes
footer Kubernetes Plant-UML
scale max 1024 width
skinparam nodesep 10
skinparam ranksep 10
' Kubernetes
!define KubernetesPuml https://raw.githubusercontent.com/dcasati/kubernetes-PlantUML/master/dist
!includeurl KubernetesPuml/kubernetes_Common.puml
!includeurl KubernetesPuml/kubernetes_Context.puml
!includeurl KubernetesPuml/kubernetes_Simplified.puml
!includeurl KubernetesPuml/OSS/KubernetesSvc.puml
!includeurl KubernetesPuml/OSS/KubernetesPod.puml
actor "User" as userAlias
left to right direction
' Kubernetes Components
Cluster_Boundary(cluster, "Kubernetes Cluster") {
Namespace_Boundary(ns, "Web") {
KubernetesSvc(svc, "service", "")
KubernetesPod(pod1, "web-pod1", "")
KubernetesPod(pod2, "web-pod2", "")
}
}
Rel(userAlias,svc,"get HTTP/1.1 index.html", "1")
Rel(svc,pod1,"load Balances to Pods", "2")
Rel(svc,pod2,"load Balances to Pods", "2")
Rel_U(pod1, svc, "serves content", "3")
Rel(svc, userAlias, "return content to", "4")
@enduml
As you can see above before you can use a custom component KubernetesPod
, you need first to import the PUML file defining that component.
Here we are defining an alias KubernetesPuml
so that we can simplify the URL of each command !includeurl
and only provide the relative path to the GitHub URL.
AWS support for PlantUML
In this section, we are going to introduce AWS icons for PlantUML, a set of icons for drawing system architecture diagrams for AWS services.
Similarly to the previous example in Kubernetes, for each custom component, you need to import the relative PUML file from the relative GitHub repo.
@startuml VPC
' Uncomment the line below for "dark mode" styling
'!$AWS_DARK = true
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v17.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/AWSSimplified.puml
!include AWSPuml/Compute/EC2.puml
!include AWSPuml/Compute/EC2Instance.puml
!include AWSPuml/Groups/AWSCloud.puml
!include AWSPuml/Groups/VPC.puml
!include AWSPuml/Groups/AvailabilityZone.puml
!include AWSPuml/Groups/PublicSubnet.puml
!include AWSPuml/Groups/PrivateSubnet.puml
!include AWSPuml/NetworkingContentDelivery/VPCNATGateway.puml
!include AWSPuml/NetworkingContentDelivery/VPCInternetGateway.puml
hide stereotype
skinparam linetype ortho
AWSCloudGroup(cloud) {
VPCGroup(vpc) {
VPCInternetGateway(internet_gateway, "Internet gateway", "")
AvailabilityZoneGroup(az_1, "\tAvailability Zone 1\t") {
PublicSubnetGroup(az_1_public, "Public subnet") {
VPCNATGateway(az_1_nat_gateway, "NAT gateway", "") #Transparent
}
PrivateSubnetGroup(az_1_private, "Private subnet") {
EC2Instance(az_1_ec2_1, "Instance", "") #Transparent
}
az_1_ec2_1 .u.> az_1_nat_gateway
}
AvailabilityZoneGroup(az_2, "\tAvailability Zone 2\t") {
PublicSubnetGroup(az_2_public, "Public subnet") {
VPCNATGateway(az_2_nat_gateway, "NAT gateway", "") #Transparent
}
PrivateSubnetGroup(az_2_private, "Private subnet") {
EC2Instance(az_2_ec2_1, "Instance", "") #Transparent
}
az_2_ec2_1 .u.> az_2_nat_gateway
}
az_2_nat_gateway .[hidden]u.> internet_gateway
az_1_nat_gateway .[hidden]u.> internet_gateway
}
}
@enduml
In this example, we are drawing a network diagram to display how two EC2 instances are deployed in a private Subnet and connected to the Internet via a NAT gateway and an Internet gateway inside a VPC.
Resources
Tutorials about Diagrams a Code:
Diagrams as code - comprehensive article by Alex Xu on multiple options for Diagrams as code.
PlantUML vs Mermaid - Comparison between diagrams as code tools
PlantUML vs Mermaid - LibHunt comparison between PlantUML and Mermaid
Tools to support PlantUML:
PlantUML - Visual Studio Code extension for PlantUML
Support for other cloud providers:
GCP icons for PlantUML - Support for GCP icons in PlantUML
Want to connect?
👉 Follow me on LinkedIn and Twitter.
If you need 1-1 mentoring sessions, feel free to check my Mentorcruise profile.
I love when I find new tools that make my life easier. I'm definitely checking out PlantUML.
PlantUML is great, and there are so many extensions for it (including C4 Model - https://github.com/plantuml-stdlib/C4-PlantUML - a great choice for Application Architects, I think).
When it comes to visualising Application/Software Architecture we have a few options:
1. Whiteboards
2. Visual Diagramming tools
3. Diagrams as Code
4. Architecture as Code
All of these are valid options in their own context from a quick architecture design discussion to a long-term enterprise level architecture management.
As a Solution Architect, I've been using Architecture as Code with C4InterFlow - my own open-source Architecture as Code framework - https://github.com/SlavaVedernikov/C4InterFlow, which extend C4 Model with two additional concepts - Interfaces and Flows.
To me this is the most optimal approach for a long-term architecture management, especially when it comes to large scale evolving architectures.
With Architecture as Code (e.g. in YAML) you need to express the Architecture Model only once and then you can generate 100s of diagrams of
- different Types (e.g. C4, Sequence etc)
- for different Scopes (e.g. All Software Systems, Application Domain, a single Software System etc)
- of different Levels of Detail (i.e. C4 Context/Container/Component etc.)
Checkout some example here - https://c4interflow.github.io/architecture-as-code-samples-visualiser