Written by Kamil Gruszowski
Published January 9, 2025

How to master your architecture with the C4 model and Structurizr?

 

Have you ever found it difficult to understand what your colleagues in other teams do, where they obtain their data, how they utilize the information you send them, or even the reasons behind their actions?

If so, welcome aboard! I’ll be your guide through the seas of common misunderstandings, the shallows of challenging design decisions, and the uncharted waters of architectural ambiguity. Over the past few years, I’ve been working in the Advertising Solutions unit, where several teams collaborate to deliver the best possible experience for booking, reporting, and managing ads on Schibsted’s websites. I’ve encountered all of the challenges I mentioned above.

 

From Isolation to the Need for a Shared Vision

Based on my experience, I know that early in your career, it is comfortable to stay within your bubble—writing code that only applies to your specific domain and occasionally stepping out of your comfort zone to implement an endpoint for another team to use. However, as you gain more experience and collaborate with other teams, you come to realize that your work is just one part of a larger puzzle. You begin to strive for your piece to fit perfectly. At this stage, you gradually construct a mental model of the entire ecosystem, which helps facilitate smoother cross-team cooperation. Eventually, though, you may hit a wall…

Why? Probably for one of two reasons: either you struggle to communicate your model, or your model differs from those of others. One of my ‘aha’ moments happened during a meeting with our architecture group as we tried to improve an existing solution that spans four teams. We discussed problems and potential solutions, but as time went by, it became increasingly clear that, even though we were talking about the same thing, we didn’t fully understand each other.

We decided to pause, step back, and examine where our perspectives were misaligned. It turned out that everyone had a different understanding of the current system. Everyone was well-versed in their specific part, but trying to piece everything together led to frequent misunderstandings. This raised an important question: how can we bridge the gap in our shared model and ensure that everyone is on the same page? Certainly, we could have prepared UML diagrams that would only be understood by a few, which might have looked professional, but could we find a better approach?

 

C4: A Comprehensive Framework for Modeling and Visualising System Architecture

Don’t get me wrong—UML is intended for these kinds of tasks and could have effectively addressed the issue. However, many people find UML intimidating, particularly non-technical individuals who may struggle to comprehend it. Fortunately, there is an alternative solution: the C4 model.

C4, which stands for Context, Containers, Components, and Code, is a technique created by Simon Brown to address the ambiguity of software architecture by providing a simple yet powerful set of tools to visualise your solutions. It relies on four levels that describe the system architecture from different perspectives. Unlike UML, it primarily uses a single type of box and line to represent concepts, making it more accessible and straightforward.

System Context diagram

Often referred to as the Big Picture, this is the initial level of diagramming and is particularly useful for non-technical individuals. It illustrates the system in the context of other systems with which it interacts. Each box in the diagram represents a distinct system, while the lines and labels between them indicate the connections and the purpose of each interaction. Details are not essential at this level, as the diagram aims to provide a starting point for further discussion or a quick overview of the system’s functions. You can present it to customers to briefly explain what your system does or use it to identify third-party systems within your organization. Since it is relatively simple to create, having this diagram defined for each team is highly recommended.

Container diagram

A quick disclaimer upfront: this is not about Docker containers! In this context, we define containers as single deployable units within your system, such as a web application, a database, or file storage. This overview is still high-level, but it helps stakeholders understand the components that make up your system. At a glance, it can indicate the architectural style—whether it’s monolithic or microservices-based—what the main technologies are, and how the building blocks interact. The level of abstraction is designed to be accessible to non-technical parties, while still primarily targeting technical teams.

Together, the system context and container diagrams help foster confidence across teams regarding the current organizational architecture. Having these diagrams in place has been transformative for us, filling a critical gap in our understanding. With shared knowledge established, it became easier to tackle existing challenges. Multiple perspectives emerged, enabling teams to approach issues from new angles. I highly recommend investing time to bring key engineers from each team together to create and refine these diagrams; the benefits will be substantial.

Component diagram

If you need more details, the next level—the component diagram—can be very useful. This diagram illustrates how each container functions internally. Every container consists of multiple components. For example, consider a microservice responsible for communication among users. You might identify several components within it, each serving a different purpose, such as an email component for message dispatch, a messaging component for queue handling, and a user permissions component to check rights for sending specific types of messages.

Since this is a more detailed, low-level diagram, it’s best used when it adds real value for your team. It can provide a helpful overview of your system’s internals, which is especially beneficial for onboarding new team members. If this aligns with your needs, feel free to proceed with it; otherwise, you may choose to skip this level.

Code diagram

The final level delves into how the code is implemented, and, no surprise—it involves UML! UML remains one of the best tools for representing code structure at this level. However, it’s a highly optional layer that can often be generated by your IDE. Given how frequently classes change, maintaining it can be challenging.

 

C4 Diagrams as a Common Language of Architecture

Now that you grasp the basics of the C4 approach, let’s apply it to an imaginary scenario to visualize and describe an e-commerce system. Imagine that you are the architect or tech lead of a shopping microservice responsible for viewing products and processing orders. Your goal is to place your system in the context of other systems it interacts with and provide a high-level overview of its operations so that engineers from other teams can understand your solution. This is an ideal situation for creating both the System Context and Container diagrams. 

Let’s dive in and prepare a System Context diagram.

In this scenario, your system takes orders from users, checks item availability in the warehouse, processes the orders, requests delivery from other microservices, and sends a notification to the user with order confirmation. With this information, the diagram might look as follows:

If you present this diagram, it will clearly convey the purpose of your system and the teams involved. This clarity is particularly beneficial when communicating with stakeholders, as it illustrates the connections and explains the interactions between these systems. 

To enhance understanding, be sure to include a legend that defines all the key concepts.

It’s time to delve deeper and explain to your colleagues how your system operates, how it processes data, what the connection points are, and which technologies are utilized. This is where the Container diagram becomes important—use it to illustrate the inner workings of your system.

After reviewing this diagram, other engineers may not dive deeply into your code to fully understand its implementation—and that’s not the intended purpose. Instead, the diagram will provide them with a solid understanding of the key components involved. It can serve as a great starting point for discussions such as, “Why are you using MySQL? Have you considered migrating to PostgreSQL for better performance?” or “How’s that message broker working out? Our team struggles with processing large data loads and is looking for a solution to decouple our systems.” 

This fosters a valuable environment for sharing knowledge, challenging existing solutions, and exploring potential improvements. Ultimately, your system is not an isolated entity; it exists within the company ecosystem and influences other systems. A bottleneck in one system can negatively impact the performance of downstream systems, making it essential to have a shared understanding of the overall architecture across the organization. Leveraging the experiences of others can also enhance existing solutions.

This example illustrates the true strength of the C4 model: its simplicity and elegance. This model prioritizes clear communication over rigid rules and formalities. With just a few simple shapes, you can create a diagram that anyone can grasp at a glance. In my opinion, this is a significant drawback of formal methods like UML, which depend on complex notations that can be difficult to comprehend without prior knowledge. Cognitive load is often overlooked, but it’s critical for preventing misunderstandings. More notations can lead to a more powerful language, but they also increase the chances of confusion. Ultimately, the goal is to ensure that everyone in your group is aligned with minimal effort.

 

Diagram as Code is a New King!

Now that we have a common understanding of our systems and a well-designed diagram to represent them, we’re in a better position to focus on new features and tackle fresh challenges. However, a year later, when we revisit our diagram to address a new problem, we find that the model has become completely outdated. Why does this happen? What went wrong?

There could be several reasons for this. Perhaps the individuals responsible for maintaining the documentation have left the company, the teams making changes didn’t notify each other, or the tool used for diagramming is no longer available, making it feel overwhelming to recreate it from scratch. 

Regardless of the reasons, the solution is straightforward: turn it into code! As a strong advocate for the Infrastructure as Code approach, I was excited to discover the Diagram as Code technique. The concept of using a Domain-Specific Language (DSL) to describe your architecture in just a few lines greatly simplifies maintenance, sharing, and updates. 

The C4 model works exceptionally well with a tool called Structurizr, which was also developed by Simon Brown. Let’s explore how we can transform our diagram into code.

workspace "E-commerce system" {

    !identifiers hierarchical

    model {
    	user = person "Customer"
    	shopping = softwareSystem "Shopping System" "Allows customers to search, view and buy products." {
        	tags = "Main System"
        	web = container "Web Application" {
            	tags = "Web"
            	technology "PHP"
        	}
        	db = container "Database Schema" {
            	technology "MySQL"
            	tags "Database"
        	}
        	queue = container "Message Broker" {
            	technology "RabbitMQ"
            	tags "Queue"
        	}
        	search = container "Search Engine" {
            	technology "OpenSearch"
            	tags "Database"
        	}
        	cache = container "Cache Storage" {
            	technology "Redis"
            	tags "Database"
        	}
    	}
    	inventory = softwareSystem "Inventory System" "Manages the inventory" {
        	tags "Internal System"
    	}
    	delivery = softwareSystem "Delivery System" "Takes orders and prepares delivery." {
        	tags "Internal System"
    	}
    	email = softwareSystem "E-mail System" {
        	tags "External System"
    	}

    	user -> shopping.web "Views products and does shopping"
    	shopping.web -> inventory "Checks available products"
    	shopping.web -> shopping.db "Reads from and writes to"
    	shopping.web -> email "Sends notifications using"
    	shopping.web -> shopping.queue "Sends order confirmation"
    	shopping.web -> shopping.search "Searches for products"
    	shopping.web -> shopping.cache "Caches the most popular product lists"
    	shopping.queue -> delivery "Requests a delivery"
    	delivery -> inventory "Updates inventory"
    	email -> user "Sends e-mails to"
    }

    views {
    	systemContext shopping "ShoppingSystemContextDiagram" {
        	include *
        	autolayout lr
    	}

    	container shopping "ShoppingContainerDiagram" {
        	include *
            autolayout lr
    	}

    	styles {
        	element "Element" {
            	color #FFFFFF
        	}
        	element "Person" {
            	background #1560BD
            	shape Person
        	}
        	element "Container" {
            	background #1560BD
        	}
        	element "Main System" {
            	background #002366
            	color #FFFFFF
        	}
        	element "Internal System" {
            	background #1560BD
        	}
        	element "External System" {
            	background #616569
        	}
        	element "Web" {
            	shape WebBrowser
        	}
        	element "Database" {
            	shape Cylinder
        	}
        	element "Queue" {
            	shape Pipe
        	}
    	}
    }

    configuration {
    	scope softwaresystem
    }

}

 

The file is structured into two main components: models and views, followed by some configuration details. The models outline the fundamental elements, which include systems and containers along with their metadata, as well as the connections between them. The views represent the diagrams and provide options for styling them.

Using this tool, it’s quite straightforward to create a System Context diagram and link it to the system’s Container diagram. This functionality offers a useful zoom feature that enhances interactivity throughout the entire diagram. You can try it for yourself by copying and pasting the code into the sandbox provided by the creators. For additional examples and guides, please refer to the tool’s documentation.

To improve accessibility, consider adopting our approach and hosting the results of the work on your organization’s intranet. You have several options to choose from, starting with the simplest method: using the appropriate Docker image along with your DSL files. For more advanced needs, there are on-premise solutions and paid cloud services provided by the creators that facilitate hosting. Ultimately, the choice depends on your requirements; you can explore all the possibilities by visiting the tool’s documentation. By hosting it, everyone will be able to revisit the diagram whenever needed, without the hassle of running specific applications or utilising the sandbox I mentioned earlier.

Therefore, there are no more excuses for neglecting your documentation. I strongly recommend creating a file that outlines your diagram, storing it in your code repository, collaborating with others on it, and revisiting it every few months to keep it up to date.

 

The C4 Model in a Broader Context: System Landscape and Deployment Diagrams

This is just a brief overview of the possibilities that the C4 model offers. The techniques mentioned work well for describing a single system within the context of other systems it interacts with. However, in reality, systems within an organization often use only a subset of the entire system portfolio. If you are responsible for a larger organization or seek a broader view of the systems, you can create a System Landscape Diagram. This diagram serves as a system context diagram for all relevant systems and can be prepared similarly.

Additionally, the C4 model can also be utilized to create a Deployment Diagram, which illustrates how instances of systems or containers are deployed onto your infrastructure. This diagram can be a valuable complement to the configuration files used in Infrastructure as Code setups. For more examples, please visit the C4 model website.

 

Empowering Teams Through the C4 Process

The modeling process involves gathering the right people, equipping them with the appropriate tools to enhance communication, and allowing time to explore the architecture. In my opinion, this is the most crucial aspect! While a polished diagram can be impressive to present, it is just a bunch of rectangles and lines. The real value lies in the meaning behind those shapes and the shared understanding among those who create them. The C4 model, in conjunction with Structurizr, provides an excellent set of tools to improve this process, making architecture easier to comprehend and maintain. If you’re looking for techniques to empower your teams, give it a try—you won’t regret it.

Written by Kamil Gruszowski
Published January 9, 2025