Skip to content

c4 blog post #269

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 263 additions & 0 deletions blog/c4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import CodeBlock from '@theme/CodeBlock';
import C4 from '@site/static/d2/c4.d2';
import C4View1 from '@site/static/d2/c4-view1.d2';
import C4View2 from '@site/static/d2/c4-view2.d2';
import C4Models from '@site/static/d2/c4-models.d2';
import C4Tags2 from '@site/static/d2/c4-tags2.d2';
import C4Tags3 from '@site/static/d2/c4-tags3.d2';
import C4Legend from '@site/static/d2/c4-legend.d2';
import C4Code1 from '@site/static/d2/c4-code1.d2';
import C4Code2 from '@site/static/d2/c4-code2.d2';
import C4All from '@site/static/bespoke-d2/c4-code.d2';

# C4 Model

The [C4 Model](https://c4model.com/) is a diagramming framework. Over the years, we've had
many practitioners request C4 features in D2. There's even a community-maintained
[exporter](https://github.com/goto1134/structurizr-d2-exporter) from the C4 creator's
tool, [Structurizr](https://docs.structurizr.com/cli/export#options), to D2.

The C4 Model is a loose framework. Unlike UML, which says these symbols always and must
mean certain things, the C4 model is a set of *diagramming concepts*. It's language and
tool agnostic, and these powerful concepts have proven to provide software projects with
clean, mature architecture diagrams.

With the latest 0.7 release of D2, we filled the gaps in the language to have first-class
support of these concepts:

1. A new `suspend` keyword which, along with existing D2 features, lets you define one
model and reuse them across different views.
2. Markdown labels on shapes allow you to give richer textual descriptions with font
hierarchy.
3. A new `c4-person` shape that is more conducive to holding longform labels.
4. A C4 theme for diagrams to be conveniently on-brand recognizable.
5. A new `d2-legend` variable to easily make beautiful legends.

Whether you choose to use these new features specifically for C4 model diagrams is up to
you. Much of this feature set was requested for other purposes. What's important are the
good practices it enables, which can be applied broadly across all sorts of diagrams.

In this article, I'll demonstrate how you can make C4 diagrams in D2. If you'd like more
detail on using a specific feature, you'll find dedicated sections in the
[docs](https://d2lang.com).

## One model, multiple views

Let's take a look at how the new `suspend` keyword can be used to slice and dice a model
into a variety of views.

First we define a medium-sized diagram using the new C4 theme, `c4-person` shape, and
markdown labels.

<CodeBlock className="language-d2" expandeable={true}>
{C4}
</CodeBlock>

<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4.svg2')}}></div>

### Show only relationships to API Application

Let's say we want to create a view for the API team that only includes what's relevant for
them. We'll use the same code as the above, but add 2 sections:

#### 1. "Suspend" all models

Use globs to target everything declared so far and suspend them. These will be removed
unless "unsuspended" later on. Suspended objects and connections are effectively the
models.

```d2
# Suspend all shapes
**: suspend
# Suspend all connections
(** -> **)[*]: suspend
```

#### 2. Unsuspend models we use for our view

Now we use globs to declare which models we want to show up. We do this by "unsuspending"
them.

```d2
# Show every connection with a source to API Application
(** -> **)[*]: unsuspend {
&src: internet_banking_system.api_app
}
# Show every connection with a destination to API Application
(** -> **)[*]: unsuspend {
&dst: internet_banking_system.api_app
}
```

#### Result

<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-view1.svg2')}}></div>

Runnable code:
<CodeBlock className="language-d2" expandeable={true}>
{C4View1}
</CodeBlock>

### Show high-level overview

Let's make a high level overview with the same concepts. The only change here is how we
unsuspend things.

```d2
# Only show root level shapes
**: unsuspend {
&level: 0
}
# Only show connections between root level things
(** -> **)[*]: unsuspend {
&src.level: 0
&dst.level: 0
}
```

And since there are connections between inner shapes of Internet Banking System to other
components, we'll make some connections that capture the general dependency.

```d2
customer -> internet_banking_system: Send requests
internet_banking_system -> mainframe: API calls
internet_banking_system -> email_system: Send emails
```

<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-view2.svg2')}}></div>

Runnable code:
<CodeBlock className="language-d2" expandeable={true}>
{C4View2}
</CodeBlock>

## Tags and filters

Let's say your repository of models is large; maybe everyone in your cross-functional team
is pushing their own models to this file.

To give an example of a small repository:

<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-models.svg2')}}></div>

<CodeBlock className="language-d2" expandeable={true}>
{C4Models}
</CodeBlock>

To reuse these models for a specific domain, we can filter by their classes (tag in C4
vernacular). Assuming the models are in their own file, we can create something like a
CustomerInterface.d2 file, which imports these models, suspends all, and unsuspends the
ones that have the `customer-facing` class.

<CodeBlock className="language-d2-incomplete">
{C4Tags2}
</CodeBlock>

<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-tags2.svg2')}}></div>

Now we can add some connections to create a diagram out of these pre-existing models.

<CodeBlock className="language-d2-incomplete" expandeable={true}>
{C4Tags3}
</CodeBlock>

<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-tags3.svg2')}}></div>

## Legend

To create a legend for this diagram, we declare it as a [variable](/tour/vars) under
`d2-legend`.

Think of `d2-legend` as a mini diagram of its own, but with a special layout where every
shape and connection is deconstructed into a table. If something has opacity 0, it is
excluded in the table (this way we can have legends that only show connections, for
example).

```d2
vars: {
d2-legend: {
banking: Banking {
style.fill: "#FFE4E1"
}
banking -> x: Alerting {
style.stroke-dash: 5
style.stroke: "#2E8B57"
}
banking -> y: Authentication {
style.stroke-width: 2
style.stroke: "#800080"
}
# Remove unneeded objects
x.style.opacity: 0
y.style.opacity: 0
}
}
```

Notice the legend in the bottom right corner.

<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-legend.svg2')}}></div>

## Google Maps zooming

Lastly, a core concept of C4 diagrams is this notion of zooming in and out of different
levels of abstraction. The 4 in C4 stands for the 4 levels of abstractions that it
recommends. Let's take a look at how that can be achieved. For this example, we'll define
two diagrams of code that represent a zoomed in view of the components.

**Example code diagram for ATM Banking System**

<CodeBlock className="language-d2-incomplete" expandeable={true}>
{C4Code1}
</CodeBlock>

<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-code1.svg2')}}></div>

**Example code diagram for Notification System**

<CodeBlock className="language-d2-incomplete" expandeable={true}>
{C4Code2}
</CodeBlock>

<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-code2.svg2')}}></div>

### Linking together

Now let's link these up using the [layers](/tour/composition) feature.

All that's needed is to declare the 2 code diagrams as layers and add the `link` property
to whichever shape we want to be able to zoom into them.

```d2
...@c4-models.d2
**: suspend
**: unsuspend {
&class: customer-facing
}

notification_system.link: layers.notification
mobile_banking_api.link: layers.banking

# Connections for component diagram
# ...

layers: {
banking: @banking-code
notification: @notification-code
}
```

We can export this to a PDF format. If you download it and click on notification or
banking, it'll take you to those respective zoomed-in code views.

<embed src={require('@site/static/img/generated/c4.pdf').default} width="100%" height="800"
type="application/pdf" />

To recap, you'd split it out into those 4 files and use imports for modularity:
1. `models.d2`: where models are defined
2. `customer-components.d2`: where you import models
3. `banking-code.d2`: code diagram for banking system
3. `notification-code.d2`: code diagram for notification system

You can easily flesh this out further by having files for the styling, for higher levels
of abstraction (e.g. for context diagrams).
2 changes: 2 additions & 0 deletions ci/render.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ _bespoke() {
sh_c d2 --pad 0 ./static/bespoke-d2/cat.d2 ./static/img/generated/cat.pdf
sh_c d2 --pad 50 ./static/bespoke-d2/lotr.d2 ./static/img/generated/lotr.pdf

sh_c d2 --pad 50 ./static/bespoke-d2/c4-code.d2 ./static/img/generated/c4.pdf

sh_c d2 --animate-interval=1600 -c --pad 50 -l elk ./static/bespoke-d2/tax.d2 ./static/img/generated/tax.svg2
sh_c d2 --animate-interval=1600 -c --pad 0 -l elk ./static/bespoke-d2/pizza.d2 ./static/img/generated/pizza.svg2
sh_c d2 --animate-interval=1600 -c --pad 0 -l elk ./static/bespoke-d2/johnwick.d2 ./static/img/generated/johnwick.svg2
Expand Down
Loading