This article is part of a series.




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 Domain-Specific Language specific for this use case 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.

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).

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:

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)

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)

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)

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:

Let’s have a look at a simple example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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"
            }
        }
    }
}
Code Snippet 1: Structurizr DSL: Basic structure

What do we have?

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

Installation

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

1
docker run -it --rm -p 1337:8080 -v ./:/usr/local/structurizr structurizr/lite

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.

👉 I’ve setup a github repository with the code I’ll be using in the next sections. Feel free to clone from https://github.com/dorneanu/ripmail.

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:

👉 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
      }
    }
  }
}
Code Snippet 2: Basic Structurizr construct

So, what do we have?

Let’s focus more on the model:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

...

  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"
          }
        }
      }

   ...
Code Snippet 3: Adding actors and different systems

We have following elements and groups:

Additionally we have these systems inside the group:

Main backend system

Now the backend system responsible for the business logic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
...

      // 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"
          }
        }

...
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:

Relationships

And finally the relationships between different components:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
        // 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
...
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 {
  // ...
  }
}
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):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...

       // 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" {...}
       }

...
Code Snippet 7: Outline main organizational units (OUs)

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.

1
2
3
4
    // System Landscape
    systemlandscape "SystemLandscape" {
      include *
    }
Code Snippet 13: The System Landscape where every component (*) is included in the diagram

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).

1
2
3
4
5
    // Deployment live
    deployment backend live "LiveDeployment"  {
      include *
      description "An example live deployment for the self-destructing email service"
    }
Code Snippet 14: The deployment view for live

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.

1
2
3
4
5
    // Backend
    container backend "Containers_All" {
      include *
      # autolayout
    }
Code Snippet 15: The container view for all resources

And now for specific services:

Extra features

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

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:

Resources

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

Tools:

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.

This article is part of a series.