Table of Contents
- 1. About the book
- 2. About the author
- 3. We have a failure to communicate
- 4. A shared vocabulary
- 5. The C4 model
I graduated from university in 1996, a time when CASE and modeling tools were popular and in common use. I remember attending a training course about the Unified Modeling Language and the SELECT tooling soon after I started my professional career. A number of the projects I worked on made extensive use of tools like SELECT and Rational Rose for diagramming and documenting the design of software systems. With buggy user interfaces and ugly diagrams, the tooling may not have been brilliant back then, but it was still very useful if used in a pragmatic way.
I’m very much a visual person. I like being able to visualise a problem before trying to find a solution. Describe a business process to me and I’ll sketch up a summary of it. Talk to me about a business problem and I’m likely to draw a high-level domain model. Visualising the problem is a way for me to ask questions and figure out whether I’ve understood what you’re saying. I also like sketching out solutions to problems, again because it’s a great way to get everything out into the open in a way that other people can understand quickly.
But then something happened somewhere during the early 2000s, probably as a result of the Manifesto for Agile Software Development that had been published a few years beforehand. The way teams built software started to change, with things like diagramming and documentation being thrown away alongside big design up front. I remember seeing a number of software development teams reducing the quantity of diagrams and documentation they were creating. In fact, I was often the only person on the team who really understood UML well enough to create diagrams with it.
Fast forward to the present day, and creating software architecture diagrams seems to be something of a lost art. I’ve been running software architecture training courses for a number of years, part of which is a simple architecture kata where groups of people are asked to design a software solution and draw some architecture diagrams to describe it. Over 10,000 people and 30+ countries later, I have gigabytes of anecdotal photo evidence - the majority of the diagrams use an ad hoc “boxes and lines” notation with no clear notation or semantics. Designing software is where the complexity should be, not communicating it.
This book focusses on the visual communication and documentation of software architecture. I’ve seen a number of debates over the years about whether software development is an art, a craft or an engineering discipline. Although I think it should be an engineering discipline, I believe we’re a number of years away from this being a reality. So while this book doesn’t present a formalised, standardised method to communicate software architecture, it does provide a collection of lightweight ideas and techniques that thousands of people across the world find useful. The core of this is my “C4 model” for visualising software architecture.
I’m an independent software development consultant specialising in software architecture; specifically technical leadership, communication and lightweight, pragmatic approaches to software architecture. In addition to being the author of Software Architecture for Developers, I’m the creator of the C4 software architecture model and I built Structurizr, which is a collection of tooling to help you visualise, document and explore your software architecture.
I regularly speak at software development conferences, meetups and organisations around the world; delivering keynotes, presentations and workshops about software architecture. In 2013, I won the IEEE Software sponsored SATURN 2013 “Architecture in Practice” Presentation Award for my presentation about the conflict between agile and architecture. I’ve spoken at events and/or have clients in over thirty countries around the world.
We’ve reached an interesting point in the software development industry. Globally distributed teams are building Internet-scale software systems in all manner of programming languages, with architectures ranging from monolithic systems through to those composed of dozens of microservices. Agile and lean approaches are now no longer seen as niche ways to build software, and even the most traditional of organisations are seeking fast feedback with minimum viable products to prove their ideas. Techniques such as automated testing and continuous delivery coupled with the power of cloud computing make this a reality for organisations of any size too. But there’s something still missing.
Ask somebody in the building industry to visually communicate the architecture of a building and you’ll likely be presented with site plans, floor plans, elevation views, cross-section views, and detail drawings. In contrast, ask a software developer to communicate the software architecture of a software system using diagrams, and you’ll likely get a confused mess of boxes and lines.
Those architecture diagrams you have on your office wall or wiki - do they reflect the system that is actually being built, or are they conceptual abstractions that bear no resemblance to the structure of the code? If diagrams are to be useful, they need to reflect reality.
Through my training and workshops, I’ve asked thousands of software developers around the world to draw software architecture diagrams over the past decade, and continue to do so today. The results still surprise me, anecdotally suggesting that effective visual communication of software architecture is a skill that’s sorely lacking in the software development industry. We’ve forgotten how to visualise software architecture; both during the software design/development process and for long-lived documentation.
If you cast your mind back in time, a number of structured processes provided a reference point for the software design process and how to communicate the resulting designs. Some well-known examples include the Rational Unified Process (RUP), and the Structured Systems Analysis And Design Method (SSADM). Although the software development industry has moved on in many ways, we seem to have forgotten some of the good things that these prior approaches gave us, specifically related to some of the artifacts these approaches encouraged us to create.
Of course, the Unified Modelling Language (UML), a standardised notation for communicating the design of software systems, still lives on. However, while you can argue about whether UML offers an effective way to communicate software architecture or not, that’s often irrelevant because many teams have already thrown out UML or simply don’t know it. Such teams typically favour informal boxes and lines style sketches instead, but often these diagrams don’t make much sense unless they are accompanied by a detailed narrative.
Abandoning UML is all very well but, in the race for agility, many software development teams have lost the ability to communicate visually. The example software architecture sketches (pictured) illustrate the typical software architecture diagrams that I see on my travels and, as we’ll see in the next chapter, they suffer from a number of problems.
This book aims to resolve these problems, by providing some ideas and techniques that software development teams can use to visualise and document their software in a lightweight way. Just to be clear, I’m not talking about detailed modelling, comprehensive UML models, or model-driven development. This is about effectively and efficiently communicating the software architecture of the software that you’re building, with a view to:
- Help everybody understand the “big picture” of what is being built, and how this fits into the “bigger picture” of the organisation/environment in which it exists.
- Create a shared vision for the development team.
- Provide a “map” that can be used by software developers to navigate the source code.
- Provide a point of focus for technical conversations about new features, technical debt, risk reviews, etc.
- Fast-track the on-boarding of new software developers into the team.
- Provide a way to explain what’s being built to people outside of the development team, whether they are technical or non-technical.
Why is this important? In today’s world of agile delivery and lean startups, many software teams have lost the ability to communicate what it is they are building, so it’s no surprise that these same teams often seem to lack technical leadership, direction, and consistency. If you want to ensure that everybody is contributing to the same end-goal, you need to be able to effectively communicate the vision of what it is you’re building. And if you want agility and the ability to move fast, you need to be able to communicate that vision efficiently too. Moving fast requires good communication.
As I mentioned in the introduction, I’ve asked thousands of software developers to draw software architecture diagrams during workshops I’ve run. Sometimes this is done as part of a software architecture kata, where groups of people are tasked with designing a software system. Other times it’s done as part of a diagramming workshop where I ask software developers to draw some pictures to describe the software architecture of a system they are currently working on. Either way, the result is the same - an ad hoc collection of “boxes and lines” diagrams.
The task is literally phrased as “draw one or more software architecture diagrams to describe your software system”. As you can probably imagine, the resulting diagrams are all very different. Some diagrams show a very high-level of abstraction, others present low-level design details. Some diagrams show static structure, others show runtime and behavioural aspects. Some diagrams show technology choices, most don’t.
When you think about it, this result is unsurprising. Asking people what they found challenging about the exercise reveals that perhaps visual communication of software architecture isn’t something that is proactively being taught. I regularly hear the following questions during the workshops:
- “What types of diagram should we draw?”
- “What notation should we use?”
- “What level of detail should we present?”
- “Who is the audience for these diagrams?”
I run this as a group-based exercise, typically with between two and five people per group. Rather than making the exercise easier, having a group of people with different backgrounds and experience tends to complicate matters, as time is wasted debating how best to complete the task. This is because, unlike the building industry, the software development industry lacks a standard, consistent way to think about, describe and visually communicate software architecture. I believe there are a number of factors that contribute to this:
- In their haste to adopt agile approaches in recent years, many software teams have “thrown out the baby with the bath water”. Modeling and documentation have been thrown out alongside traditional plan-driven processes and methodologies. That may sound a little extreme, but many of the software teams I work with only have a very limited amount of documentation for their software systems.
- Teams that still do see the value in documents and diagrams have typically abandoned the Unified Modeling Language (UML) in favour of an approach that is more lightweight and pragmatic. I’ll discuss UML later in the book, but my anecdotal evidence, based upon meeting and speaking to thousands of software developers, suggests that UML is optimistically only used by a small percentage of software developers.
- There are very few people out there who teach software teams how to effectively model, visualise and communicate software architecture. Based upon running a small number of workshops for computer science undergraduates, this includes lecturers at universities too.
Let’s look at some examples. The small selection of photos that follow are taken from my workshops, where groups have been asked to design a small “financial risk system” for a bank, and draw one or more diagrams to communicate the software architecture of it. The purpose of the financial risk system is to import data from two data sources (a “Trade Data System”, and a “Reference Data System”), merge the datasets, perform some risk calculations and produce a Microsoft Excel compatible report for a number of business users. A subset of those business users can additionally modify some of the parameters that are used during the calculations. You can see the full set of requirements for the financial risk system in the Appendix A.
Regardless of whether this is the only software architecture diagram or one of a collection of software architecture diagrams, this diagram doesn’t tell you much about the solution. Essentially it’s just a shopping list of technologies.
There’s a Unix box and a Windows box, with some additional product selections that include JBoss (a Java EE application server) and Microsoft SQL Server. The problem is, I don’t know what those products are doing, plus there seems to be a connection missing between the Unix box and the Windows box. It’s essentially a bulleted list that’s been presented as a diagram.
When people talk about software architecture, they often refer to “boxes and lines”. This next diagram has boxes, but no lines.
This is a three-tier solution (I think) that uses the Microsoft technology stack. There’s an ASP.NET web application at the top, which I assume is being used for some sort of user interaction, although that’s not shown on the diagram. The bottom section is labelled “SQL Server” and there are lots of separate cylinders. To be honest though, I’m left wondering whether these are separate database servers, schemas or tables.
Finally, in the middle, is a collection of boxes, which I assume are things like components, services, modules, etc. From one perspective, it’s great to see how the middle-tier of the overall solution has been decomposed into smaller chunks, and these are certainly the types of components/services/modules that I would expect to see for such a solution. But again, there are no responsibilities and no interactions. Software architecture is about structure, which is about things (boxes) and how they interact (lines). This diagram has one, but not the other. It’s telling a story, but not the whole story.
This is similar to the previous diagram and is very common, particularly in large organisations for some reason.
Essentially the group that produced this diagram has simply documented their functional decomposition of the solution into a number of smaller things. Imagine a building architect drawing you a diagram of your new house that simply had a collection of boxes labelled “Cooking”, “Eating”, “Sleeping”, “Relaxing”, etc or “Kitchen”, “Dining Room”, “Bedroom”, “Lounge”, etc.
This diagram suffers from the same problem as the previous diagram (no responsibilities and no interactions) plus we additionally have a colour coding to decipher. Can you work out what the colour coding means? Is it related to input vs output functions? Or perhaps it’s business vs infrastructure? Existing vs new? Buy vs build? Or maybe different people simply had different colour pens! Who knows. I often get asked why the central “Risk Assessment Processor” box has a noticeably thicker border than the other boxes. I honestly don’t know, but I suspect it’s simply because the marker pen was held at a different angle.
This is one of my all-time favourites. It was also the one and only diagram that this particular group used to present their solution.
The central spine of this diagram is great because it shows how data comes in from the source data systems (TDS and RDS) and then flows through a series of steps to import the data, perform some calculations, generate reports and finally distribute them. It’s a super-simple activity diagram that provides a nice high-level overview of what the system is doing. But then it all goes wrong.
I think the green circle on the right of the diagram is important because everything is pointing to it, but I’m not sure why. And there’s also a clock, which I assume means that something is scheduled to happen at a specific time.
The left of the diagram is equally confusing, with various lines of differing colours and styles zipping across one another. If you look carefully you’ll see the letters “UI” (User Interface) upside-down. The reason? People were writing from wherever they sat around the table.
This is another very common style of diagram. Next time somebody asks you to produce a software architecture diagram of a system, present them this photo and you’re done!
It’s a very “Software Architecture 101” style of diagram where most of the content is generic. Ignoring the source data systems at the top of the diagram (TDS and RDS); we have boxes generically labelled “transport”, “archive”, “audit”, “report generation”, “error handling” and arrows labelled “error” and “action”. And look at the box in the centre - it’s labelled “business logic”, which is not hugely descriptive!
This diagram is also relatively common. It shows the logical (or conceptual, or functional) building blocks that the software system is comprised of, but offers very little information other than that.
There’s a common misconception that “software architecture” diagrams should be “logical” in nature rather than include any references to technology or implementation details, especially before any code is written. We’ll look at this later in the book.
This diagram is also relatively common. It shows the overall shape of the software architecture (including responsibilities, which I really like) but the technology choices are left to your imagination.
And similarly, this next diagram tells us that the solution is an n-tier Java EE system but, like the previous diagram, it omits some important technology details.
The lines between the web server and the application server have no information about how this communication occurs. Is it SOAP? A JSON web API? XML over HTTPS? Remote method invocation? Asynchronous messaging? It’s not clear.
I’m often told that the financial risk system “is a simple solution that can be built with any technology”, so it doesn’t really matter anyway. I disagree this is the case and the issue of including or omitting technology choices is covered in more detail elsewhere in the book.
This next one is a Java solution consisting of a web application and a number of server-side components. Although it provides a simple high-level overview of the solution, it’s missing some information and you need to make some educated guesses to fill in the blanks.
If you look at the Unix box in the centre of the diagram, you’ll see two smaller boxes labelled “Risk Analysis System” and “Data Import Service”. If you look closely, you’ll see that both boxes are annotated “JAR”, which is the deployment mechanism for Java code (Java ARchive). Basically this is a ZIP file containing compiled Java bytecode. The equivalent in the .NET world is a DLL.
And herein lies the ambiguity. What happens if you put a JAR file on a Unix box? Well, the answer is not very much other than it takes up some disk space. And cron (the Unix scheduler) doesn’t execute JAR files unless they are really standalone console applications, the sort that have a “public static void main” method as a program entry point. By deduction then, I think both of those JAR files are actually standalone applications and that’s what I’d like to see on the diagram. Rather than the deployment mechanism, I want to understand the execution context.
If you’ve heard of “Plain Old C# Objects” (POCOs) or “Plain Old Java Objects” (POJOs), this is the homeless edition. This diagram mixes up a number of different levels of detail.
In the bottom left of the diagram is a SQL Server database, and at the top left of the diagram is a box labelled “Application”. Notice how that same box is annotated (in green) “Console-C#”. Basically, this system seems to be made up of a C# console application and a database. But what about the other boxes?
Well, most of them seem to be C# components, services, modules or objects and they’re much like what we’ve seen on some of the other diagrams. There’s also a “data access” box and a “logger” box, which could be frameworks or architectural layers. Do all of these boxes represent the same level of granularity as the console application and the database? Or are they actually part of the application? I suspect the latter, but the lack of boundaries makes this diagram confusing. I’d like to draw a big box around most of the boxes to say “all of these things live inside the console application”. I want to give those boxes a home. Again, I do want to understand how the system has been decomposed into smaller components, but I also want to know about the execution context too.
This is the middle part of a more complex diagram.
It’s a little like those “choose your own adventure” books that I used to read as a kid. You would start reading at page 1 and eventually arrive at a fork in the story where you decide what should happen next. If you want to attack the big scary creature you’ve just encountered, you turn to page 47. If you want to run away like a coward, it’s page 205 for you. You keep making similar choices and eventually, and annoyingly, your character ends up dying and you have to start over again.
This diagram is the same. You start at the top and weave your way downwards through what is a complex asynchronous and event-driven style of architecture. You often get to make a choice - should you follow the “fail event” or the “complete event”? As with the books, all paths eventually lead to the (SNMP) trap on the left of the diagram.
The diagram is complex, it’s trying to show everything and the single colour being used doesn’t help. Removing some information and/or using colour coding to highlight the different paths through the architecture would help tremendously.
To pick up on something you may have noticed from previous diagrams, I regularly see diagrams that include unlabelled users/actors. Essentially they are faceless clones. I don’t know who they are and why they are using the software.
The final diagram is a great example of why whiteboards are such useful bits of equipment!
All joking aside, these diagrams do suffer from one or more of the following problems:
- Notation (e.g. colour coding, shapes, etc) is not explained or is inconsistent.
- The purpose and meaning of elements is ambiguous.
- Relationships between elements are missing or ambiguous.
- Generic terms such as “business logic” are used.
- Technology choices (or options, if doing up front design) are omitted.
- Levels of abstraction are mixed.
- Too much or too little detail.
- No context or a logical starting point.
In addition, the problems associated with a single diagram are often exacerbated when a collection of diagrams is created:
- The notation (colour coding, line styles, etc) is not consistent between diagrams.
- The naming of elements is not consistent between diagrams.
- The logical order in which to read the diagrams isn’t clear.
- There is no clear transition between one diagram and the next.
The example diagrams typify what I see during my workshops and these types problems are incredibly common. A quick Google image search will uncover a plethora of similar block diagrams that suffer from many of the same problems we’ve seen already. I’m sure you will have seen diagrams like this within your own organisations too.
One of the easiest ways to understand whether a diagram makes sense is to give it to somebody else and ask them to interpret it without providing a narrative. I’m a firm believer that diagrams should be able to stand alone, to some degree anyway. Any narrative should complement the diagram rather than explain it. However, I often hear groups in my workshops say the following:
- “We’ll talk through the diagrams.”
- “This doesn’t make sense, but we’ll explain it during the presentation.”
The assumption that a diagram will be accompanied by a narrative creates a gap between the information captured on the paper and what remains in people’s heads. Diagrams that need explaining have limited value, especially when used for the purpose of creating long-lived documentation.
The diagrams we’ve seen so far have been an ad hoc collection of “boxes and lines”. Although notation is important, one of the fundamental problems I believe we have in the software development industry is that we lack a common, shared vocabulary with which to think about and describe the software systems we build.
Next time you’re sitting in a conversation about software design, listen out for how people use terms like “component”, “module”, “sub-system”, etc. These terms are typically ambiguous. For example, the dictionary definition for the word “component” is “a part of a larger whole”. Imagine that you’re building a web application, which itself uses a database. Given the dictionary definition, both of the following uses of the word “component” are valid.
- “The web application is a component of the entire software system.”
- “The web application is made up of a number of components.”
In essence, the word “component” is being used to describe two very different levels of abstraction.
My goal is to see teams able to discuss the structure of their software systems with a common set of abstractions rather than struggling to understand what the various notational elements are trying to show. Although I would like to see the software development industry create a standard notation that we all understand (like the electrical engineering industry has with circuit diagrams, for example), I don’t think we are there yet. Perhaps a common set of abstractions is more important than a common notation given our industry’s current lack of maturity and engineering discipline.
Most maps are a great example of this principle in action. If you get two different maps of your local area and lay them out side by side, they will both show the major roads, rivers, lakes, forests, towns, districts, schools, churches and so on. Visually though, these maps will probably use different notation in terms of colour-coding, line styles, iconography, etc. In other words, the maps are showing the same things (the same abstractions), but the notation varies. The key to understanding them is exactly that; a key or legend tucked away in a corner somewhere. We can do the same with our software architecture diagrams.
Diagrams are the maps that help software developers navigate a complex codebase.
In order to get to this point though, we need to agree upon some vocabulary. And this is the step that is usually missed during the initial iteration of my software architecture diagramming workshops. Teams charge headlong into the exercise without having a shared understanding of the terms they are using.
“This is a component of our system”, says one developer, pointing to a box on a diagram labelled “Web Application”.
I’ve witnessed groups of people having design discussions using terms like “component” where they are clearly not talking about the same thing. Yet everybody in the group is oblivious to this. Each group needs to agree upon the vocabulary, terminology and abstractions they are going to use. The notation can then evolve.
So, notation aside (we’ll cover that later in the book), my approach to tackling this problem is to introduce a shared vocabulary that we can use to describe our software. The primary aspect I’m interested in is the static structure. And I’m interested in the static structure from different levels of abstraction. Once this static structure is understood and in use, it’s easy to supplement it with other information to illustrate runtime/behavioural characteristics, infrastructure, deployment models, etc.
I like to think of my software system as being a hierarchy of building blocks as follows:
A software system is made up of one or more containers (web applications, mobile apps, desktop applications, databases, file systems, etc), each of which contains one or more components, which in turn are implemented by one or more code elements (e.g. classes, interfaces, objects, functions, etc). And people use the software systems that we build.
A software system is the highest level of abstraction, representing something that delivers value to its users, whether they are human or not.
Put simply, a container represents something that hosts code or data, like an application or a data store. A container is something that needs to be running in order for the overall software system to work. In real terms, a container is something like:
- Server-side web application: A Java EE web application running on Apache Tomcat, an ASP.NET MVC application running on Microsoft IIS, a Ruby on Rails application running on WEBrick, a Node.js application, etc.
- Client-side desktop application: A Windows desktop application written using WPF, a macOS desktop application written using Objective-C, a cross-platform desktop application written using JavaFX, etc.
- Mobile app: An Apple iOS app, an Android app, a Microsoft Windows Phone app, etc.
- Server-side console application: A standalone (e.g. “public static void main”) application, a batch process, etc.
- Microservice: A single microservice, hosted in anything from a traditional web server to something like Spring Boot, Dropwizard, etc.
- Serverless function: A single serverless function (e.g. Amazon Lambda, Azure Function, etc).
- Database: A schema or database in a relational database management system, document store, graph database, etc such as MySQL, Microsoft SQL Server, Oracle Database, MongoDB, Riak, Cassandra, Neo4j, etc.
- Blob or content store: A blob store (e.g. Amazon S3, Microsoft Azure Blob Storage, etc) or content delivery network (e.g. Akamai, Amazon CloudFront, etc).
- File system: A full local file system or a portion of a larger networked file system (e.g. SAN, NAS, etc).
- Shell script: A single shell script written in Bash, etc.
A container is essentially a context or boundary inside which some code is executed or some data is stored. The name “container” was chosen because I wanted a name that didn’t imply anything about the physical nature of how that container is executed1. For example, some web servers run multiple threads inside a single process, whereas others run single threads across multiple processes. When I’m thinking about the static structure of a software system, I don’t want to concern myself with the details of whether a web application is using one operating system process or many when it’s servicing requests. It’s an important detail, but we can get into that later.
It’s also worth noting that each container should be a separately deployable thing. The physical deployment is another important detail that we will look at later, but, in theory anyway, every container can be deployed onto or run on a separate piece of infrastructure; whether that infrastructure is physical, virtual or containerised. The implication here is that communication between containers is likely to require an out-of-process or remote procedure call across the process and/or network boundary.
To give an example, let’s imagine you’re building a website that is comprised of two different web applications (e.g. a desktop version and a mobile version, or an end-user version serving HTML and an API endpoint serving JSON). There are a number of scenarios to consider:
- Each web application is packaged up into separately deployable units (e.g. two Java WAR files, two ASP.NET web applications, etc). This is two containers, regardless of whether both deployable units are actually deployed into the same physical web server (a deployment optimisation).
- Although you think about the two web applications as being logically separate, they are actually inseparable because they are packaged as a single deployment unit (e.g. a single Java WAR file or ASP.NET web application). This is a single container.
The same is true with relational database schemas. I would treat two separate schemas as two separate containers, irrespective of whether they are deployed into the same database server or not.
As a final note, put simply, a container refers to an execution context and it’s a really runtime construct. This means that libraries or modules (e.g. JAR files, DLL files, .NET assemblies, etc) should not be considered as containers unless they are runnable on their own, like a Java or Spring Boot application that is packaged into an executable JAR file, for example.
The word “component” is a hugely overloaded term in the software development industry, but I like to think of a component as being a grouping of related functionality encapsulated behind a well-defined interface. With the C4 model, components are not separately deployable units. Instead, it’s the container that’s the deployable unit. In other words, all of the components inside a container typically execute in the same process space.
Aspects such as how those components are packaged (e.g. one component vs many components per JAR file, DLL, shared library, etc) is an orthogonal concern and, from my perspective, doesn’t affect how we think about components.
Finally, components are made up of one or more code elements constructed with the basic building blocks of the programming language that you’re using; classes, interfaces, enums, functions, objects, etc.
A component is a way to step up one level of abstraction from the code-level building blocks that you have in the technology you’re using. For example:
- Object-oriented programming languages (e.g. Java, C#, C++, etc): A component is made up of classes and interfaces.
- Procedural programming languages (e.g. C): A component could be made up of a number of C files in a particular directory.
- Functional programming languages: A component could be a module (a concept supported by languages such as F#, Haskell, etc), which is a logical grouping of related functions, types, etc.
- Relational database: A component could be a logical grouping of functionality; based upon a number of tables, views, stored procedures, functions, triggers, etc.
If you’re using an object-oriented programming language, your components will be implemented using one or more classes. Let’s look at a quick example to better define what a component is in the context of some code.
The Spring PetClinic application is a sample codebase that illustrates how to build a Java web application using the Spring MVC framework. From a non-technical perspective, it’s a software system designed for an imaginary pet clinic that stores information about pets and their owners, visits made to the clinic, and the vets who work there. The system is only designed to be used by employees of the clinic. From a technical perspective, the Spring PetClinic system consists of a web application and a relational database.
The version of the web application we’ll look at here is a typical layered architecture consisting of a number of web MVC controllers, a service containing “business logic” and some repositories for data access. There are also some domain and util classes too. If you download a copy of the GitHub repository, open it in your IDE of choice and visualise it by reverse-engineering a UML class diagram from the code, you’ll get something like this.
As you would expect, this diagram is showing you all of the Java classes and interfaces that make up the Spring PetClinic web application, plus all of the relationships between them. The properties and methods are hidden on the diagram because they add too much noise to the picture. This isn’t a complex codebase by any stretch of the imagination but, by showing classes and interfaces, the diagram is showing too much detail.
Let’s remove those classes that aren’t useful to having an “architecture” discussion about the system. In other words, let’s only show those classes/interfaces that have some significance from a static structure perspective. In concrete terms, for this specific codebase, it means excluding the model (domain) and util classes.
After a little rearranging, we now have a simpler diagram with which to reason about the software architecture. We can also see the architectural layers again (controllers, services and repositories). But this diagram is still showing code-level elements (i.e. classes and interfaces). In order to zoom up one level, we need to identify which of these code-level elements can be grouped together to form “components”. The strategy for grouping code-level elements into components will vary from codebase to codebase (and we’ll discuss this later in the book) but, for this codebase, the strategy might look like this.
Each of the blue boxes represents what I would consider to be a “component” in this codebase. In summary, each of the web controllers is a separate component, along with the result of combining the remaining interfaces and their implementation classes. If we remove the code level noise, we get a picture like this.
In essence, we’re grouping the classes and interfaces into components, which is a unit of related functionality. You will likely have shared code (e.g. abstract base classes, supporting classes, helper classes, utility classes, etc) that are used across many components, such as the
JdbcPetVisitExtractor in this example. Some can be refactored and moved “inside” a particular component, but some of them are inevitable.
Although this example illustrates a traditional layered architecture, the same principles are applicable regardless of how you package your code (e.g. by layer, feature or component) or the architectural style in use (e.g. layered, hexagonal, ports and adapters, etc). My aim in all of this is to minimise, and in fact remove, the gap between how software developers think about components from a logical and physical perspective. Components should be real things, evident in the code, rather than logical constructs that are used in architecture discussions only.
If you’re familiar with the definition of software architecture from books such as Software Architecture in Practice, you will have noticed that I don’t use the term “module” as a part of the static structure definition. A module typically refers to an implementation unit (e.g. a library or some other collection of programming elements) that may be combined with other modules into a component, which itself is instantiated to create component instances at runtime. While this model makes sense, I find it adds an additional level of detail that is usually unnecessary when thinking about a software system from a “big picture” perspective. For this reason, I’ve deliberately avoided using the term “module” and instead focus on the identification of coarser-grained components within the static structure.
I’ve also avoided using the term “subsystem”, which some people use to refer to a collection of related components or a functional slice of a software system. The problem I have with the term “subsystem” is that it’s often difficult to map this concept onto a real-world codebase. If the concept of components and modules, or systems and subsystems, is useful, then feel free to build that into the shared vocabulary that you create.
Given the degree of hype and discussion around microservices at the moment, it’s worth being explicit about how to describe microservices using the vocabulary we’ve defined so far. Broadly speaking, there are two options.
If your software system has a dependency upon a number of microservices that are outside of your control (e.g. they are owned and/or operated by a separate team), I would treat these microservices as external software systems that you can’t see inside of.
On the other hand, if the microservices are a part of a software system that you are building (i.e. you own them), I would treat them as containers, along with any data stores that those microservices use (these are separate containers). In the same way that a modular monolithic application is a container with a number of components running inside it, a microservice is a container with a (smaller) number of components running inside it. The actual number of components will depend upon the implementation strategy. It could range from the very simple (i.e. one, where a microservice is a container with a single component running inside) through to something like a mini-layered or hexagonal architecture.
I tend to treat the serverless concepts (e.g. Amazon Lambdas, Azure Functions) in the same way as microservices. If you’re building a software system comprised of a number of serverless functions, think of them as containers because they are all separately deployable.
You might also be wondering where platforms, frameworks and libraries fit into all of this. After all, platforms and frameworks are usually something that you build your software on top of, while libraries are things that your software uses. In most cases, these are really just technology choices that components make use of, and are therefore implementation details rather than components in their own right. For example, the components in the Spring PetClinic web application use the Spring framework, but I wouldn’t show the Spring framework as a component. The same is true of the logging library and database driver, etc.
In most cases, good design2 will encourage you to wrap up components from platforms, frameworks and libraries into your own components. Having said that, there are times when your software might use components provided by platforms, frameworks and libraries of course. Understanding how you use such components is the key to understanding how they fit into a static model of your software.
I’ve illustrated the vocabulary that I use here, and it works for the majority of organisations I work with. But, of course, there are no universal rules. Sometimes, rather than introducing something new, it’s easier for an organisation to stick with the vocabulary they are already using, ensuring that it is explicitly defined and understood by everybody.
As an example, one organisation I worked with builds software in C to run on mobile devices. Instead of “container” and “component”, they use the terms “component” and “module” respectively. In this case, a “component” refers to an executable built in C, which in turn is made up of a number of modules. Although the terminology is different, we still have a hierarchical structure that can be used to describe a software system at a number of different levels of abstraction.
With a shared vocabulary in mind, we can now move on to draw some diagrams at varying levels of abstraction to visualise the static structure of a software system. I call this the “C4 model”; (System) Context, Containers, Components and Code.
- System Context: A System Context diagram provides a starting point, showing how the software system in scope fits into the world around it.
- Containers: A Container diagram zooms into the software system in scope, showing the high-level technical building blocks (containers) and how they interact.
- Components: A Component diagram zooms into an individual container, showing the components inside it.
- Code: A code (e.g. UML class) diagram can be used to zoom into an individual component, showing how that component is implemented.
You can think of the C4 diagrams as being a set of maps for a software system, which provides you with the ability to zoom in and out at varying levels of detail. Shneiderman’s mantra is a simple concept for understanding and visualising large quantities of data, but it fits really nicely with the C4 model because it’s hierarchical.
Overview first, zoom and filter, then details-on-demand
My starting point for understanding any software system is to draw a system context diagram. This helps me to understand the scope of the system, who is using it and what the key system dependencies are. It’s usually quick to draw and quick to understand.
Next I’ll open up the system and draw a diagram showing the containers (web applications, mobile apps, standalone applications, databases, file systems, message buses, etc) that make up the system. This shows the overall shape of the software system, how responsibilities have been distributed and the key technology choices that have been made.
As developers, we often need more detail, so I’ll then zoom into each (interesting) container in turn and show the “components” inside it. This is where I show how each application has been decomposed into components, along with a brief note about key responsibilities and technology choices of those components. Hand-drawing the diagrams can become tedious, which is why you should ideally look at tooling to help automate it instead.
I might optionally progress deeper into the hierarchy to show the code-level elements (e.g. classes, interfaces, objects, functions, etc) that make up a particular component. Ultimately though, this detail resides in the code and, as software developers, we can get that on demand via our IDEs.
Next time you’re asked to create some software architecture diagrams (whether that’s to understand an existing system, present a system overview, or do some software archaeology), my advice is to keep Shneiderman’s mantra in mind. Start at the top and work into the detail, creating a story that gets deeper into the detail as it progresses. The different levels of diagrams allow you to tell different stories to different audiences; some of who will be technical, some not.
As a quick note, the C4 model is not a description of a design process, it’s just a collection of diagrams that you can use to describe the static structure of a software system. That said, and we’ll cover this later, while the C4 model describes diagrams covering four levels of abstraction, you don’t necessarily need to create every diagram at every level. My recommendation is that all teams create System Context and Container diagrams, and really think about whether Component and Code diagrams provide enough benefit considering the cost of creating and keeping them up to date.
1I do appreciate that the term “container” is now in widespread use because of containerisation and technologies like Docker. Feel free to use something like “runtime context”, “execution environment” or “deployable unit” instead if you’d prefer to avoid the term “container” when discussing software architecture.↩
2Many software architects will often try to decouple their code from that provided by platforms, frameworks and libraries so that decisions can deferred and technologies changed if necessary.↩