DEV Community

Cover image for Documentation as Code for Cloud - C4 Model & Structurizr
Victor Dorneanu
Victor Dorneanu

Posted on • Originally published at blog.dornea.nu on

Documentation as Code for Cloud - C4 Model & Structurizr

Introduction

In the last post, I've used PlantUML to draw things like groups, accounts, and clusters. However, I didn't focus on how different parts inside the business layer interact (usually components related to the main application/system relevant for your business). Now, we'll use a DSL to show these interactions between components, services, and systems. I'll use the C4 model to show the same system in different ways based on who we're showing it to. It allows us to adjust how much detail we include.

image

👉 To learn how this diagram was created, continue reading 👇

C4 Model

The C4 model was developed by Simon Brown as a means of providing a visual map of system components across four levels of abstraction, as suggested by its title. Each level of abstraction in the C4 model suits different audiences, from the non-technical management level to detailed developer perspectives, each level of abstraction is tailored to meet its observer's understanding. To maintain consistency when describing the system design, the C4 model uniformly applies the same terminology and abstractions across all its levels, effectively implementing ubiquitous language principles from Domain-Driven Design (DDD).

image

👉 The 4 perspectives in the C4 model

Abstractions

The C4 model uses abstractions to form an hierarchy of well-defined diagrams (at different levels). Currently these abstractions are available:

  1. Person

    • Represents human users interacting with the system (e.g., Administrator, End User, Customer).
  2. System

    • A top-level view showing different people interacting with different software systems. (e.g., E-commerce Platform, Payment Gateway, our self-destructing email service 😎).
  3. Container

    • Involves zooming into an individual system to reveal containers within. Examples include server-side applications, client-side applications, databases, etc.
    • not to be confused with Docker containers
  4. Component

    • Dives deeper into an individual container to expose its components, like classes, interfaces or objects in your code.

Diagram types

Level 1: Context diagram

Shows how your system fits into the larger system environment (system landscape). It basically shows interactions between users and systems:

  • e.g. A payment system interacting with an user and a banking system

image

👉 Context diagram

Level 2: Container diagram

Higher level view within a system itself. Shows software "containers" like web servers, standalone apps, or databases. (e.g., An API server, a database, and a client app in a single system)

image

👉 Container diagram

Level 3: Component diagram

Shows internal parts of a container. Mostly used with complex software. (e.g., Controllers, services, repositories inside of a web application)

image

👉 Component diagram

Level 4: Code diagram

A detailed view of the code level. For systems with little internal complexity, it can be skipped. (e.g., UML class diagrams)

image

👉 Code diagram

Structurizr DSL

Structurizr is used for describing and visualizing architecture using the C4 model. One of the main selling points is the fact you can define an entire (IT) architecture model using text. A typical model consists of:

  • relationships between abstractions
  • different views

Let's have a look at a simple example:

workspace {

    model {
        user = person "User"

        webApp = softwareSystem "Web Application" {
            tags "System"
        }

        database = softwareSystem "Database" {
            tags "Database"
        }

        team = person "Development Team"

        user -> webApp "Uses"
    }

    views {
        container webApp {
            include *
            autoLayout
        }

        styles {
            element "Database" {
                color "#0000ff"
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
Code Snippet 1: Structurizr DSL: Basic structure

What do we have?

  • *Entities*:
    • "User": a person who uses the "Web Application".
    • "Web Application": a software system tagged as "System".
    • "Database": another software system tagged as "Database".
    • "Development Team": a person representing the team that develops the "Web Application".
  • Relationships:
    • The "User" uses the "Web Application".
  • Container View:
    • Focused on "Web Application".
    • Includes all elements in the model.
    • Uses automatic layout.
  • Styles:
    • The "Database" elements are colored in blue ("#0000ff").

Before we move on, let's briefly discuss the installation steps.

Installation

I'd suggest you use the Docker image for a safe playground:

docker run -it --rm -p 1337:8080 -v ./:/usr/local/structurizr structurizr/lite
Enter fullscreen mode Exit fullscreen mode

This will fetch the structurizr/lite Docker image from Dockerhub, start the container, mount the current working directory to /usr/local/structurizr and setup a port forwarding from localhost:1337 to <docker container>:8000.

Info Notice:

👉 I've setup a GitHub repository with the code I'll be using in the next sections. Feel free to clone from here.

Short recap

If you recall my initial post the entire aim was to document a hypothetical self-destructing e-mail service. In my 2nd blog post (about PlantUML) I've generated following sequence diagram:

image

👉 Full PlantUML Code

In the following I'll try to implement exactly this workflow using C4 and Structurizr DSL.

ripmail

👉 Checkout the code at https://github.com/dorneanu/ripmail.

Model

Let's start with the basic construct:

workspace {  ❶
  name "Self-Destructing Email Service"
  description "The sofware architecture of the self-destructing email service"

  model {    ❷
    //  ...
  }

  views {    ➌
    // System Landscape
 ❺ systemlandscape "SystemLandscape" {
      include *
      # autoLayout
    }

    // Themes
    // You can combine multiple themes!
 ❻ theme https://static.structurizr.com/themes/amazon-web-services-2023.01.31/theme.json

    styles { ❹
      element "Person" {
        color #ffffff
        fontSize 22
        shape Person
      }
      element "Sender" {
        color #ffffff
        background #8FB5FE
        shape Person
      }
      element "Recipient" {
        color #ffffff
        background #E97451
        shape Person
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
Code Snippet 2: Basic Structurizr construct

So, what do we have?

  • workspace
    • Defines the workspace for a self-destructing email service.
  • model
    • This has to be implemented but basically it's a
    • Placeholder section where you'd define the elements (software systems, people, containers) and their relations.
  • views
    • A System Landscape view ❺ that includes all elements defined in the model.
    • The specified theme ❻ comes from an external JSON file, allowing broad customization of the look-and-feel.
    • Three styles are defined for different types of elements labeled as Person, Sender, and Recipient. These characters are all represented by the Person shape.

Let's focus more on the model:


...

  model {
 ❶ sender = person "Sender" "Sender creates self-destructing email" {
      tags "Sender"
    }
 ❷ recipient = person "Recipient" "Recipient receives self-destructing email" {
      tags "Recipient"
    }

 ➌ group "Self-Destructing Email Service" {
      // Logging keeps track of several events
 ❹   logging = softwaresystem "Logging System" "Logs several events related to mail generation" {
        tags "Service API"
      }
 ❺   storage = softwaresystem "Storage System" "Stores encrypted mail content" {
        tags "Database"
        storageBackend = container "Storage Backend"
      }

 ❻   notification = softwaresystem "Notification System" "Sends notification to recipient to view email" {
        tags "System"

        // --- Notification Service
        notificationService = group "Notification Service" {
          notificationAPI = container "Notification API" {
            tags "NotificationService" "Service API"
          }
        }
      }

   ...

Enter fullscreen mode Exit fullscreen mode
Code Snippet 3: Adding actors and different systems

We have following elements and groups:

  • Sender: Person who creates the self-destructing email.
  • Recipient: Person who receives the self-destructing email.
  • Self-Destructing Email Service: Represents the overall service/system being described.

Additionally we have these systems inside the group:

  • Logging System: Keeps track of events related to mail creation.
  • Storage System: Stored encrypted email content. Includes a Storage Backend container.
  • Notification System: Sends notification to recipient. Contains a Notification Service group with a Notification API container.

Main backend system

Now the backend system responsible for the business logic:

...

      // Backend system responsible for the business logic
❶    backend = softwaresystem "Backend System" "Contains logic how self-destructing mails should be created and dispatched to the recipient." {
        tags "BackendSystem"
❷       webapplication = container "Web Application"

        // Services/
        // --- Authentication Service
➌      authService = group "Authentication Service" {
❹        authAPI = container "Auth Service API" {
            tags "AuthService" "Service API"
          }
❺        authDB = container "Auth Service Database" {
            tags "AuthService" "Database"
❻          authAPI -> this "Checks if credentials match"
          }
        }


        // --- Email Composition Service
❼      mailCompositionService = group "Email Composition Service" {
❽        mailCompositionAPI = container "Email Composition API" {
            tags "EmailCompositionService" "Service API"
          }
❾        mailDB = container "Email Composition Database" {
            tags "Emailcompositionservice" "Database"
❿          mailCompositionAPI -> this "Stores metadata of mails"
          }

        }

        // --- Email Composition Service
⓫      viewEmailService = group "View Email Service" {
⓬          viewEmailFrontend = container "Email View Frontend" {
            tags "ViewEmailService"
          }
        }

...
Enter fullscreen mode Exit fullscreen mode
Code Snippet 4: Main backend system and ther underlying services

The Backend System is the core system, with business logic, for creating/dispatching self-destructing mails.
of following containers and services:

  • Web Application: The frontend component within the backend system for creating mails.
  • Authentication Service: Group handling user credential verification.
    • Auth Service API: Provides interface for authentication service.
    • Auth Service Database: Stores user credential data.
    • Auth Service API->Auth Service Database: Indicates API checks credentials against this database.
  • Email Composition Service: Group handling creation/storage of emails.
    • Email Composition API: Interface for the email composition service.
    • Email Composition Database: Stores meta-information of emails.
    • Email Composition API -> Email Composition Database: Indicates API stores mail metadata in this database.
  • View Email Service: Group handling email display.
    • Email View Frontend: The frontend component withing the backend system for viewing emails.

Relationships

And finally the relationships between different components:

...
        // Store mail data and encrypted content
        mailCompositionAPI -> storage "Store mail metadata and content"

        // Notify recipient
        mailCompositionAPI -> notificationAPI "Notify recipient"
        notificationAPI -> mailcompositionAPI "Recipient notified"

        // Log events
        notificationAPI -> logging "Log Email sent event"

        // Sender creates new email
        sender -> webapplication "Create new mail"
        webapplication -> authAPI "Authenticate user"
        webapplication -> mailCompositionAPI "Create mails"
        notification -> recipient "Send out notification"
        backend -> logging "Create events"

        // Recipient receives new mail
        recipient -> webapplication "View self-destructing mail"
        webapplication -> viewEmailFrontend "View email"
        viewEmailFrontend -> mailDB
        viewEmailFrontend -> storage
...
Enter fullscreen mode Exit fullscreen mode
Code Snippet 5: Relationships between different components
From To Description
Mail Composition API Storage Store mail metadata and encrypted content
Mail Composition API Notification API Notify recipient
Notification API Mail Composition API Recipient notified
Notification API Logging Log Email sent event
Sender Web Application Create new mail
Web Application Auth API Authenticate user
Web Application Mail Composition API Create mails
Notification Recipient Send out notification
Backend System Logging Create events
Recipient Web Application View self-destructing mail
Web Application View Email Frontend View email
View Email Frontend MailDB Fetches email data for visualization
View Email Frontend Storage Fetches email details for visualization

Deployments

Deployment components are required for the deployment diagrams. These illustrate how software systems are deployed onto infrastructure elements in an environment. They also enable you to visualize how containers within a system map onto the infrastructure.

This kind of diagrams are very important as they provide important information regarding system runtime environment such as scaling, redundancy, network topology, and communication protocols. They are crucial to understanding the physical aspects and deployment context of a system.

workspace {

  model {
  //  ...
  live = deploymentEnvironment "Live" { ❶
    // AWS
    deploymentNode "Amazon Web Services" { ❷
        tags "Amazon Web Services - Cloud"

          // Which region
    ➌     deploymentNode "eu-central-1" {
            tags "Amazon Web Services - Region"
            ...
          }
    }
  }
  }

  views {
  // ...
  }
}
Enter fullscreen mode Exit fullscreen mode
Code Snippet 6: The live deployment environment

We're still defining the model. Now we have defined a deploymentEnvironment live ❶ which should be deployed into the AWS Cloud ❷, namely in eu-central-1 ➌. In this region we'll have different organizational units (OUs):

  • OU: Tech

    This will host the EKS cluster as well as the API Gateway which serves as the main entrypoint for API calls.

  • OU: Security

    This is where alert & monitoring will take place. Also here relevant data to the mail will be stored within S3 buckets. Finally we use IAM capabilities to make sure authentication and authorization works properly.

  • OU: DevOps

    The CI/CD build pipeline and infrastructure provisioning will take place here. The software artefacts will be built here and deployed into the accounts
    inside "OU: Tech".

...

       // Which region
       deploymentNode "eu-central-1" {
         tags "Amazon Web Services - Region"

         // ------------------------------------------------
         // Organizational Unit: DevOps
         // ------------------------------------------------
         deploymentNode "OU-DevOps" {...}

         // ------------------------------------------------
         // Organizational Unit: Tech
         // ------------------------------------------------
         ou_tech = deploymentNode "OU-Tech" {...}


         // ------------------------------------------------
         // Organizational Unit: Security
         // ------------------------------------------------
         deploymentNode "OU-Security" {...}
       }

...
Enter fullscreen mode Exit fullscreen mode
Code Snippet 7: Outline main organizational units (OUs)
  • OU: DevOps

    But one thing at a time:

          // --------------------------------------------
          // Organizational Unit: DevOps
          // --------------------------------------------
          deploymentNode "OU-DevOps" {
    ❶      tags "Amazon Web Services - AWS Organizations Organizational Unit"
    
    ❷        deploymentNode "acc-devops-prod" {
    ➌          tags "Amazon Web Services - AWS Organizations Account"
    
    ❹          vpc_management = deploymentNode "VPC (management)" {
    ❺            tags "Amazon Web Services - VPC Virtual private cloud VPC"
    
    ❻            gitlab_server = infrastructureNode "Gitlab Server" {
    ❼              tags "Amazon Web Services - EC2"
                }
    
              }
            }
          }
    
    Code Snippet 8: Deployment components for the OU DevOps

    The DevOps OU deployment components are:

    • Organizational Unit (OU-DevOps): Represents a unit in the organization.
      • ❶ Tagged as an Amazon Web Services (AWS) Organizations Organizational Unit.
    • acc-devops-prod: Represents an account within the OU-DevOps unit.
      • ➌ Tagged as an AWS Organizations Account.
    • ❹ VPC (vpc_management): A Virtual Private Cloud (VPC) within the acc-devops-prod account.
      • ❺ Tagged as an AWS VPC Virtual private cloud VPC.
    • ❻ Gitlab Server (gitlab_server): Infrastructure node within the VPC.
      • ❼ Tagged as AWS EC2.
  • OU: Security

         // ---------------------------------------------
         // Organizational Unit: Security
         // ---------------------------------------------
    ❶    deploymentNode "OU-Security" {
    ❷      tags "Amazon Web Services - AWS Organizations Organizational Unit"
    
    ➌      deploymentNode "acc-security-logging" {
    ❹        tags "Amazon Web Services - AWS Organizations Account"
    
    ❺        s3_logging = infrastructureNode "S3 Bucket" {
    ❻          tags "Amazon Web Services - Simple Storage Service"
             }
    
    ❼        infrastructureNode "CloudWatch Logs" {
    ❽          tags "Amazon Web Services - CloudWatch Logs"
             }
           }
    
    ❾      deploymentNode "acc-security-monitoring" {
    ❿        tags "Amazon Web Services - AWS Organizations Account"
    
    ⓫        infrastructureNode "CloudWatch" {
    ⓬          tags "Amazon Web Services - CloudWatch Alarm"
             }
           }
         }
    
    
    Code Snippet 9: Deployment components for the OU Security

    The Security OU deployment components are:

    • Organizational Unit (OU-Security): Represents a security unit in the organization.
      • ❶ Tagged as an AWS Organizations Organizational Unit.
    • acc-security-logging: Represents an account within the OU-Security unit for logging purposes.
      • ❹ Tagged as an AWS Organizations Account.
      • ❺ Contains an "S3 Bucket" infrastructure node.
        • ❻ Tagged as AWS Simple Storage Service.
      • ❼ Contains a "CloudWatch Logs" infrastructure node.
        • ❽ Tagged as AWS CloudWatch Logs.
    • acc-security-monitoring: Represents another account within the OU-Security unit for monitoring purposes.
      • ❿ Tagged as an AWS Organizations Account.
      • ⓫ Contains a "CloudWatch" infrastructure node.
        • ⓬ Tagged as AWS CloudWatch Alarm.
  • OU: TECH

    Now the most complicated one:

         // --------------------------------------------
         // Organizational Unit: Tech
         // --------------------------------------------
    ❶    ou_tech = deploymentNode "OU-Tech" {
           tags "Amazon Web Services - AWS Organizations Organizational Unit"
    
    ❷      deploymentNode "acc-tech-prod" {
             tags "Amazon Web Services - AWS Organizations Account"
    
             // EKS control plane
    ➌        eks_vpc = deploymentNode "EKS VPC" {
               tags = "Amazon Web Services - VPC Virtual private cloud VPC"
    
    ❹          eks_control_plane = infrastructureNode "EKS Control Plane" {
                 tags = "Amazon Web Services - EKS Cloud"
               }
             }
    
             // ECR
    ❺        ecr = infrastructureNode "ECR" {
               tags "Amazon Web Services - Elastic Container Registry"
               description "Private ECR registry"
             }
    
             // EKS cluster
    ❻        workload_vpc = deploymentNode "Workload VPC" {...}
    
             // DynamoDB instances
    ❼        dbs = group "Databases" {...}
    
             // S3 (Storage System)
    ❽        s3_storage = infrastructureNode "S3 Bucket (storage)" {
               tags "Amazon Web Services - Simple Storage Service"
             }
    
           }
         }
    
    
    Code Snippet 10: Deployment components for the OU Tech

    This will result in following components hierarchy:

    • ❶ Organizational Unit (OU-Tech): Represents a technology unit within the organization.
    • acc-tech-prod: Represents a production account within the "OU-Tech" unit.
      • EKS control plane deployed within an EKS VPC.
        • EKS VPC: VPC where the EKS control plane is deployed,
        • EKS Control Plane: infrastructure node within EKS VPC.
      • ECR: Infrastructure node for the Elastic Container Registry with private ECR registry.
      • Workload VPC: Deployment node holding different workloads.
      • ❼ Databases group holds DynamoDB instances.
      • S3 Bucket (storage): Infrastructure node for the storage system.

    Let's have a look what's inside the workload VPC:

      // EKS cluster
      workload_vpc = deploymentNode "Workload VPC" {
        tags = "Amazon Web Services - VPC Virtual private cloud VPC"
    
        // AZ 1
    ❶   deploymentNode "Availability Zone 1" {
          tags = "Amazon Web Services - Region"
    
    ❷     deploymentNode "Subnet 1" {
            tags = "Amazon Web Services - VPC VPN Gateway"
    
    ➌       eks_node_group1 = deploymentNode "EKS Managed Node Group" {
              tags = "Amazon Web Services - EKS Cloud"
    
    ❹         eks_node_group1_pod1 = deploymentNode "Pod 1" {
                tags = "Kubernetes - pod"
    ❺           pod1_authAPI = containerInstance authAPI
              }
    
    ❻         eks_node_group1_pod2 = deploymentNode "Pod 2" {
                tags = "Kubernetes - pod"
    ❼           pod2_mailCompositionAPI = containerInstance mailcompositionAPI
    ❽           pod2_notificationAPI = containerInstance notificationAPI
    
              }
    
            }
          }
    
        }
    
        // AZ 2
    ❾   deploymentNode "Availability Zone 2" {
          tags = "Amazon Web Services - Region"
    
    ❿     deploymentNode "Subnet 1" {
            tags = "Amazon Web Services - VPC VPN Gateway"
    
    ⓫       eks_node_group2 = infrastructureNode "EKS Managed Node Group" {
              tags = "Amazon Web Services - EKS Cloud"
            }
    
          }
        }
      }
    
    Code Snippet 11: Components of the workload VPC

    The workload VPC consists of 2 availability zones:

    • Availability Zone 1: A separate deployment node categorized as an AWS Region.
      • Subnet 1: Deployment node within Availability Zone 1.
      • EKS Managed Node Group: Node group for managing EKS resources.
      • Pod 1: Deployment node within the EKS Managed Node Group.
        • ❺ Houses instance of authAPI container.
      • Pod 2: Another deployment node within the EKS Managed Node Group.
        • ❼ Houses instance of mailcompositionAPI container.
        • ❽ Houses instance of notificationAPI container.
    • Availability Zone 2: Another separate deployment node categorized as an AWS Region.
      • Subnet 1: Deployment node within Availability Zone 2.
      • EKS Managed Node Group: Infrastructure node for managing EKS resources.

    Let's also have a look how we can use containerInstance for the databases:

         // DynamoDB instances
    ❶  dbs = group "Databases" {
           deploymentNode "DB VPC" {
             tags = "Amazon Web Services - VPC Virtual private cloud VPC"
    
    ❷      deploymentNode "DynamoDB (Auth)" {
               tags "Amazon Web Services - DynamoDB"
    
    ➌        liveUserDB = containerInstance authDB
             }
    
    ❹      deploymentNode "DynamoDB (Mails)" {
               tags "Amazon Web Services - DynamoDB"
    
    ❺        liveMailDB = containerInstance mailDB
             }
           }
         }
    
    
    Code Snippet 12: Components of the DB VPC

    This will create:

    • Databases: A group of databases within a DB VPC deployment node.
    • DynamoDB (Auth): DynamoDB instance for authentication.
      • ➌ Contains a live instance of authDB.
    • DynamoDB (Mails): DynamoDB instance for mail metadata.
      • ❺ Contains a live instance of mailDB.

Views

Views in Structurizr are used to create visual diagrams of your software architecture model. They provide a way to communicate the different aspects of your system to various stakeholders. Views can be thought of as 'camera angles' on your architecture model, each designed to present a certain perspective of the system.

System Landscape

The System Landscape view in Structurizr is the highest level view of a software system's architecture. It shows all users, software systems and external systems or services in scope. It informs about the overall system context and interaction among systems and users.

    // System Landscape
    systemlandscape "SystemLandscape" {
      include *
    }
Enter fullscreen mode Exit fullscreen mode
Code Snippet 13: The System Landscape where every component (*) is included in the diagram

image

👉 System Landscape

Deployment Live

The Deployment View in Structurizr is a type of view that visualizes the mapping of software building blocks (like Containers or Components) to infrastructure elements, including servers, containers or cloud services. It gives a clear indication of how and where the software system runs in different environments (like development, staging, production).

    // Deployment live
    deployment backend live "LiveDeployment"  {
      include *
      description "An example live deployment for the self-destructing email service"
    }

Enter fullscreen mode Exit fullscreen mode
Code Snippet 14: The deployment view for live

image

👉 Deployment View

Containers

In Structurizr, a Container represents an executable unit (application, data store, microservice, etc.) that encapsulates a portion of your software system. Containers run inside software systems and have interfaces that let them interact with other containers and/or software systems. The Container view shows the internal layout of a software system, specifying contained components and their interactions. This level of abstraction is valuable for developers and others dealing with system implementation and operation.

    // Backend
    container backend "Containers_All" {
      include *
      # autolayout
    }
Enter fullscreen mode Exit fullscreen mode
Code Snippet 15: The container view for all resources

image

👉 Containers (global)

And now for specific services:

  • Notification Service

        container backend "Containers_NotificationService" {
          include ->notificationService->
          autolayout
        }
    
    Code Snippet 16: Container view for the notification service

    image

👉 Container view for the notification service
  • Mail Composition Service

        container backend "Containers_MailCompositionService" {
          include ->mailCompositionService->
          autolayout
        }
    
    Code Snippet 17: Container view for the email composition service

    image

👉 Container view for the Mail Composition Service
  • Authentication Service

        container backend "Containers_AuthenticationService" {
          include ->authService->
          autolayout
        }
    
    Code Snippet 18: Container view for the authentication service

    image

👉 Container view for the Authentication Service

Extra features

With the online version of Structurizr you get access to diffeerent exporting features:

  • PlantUML: Export your model as PlantUML diagrams.
  • C4-PlantUML: Export your model as C4-PlantUML diagrams.
  • Mermaid: Generate Mermaid diagrams from your model.
  • DOT: Export containers and components as DOT graph description language.
  • ilograph: Export the model and the views for ilograph.com

Among these I've found ilograph to be the most interactive one.

Ilograph

Once you've exported your workspace in ilograph format, follow these steps:

And this is what you get:

image

👉 ilograph with code

image

👉 ilograph without code

Resources

The resources I've consumed for generating the content and diagrams for this blog post:

Tools:

  • 2023-10-05 ◦ IcePanel.io

    A visual modelling tool for C4

  • 2023-10-05 ◦ C4-PlantUML

    C4-PlantUML combines the benefits of PlantUML and the C4 model for providing a simple way of describing and communicate software architectures

Articles:

Structurizr:

Videos:

Outlook

In the next post I'll deep-dive into the D2 language which also has a huge set of features. Stay tuned.

Top comments (0)