Using Pulumi and Structurizr to Build a C4 Model

Anuj Agarwal
5 min readJul 31, 2024

--

In modern software development, it’s crucial to have clear and effective ways to design, manage, and visualize your infrastructure and architecture.

Combining Pulumi and Structurizr can provide a powerful way to manage and visualize your infrastructure and software architecture. Pulumi allows you to define your infrastructure as code, while Structurizr enables you to visualize your architecture using the C4 model. This guide will walk you through an example of how to use both tools to build a C4 model for a simple web application.

Basic of C4 Model , Pulumi and Structurizr and how they can help you.

What is the C4 Model?

The C4 model is a simple yet powerful way to visualize software architecture. It breaks down the architecture into four levels:

  1. Context Diagram: Shows how your system interacts with external users and systems.
  2. Container Diagram: Details the high-level technology components, like web servers, databases, and APIs.
  3. Component Diagram: Focuses on the internal structure of each container, showing how different parts of your system work together.
  4. Code (or Class) Diagram: Provides the most detailed view, focusing on the implementation details like classes and their relationships.

By using these diagrams, everyone from developers to business stakeholders can understand the architecture at the right level of detail.

What is Pulumi?

Pulumi is a tool that lets you define your cloud infrastructure using code. Instead of manually setting up servers, databases, and other resources, you can write scripts in familiar programming languages (like JavaScript, Python, or TypeScript) to describe what your infrastructure should look like. Pulumi then takes care of creating and managing these resources for you. This approach, known as Infrastructure as Code (IaC), ensures that your infrastructure is consistent, version-controlled, and easy to update.

What is Structurizr?

Structurizr is a tool for creating and visualizing software architecture diagrams using the C4 model. It allows you to describe your architecture with code, ensuring that your diagrams are always up-to-date with the actual implementation. Structurizr helps teams understand the structure of their system, how components interact, and how everything fits together, making it easier to design, communicate, and document software architectures.

Prerequisites

  1. Pulumi installed and configured.
  2. Structurizr account and API access.
  3. Basic knowledge of JavaScript/TypeScript for Pulumi and C4 modeling concepts.

Step 1: Define Infrastructure with Pulumi

First, let’s set up a simple infrastructure using Pulumi. We’ll create a basic web application with an API server and a database.

  1. Install Pulumi CLI: Follow the installation guide on the Pulumi website.
  2. Set Up a New Pulumi Project:
mkdir pulumi-structurizr-c4
cd pulumi-structurizr-c4
pulumi new aws-typescript
  1. Define Infrastructure:

In index.ts, define a simple web application infrastructure.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Create an S3 bucket to host the website
const bucket = new aws.s3.Bucket("my-bucket", {
website: {
indexDocument: "index.html"
}
});

// Create a Lambda function for the API
const api = new aws.lambda.Function("my-api", {
runtime: aws.lambda.NodeJS14dX,
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./api")
}),
handler: "index.handler",
environment: {
variables: {
"BUCKET": bucket.bucket
}
}
});

// Grant the Lambda function read access to the bucket
const bucketPolicy = new aws.s3.BucketPolicy("bucketPolicy", {
bucket: bucket.bucket,
policy: bucket.bucket.apply(publicReadPolicyForBucket)
});

function publicReadPolicyForBucket(bucketName: string): string {
return JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: ["s3:GetObject"],
Effect: "Allow",
Resource: [
`arn:aws:s3:::${bucketName}/*`
],
Principal: "*"
}]
});
}

export const bucketName = bucket.bucket;
export const apiUrl = api.arn;

Step 2: Visualize Architecture with Structurizr

Next, we’ll visualize the architecture defined in Pulumi using Structurizr.

  1. Install Structurizr CLI: Follow the installation guide on the Structurizr website.
  2. Create Structurizr Model:

Create a new file structurizr.ts to define the C4 model.

import { Workspace, Container, Component } from "structurizr-typescript";

// Define the workspace
const workspace = new Workspace("Pulumi Structurizr Example", "An example C4 model using Pulumi and Structurizr");

// Define the system context
const system = workspace.model.addSoftwareSystem("My Web Application", "A simple web application");
const user = system.addPerson("User", "A user of the web application");
user.uses(system, "Uses");

// Define containers
const webApp = system.addContainer("Web Application", "Allows users to interact with the system", "HTML, CSS, JavaScript");
const api = system.addContainer("API", "Handles business logic", "Node.js");
const database = system.addContainer("Database", "Stores data", "Amazon S3");

user.uses(webApp, "Uses");
webApp.uses(api, "Makes API calls to");
api.uses(database, "Reads/Writes data from/to");

// Define components
const controller = api.addComponent("Controller", "Handles HTTP requests", "Node.js");
const service = api.addComponent("Service", "Contains business logic", "Node.js");
const repository = api.addComponent("Repository", "Handles data access", "Node.js");

webApp.uses(controller, "Makes requests to");
controller.uses(service, "Delegates to");
service.uses(repository, "Uses");

// Export the model to JSON
const fs = require("fs");
fs.writeFileSync("workspace.json", JSON.stringify(workspace.toJson(), null, 2));

Step 3: Integrate Pulumi and Structurizr

We will create a script that generates Structurizr diagrams based on the Pulumi deployment.

Deploy Infrastructure with Pulumi:

pulumi up

Generate and Upload Structurizr Model:

Create a script generate-structurizr-model.ts to combine Pulumi outputs and Structurizr model.

import * as pulumi from "@pulumi/pulumi";
import { Workspace, Container, Component } from "structurizr-typescript";
import * as fs from "fs";
import axios from "axios";

// Load Pulumi outputs
const config = new pulumi.Config();
const bucketName = config.require("bucketName");
const apiUrl = config.require("apiUrl");

// Define the Structurizr model
const workspace = new Workspace("Pulumi Structurizr Example", "An example C4 model using Pulumi and Structurizr");

const system = workspace.model.addSoftwareSystem("My Web Application", "A simple web application");
const user = system.addPerson("User", "A user of the web application");
user.uses(system, "Uses");

const webApp = system.addContainer("Web Application", "Allows users to interact with the system", "HTML, CSS, JavaScript");
const api = system.addContainer("API", "Handles business logic", "Node.js");
const database = system.addContainer("Database", `Stores data (S3 bucket: ${bucketName})`, "Amazon S3");

user.uses(webApp, "Uses");
webApp.uses(api, `Makes API calls to ${apiUrl}`);
api.uses(database, "Reads/Writes data from/to");

const controller = api.addComponent("Controller", "Handles HTTP requests", "Node.js");
const service = api.addComponent("Service", "Contains business logic", "Node.js");
const repository = api.addComponent("Repository", "Handles data access", "Node.js");

webApp.uses(controller, "Makes requests to");
controller.uses(service, "Delegates to");
service.uses(repository, "Uses");

// Export the model to JSON
fs.writeFileSync("workspace.json", JSON.stringify(workspace.toJson(), null, 2));

// Upload to Structurizr
const structurizrApiUrl = "https://api.structurizr.com";
const structurizrWorkspaceId = "YOUR_WORKSPACE_ID";
const structurizrApiKey = "YOUR_API_KEY";
const structurizrApiSecret = "YOUR_API_SECRET";

axios.put(`${structurizrApiUrl}/workspaces/${structurizrWorkspaceId}`, {
workspace: workspace.toJson(),
}, {
headers: {
"X-Authorization": `apikey ${structurizrApiKey}:${structurizrApiSecret}`
}
}).then(() => {
console.log("Structurizr model uploaded successfully.");
}).catch((error: any) => {
console.error("Error uploading Structurizr model:", error);
});

Replace "YOUR_WORKSPACE_ID", "YOUR_API_KEY", and "YOUR_API_SECRET" with your Structurizr workspace ID, API key, and API secret.

By combining these tools, you can maintain a consistent and up-to-date view of your system’s architecture, facilitating better communication and collaboration among your team members. This approach ensures that your infrastructure and architecture documentation are always in sync with the actual deployment, promoting transparency and clarity in your development process.

--

--

Anuj Agarwal
Anuj Agarwal

Written by Anuj Agarwal

Director - Technology at Natwest. Product Manager and Technologist who loves to solve problems with innovative technological solutions.

No responses yet