Using the C4 model to document Software Architectures
Tags: documentation, uml, c4model
Published: 2021-02-02 23:21:20

Long time ago I used to be a proponent of [UML](https://www.uml.org/what-is-uml.htm), I even started an open source project called [MonoUML](https://sourceforge.net/projects/monouml/) (already defunct). MonoUML was meant to, **automatically**, help developers to document their code using UML-based diagrams  with little to no human intervention.

Sadly, I quickly realized that using UML was not the way to go, sure there are some great ideas and some useful diagrams but in the end there are way too many details to keep in mind to properly use this modeling language efficiently.

After moving on from UML I started using _box and line diagrams_, nothing fancy really and not _standardized_ for sure, and **that worked**, however a few years ago I came across this model created by [Simon Brown](https://simonbrown.je/) called [C4 Model](https://c4model.com/), C4 as in **Context, Containers, Components, and Code**.

**C4** is sort of a slim _UML_ where only the relevant data is presented to the user that happens to be interested in learning about the Software Architecture being described. What this means in practice is that the consumer of the diagram is who drives how much information is displayed, the official website uses this great analogy of Google Maps, where depending on what your goal is you will be presented with different _detail maps_.

The C4 Model defines four levels of diagrams:

* **Context (L1)**: the highest level, it shows how the system relates to users and other systems. It is used as a quick introduction to the system, usually meant to be presented to _stakeholders_, this is the _big picture_ diagram.
* **Container (L2)**: breaks down the system into interrelated _containers_, containers are executable and deployable sub-systems. It is meant to be shared with people that need more technical details about how the system is composed.
* **Component (L3)**: takes the containers and decomposes them into interrelated _components_, relates them to other containers or other systems. It is meant to be read by engineers not familiar with the system that are planning to work on it.
* **Code (L4)**: provides more details about how the actual code is being implemented. It is meant to clearly display some concrete details about the **most complex** pieces of the system.

After using the C4 model for a couple of years already I've found those four levels more than enough to describe complex Software Architectures, I've found practical to implement the first three levels **only**, and leave the fourth level as the _actual code_ that the engineers need to review and understand.

## Caveats

With everything documentation-related there are  always _caveats_, documenting Software Architectures is no different. In this case these caveats are presented in the form of questions: **When?**, **How?** and **Who?**

* **When** should we update the documentation?
* **How** should we update the documentation?
* **Who** should update the documentation?

Those three questions are hard to answer in isolation without enough details about the team trying to implement this model, this is because they depend on the team itself and the existing tools and processes they follow.

**Ideally** this documentation is updated by _the team_ working on the system, this makes sense because they are really close to the changes being implemented. Usually teams using [Infrastructure as a Code](https://en.wikipedia.org/wiki/Infrastructure_as_code) as part of the system being built, maybe using [CloudFormation](https://aws.amazon.com/cloudformation/) for AWS or maybe something for generic like [Terraform](https://www.terraform.io/), have less of a hard time keeping those documents updated because updating infrastructure details reminds the author to also revisit the documentation if needed.

In practice I haven't found a way to enforce changes like those when committing a change into the repository, there's no _linter_ for those yet.

The recommended guideline for all team members is to keep in mind that diagrams should be updated when making new changes that affect anything related to any of those different levels.

## Example

The following is a concrete example using the hypothetical _Book Store System_ I introduced last time during my video [Go Tools: counterfeiter](https://www.youtube.com/watch?v=ENqwq64TsDk) ([blog link](https://mariocarrion.com/2019/06/24/golang-tools-counterfeiter.html)). This time the system will be extended a bit more, and as usual the complete code of this example is available on [my Gitlab repository](https://gitlab.com/MarioCarrion/blog-examples/-/tree/main/2020/12/30).

Let's start with the **Context Diagram**.

### Context Diagram

[![Level 1 Context](https://live.staticflickr.com/65535/50788542106_f2ae921ca8_w.jpg)](https://flic.kr/p/2ko1y85)

This diagram is more than enough when trying to convey three details we need to share with our stakeholders about our `Books System`:

* It has two different types of users: _Public_ and _Authorized_,
* Depends on an external _Authorization System_ for authorization purposes, and
* Depends on two external systems _Publisher 1 System_ and _Publisher 2 System_ for catalog purposes

Next, the **Container Diagram**.

### Container Diagram

[![Level 2 Container](https://live.staticflickr.com/65535/50787782973_30156f1d5d_w.jpg)](https://flic.kr/p/2knWEsz)

This diagram zooms in to mention more technical details about the actual system, it explicitly indicates the technologies we use, like Go, PostgreSQL, ElasticSearch and Kafka; and depending on the technology being used it calls out the concrete containers in charge of handling that work, specifically we see (left to right, top to bottom):

* _Search Web API_: HTTP handlers, use ElasticSearch as the _Search Database_ for searching read-only records,
* _Admin Web API_: HTTP handlers, use PostgreSQL as the _Read/Write Relational Database_ for administering records,
* _ElasticSearch Events Consumer_: binary listening to _Kafka_ domain events for updating the _Search Database_ on demand,
* _Publisher 1 Recurrent Updater_: binary that runs every X amount of time in charge of importing data from Publisher 1, it uses the _Admin Web API_ for updating that data,
* _Publisher 2 Events Consumer_: binary listening to external events coming from _Publisher 2 System_ using _Kafka_, it uses the _Admin Web API_ for updating that data, and
* _Public Web API_: HTTP handlers, use the PostgreSQL _Read-Only Retailer Database_ for getting read-only records.

Finally, the **Component Diagram**

### Component Diagram - Admin Web API

[![Level 3 Component](https://live.staticflickr.com/65535/50788542081_cf9ebffa86.jpg)](https://flic.kr/p/2ko1y7D)

The last diagram I cover this time is the concrete component diagram for the _Admin Web API_, keep in mind if you look at the list above **there are six containers**, we may want to implement all of them or some of them depending on how complex they are; in this case I'm only implementing this one but the idea should be the same when implementing the other five.

This diagram zooms in even more to describe concrete containers and how the interaction happens with each other, the goal is to give engineers more details about how things are implemented, for example indicate what concrete package or types are in charge of the work being done.

### Final thoughts

I find the C4 Model better than UML for documenting software architectures, however the problems we have had for years are still there, specifically the questions I mentioned in the beginning, at the moment there is no way to enforce updating diagrams when making changes to the actual code or the infrastructure.

Engineers should keep an eye out when making those changes and react accordingly, this is easier said than done and it may take a while to get the team used to, but this should be the initial step when trying to keep the documentation up to date.