Skip to content

Commit 117a619

Browse files
authored
Merge pull request #269 from terrastruct/c4-blog-post
c4 blog post
2 parents 403d6e1 + 2b3845c commit 117a619

23 files changed

+9560
-0
lines changed

blog/c4.md

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import CodeBlock from '@theme/CodeBlock';
2+
import C4 from '@site/static/d2/c4.d2';
3+
import C4View1 from '@site/static/d2/c4-view1.d2';
4+
import C4View2 from '@site/static/d2/c4-view2.d2';
5+
import C4Models from '@site/static/d2/c4-models.d2';
6+
import C4Tags2 from '@site/static/d2/c4-tags2.d2';
7+
import C4Tags3 from '@site/static/d2/c4-tags3.d2';
8+
import C4Legend from '@site/static/d2/c4-legend.d2';
9+
import C4Code1 from '@site/static/d2/c4-code1.d2';
10+
import C4Code2 from '@site/static/d2/c4-code2.d2';
11+
import C4All from '@site/static/bespoke-d2/c4-code.d2';
12+
13+
# C4 Model
14+
15+
The [C4 Model](https://c4model.com/) is a diagramming framework. Over the years, we've had
16+
many practitioners request C4 features in D2. There's even a community-maintained
17+
[exporter](https://github.com/goto1134/structurizr-d2-exporter) from the C4 creator's
18+
tool, [Structurizr](https://docs.structurizr.com/cli/export#options), to D2.
19+
20+
The C4 Model is a loose framework. Unlike UML, which says these symbols always and must
21+
mean certain things, the C4 model is a set of *diagramming concepts*. It's language and
22+
tool agnostic, and these powerful concepts have proven to provide software projects with
23+
clean, mature architecture diagrams.
24+
25+
With the latest 0.7 release of D2, we filled the gaps in the language to have first-class
26+
support of these concepts:
27+
28+
1. A new `suspend` keyword which, along with existing D2 features, lets you define one
29+
model and reuse them across different views.
30+
2. Markdown labels on shapes allow you to give richer textual descriptions with font
31+
hierarchy.
32+
3. A new `c4-person` shape that is more conducive to holding longform labels.
33+
4. A C4 theme for diagrams to be conveniently on-brand recognizable.
34+
5. A new `d2-legend` variable to easily make beautiful legends.
35+
36+
Whether you choose to use these new features specifically for C4 model diagrams is up to
37+
you. Much of this feature set was requested for other purposes. What's important are the
38+
good practices it enables, which can be applied broadly across all sorts of diagrams.
39+
40+
In this article, I'll demonstrate how you can make C4 diagrams in D2. If you'd like more
41+
detail on using a specific feature, you'll find dedicated sections in the
42+
[docs](https://d2lang.com).
43+
44+
## One model, multiple views
45+
46+
Let's take a look at how the new `suspend` keyword can be used to slice and dice a model
47+
into a variety of views.
48+
49+
First we define a medium-sized diagram using the new C4 theme, `c4-person` shape, and
50+
markdown labels.
51+
52+
<CodeBlock className="language-d2" expandeable={true}>
53+
{C4}
54+
</CodeBlock>
55+
56+
<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4.svg2')}}></div>
57+
58+
### Show only relationships to API Application
59+
60+
Let's say we want to create a view for the API team that only includes what's relevant for
61+
them. We'll use the same code as the above, but add 2 sections:
62+
63+
#### 1. "Suspend" all models
64+
65+
Use globs to target everything declared so far and suspend them. These will be removed
66+
unless "unsuspended" later on. Suspended objects and connections are effectively the
67+
models.
68+
69+
```d2
70+
# Suspend all shapes
71+
**: suspend
72+
# Suspend all connections
73+
(** -> **)[*]: suspend
74+
```
75+
76+
#### 2. Unsuspend models we use for our view
77+
78+
Now we use globs to declare which models we want to show up. We do this by "unsuspending"
79+
them.
80+
81+
```d2
82+
# Show every connection with a source to API Application
83+
(** -> **)[*]: unsuspend {
84+
&src: internet_banking_system.api_app
85+
}
86+
# Show every connection with a destination to API Application
87+
(** -> **)[*]: unsuspend {
88+
&dst: internet_banking_system.api_app
89+
}
90+
```
91+
92+
#### Result
93+
94+
<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-view1.svg2')}}></div>
95+
96+
Runnable code:
97+
<CodeBlock className="language-d2" expandeable={true}>
98+
{C4View1}
99+
</CodeBlock>
100+
101+
### Show high-level overview
102+
103+
Let's make a high level overview with the same concepts. The only change here is how we
104+
unsuspend things.
105+
106+
```d2
107+
# Only show root level shapes
108+
**: unsuspend {
109+
&level: 0
110+
}
111+
# Only show connections between root level things
112+
(** -> **)[*]: unsuspend {
113+
&src.level: 0
114+
&dst.level: 0
115+
}
116+
```
117+
118+
And since there are connections between inner shapes of Internet Banking System to other
119+
components, we'll make some connections that capture the general dependency.
120+
121+
```d2
122+
customer -> internet_banking_system: Send requests
123+
internet_banking_system -> mainframe: API calls
124+
internet_banking_system -> email_system: Send emails
125+
```
126+
127+
<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-view2.svg2')}}></div>
128+
129+
Runnable code:
130+
<CodeBlock className="language-d2" expandeable={true}>
131+
{C4View2}
132+
</CodeBlock>
133+
134+
## Tags and filters
135+
136+
Let's say your repository of models is large; maybe everyone in your cross-functional team
137+
is pushing their own models to this file.
138+
139+
To give an example of a small repository:
140+
141+
<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-models.svg2')}}></div>
142+
143+
<CodeBlock className="language-d2" expandeable={true}>
144+
{C4Models}
145+
</CodeBlock>
146+
147+
To reuse these models for a specific domain, we can filter by their classes (tag in C4
148+
vernacular). Assuming the models are in their own file, we can create something like a
149+
CustomerInterface.d2 file, which imports these models, suspends all, and unsuspends the
150+
ones that have the `customer-facing` class.
151+
152+
<CodeBlock className="language-d2-incomplete">
153+
{C4Tags2}
154+
</CodeBlock>
155+
156+
<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-tags2.svg2')}}></div>
157+
158+
Now we can add some connections to create a diagram out of these pre-existing models.
159+
160+
<CodeBlock className="language-d2-incomplete" expandeable={true}>
161+
{C4Tags3}
162+
</CodeBlock>
163+
164+
<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-tags3.svg2')}}></div>
165+
166+
## Legend
167+
168+
To create a legend for this diagram, we declare it as a [variable](/tour/vars) under
169+
`d2-legend`.
170+
171+
Think of `d2-legend` as a mini diagram of its own, but with a special layout where every
172+
shape and connection is deconstructed into a table. If something has opacity 0, it is
173+
excluded in the table (this way we can have legends that only show connections, for
174+
example).
175+
176+
```d2
177+
vars: {
178+
d2-legend: {
179+
banking: Banking {
180+
style.fill: "#FFE4E1"
181+
}
182+
banking -> x: Alerting {
183+
style.stroke-dash: 5
184+
style.stroke: "#2E8B57"
185+
}
186+
banking -> y: Authentication {
187+
style.stroke-width: 2
188+
style.stroke: "#800080"
189+
}
190+
# Remove unneeded objects
191+
x.style.opacity: 0
192+
y.style.opacity: 0
193+
}
194+
}
195+
```
196+
197+
Notice the legend in the bottom right corner.
198+
199+
<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-legend.svg2')}}></div>
200+
201+
## Google Maps zooming
202+
203+
Lastly, a core concept of C4 diagrams is this notion of zooming in and out of different
204+
levels of abstraction. The 4 in C4 stands for the 4 levels of abstractions that it
205+
recommends. Let's take a look at how that can be achieved. For this example, we'll define
206+
two diagrams of code that represent a zoomed in view of the components.
207+
208+
**Example code diagram for ATM Banking System**
209+
210+
<CodeBlock className="language-d2-incomplete" expandeable={true}>
211+
{C4Code1}
212+
</CodeBlock>
213+
214+
<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-code1.svg2')}}></div>
215+
216+
**Example code diagram for Notification System**
217+
218+
<CodeBlock className="language-d2-incomplete" expandeable={true}>
219+
{C4Code2}
220+
</CodeBlock>
221+
222+
<div className="embedSVG" dangerouslySetInnerHTML={{__html: require('@site/static/img/generated/c4-code2.svg2')}}></div>
223+
224+
### Linking together
225+
226+
Now let's link these up using the [layers](/tour/composition) feature.
227+
228+
All that's needed is to declare the 2 code diagrams as layers and add the `link` property
229+
to whichever shape we want to be able to zoom into them.
230+
231+
```d2
232+
...@c4-models.d2
233+
**: suspend
234+
**: unsuspend {
235+
&class: customer-facing
236+
}
237+
238+
notification_system.link: layers.notification
239+
mobile_banking_api.link: layers.banking
240+
241+
# Connections for component diagram
242+
# ...
243+
244+
layers: {
245+
banking: @banking-code
246+
notification: @notification-code
247+
}
248+
```
249+
250+
We can export this to a PDF format. If you download it and click on notification or
251+
banking, it'll take you to those respective zoomed-in code views.
252+
253+
<embed src={require('@site/static/img/generated/c4.pdf').default} width="100%" height="800"
254+
type="application/pdf" />
255+
256+
To recap, you'd split it out into those 4 files and use imports for modularity:
257+
1. `models.d2`: where models are defined
258+
2. `customer-components.d2`: where you import models
259+
3. `banking-code.d2`: code diagram for banking system
260+
3. `notification-code.d2`: code diagram for notification system
261+
262+
You can easily flesh this out further by having files for the styling, for higher levels
263+
of abstraction (e.g. for context diagrams).

ci/render.sh

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ _bespoke() {
1616
sh_c d2 --pad 0 ./static/bespoke-d2/cat.d2 ./static/img/generated/cat.pdf
1717
sh_c d2 --pad 50 ./static/bespoke-d2/lotr.d2 ./static/img/generated/lotr.pdf
1818

19+
sh_c d2 --pad 50 ./static/bespoke-d2/c4-code.d2 ./static/img/generated/c4.pdf
20+
1921
sh_c d2 --animate-interval=1600 -c --pad 50 -l elk ./static/bespoke-d2/tax.d2 ./static/img/generated/tax.svg2
2022
sh_c d2 --animate-interval=1600 -c --pad 0 -l elk ./static/bespoke-d2/pizza.d2 ./static/img/generated/pizza.svg2
2123
sh_c d2 --animate-interval=1600 -c --pad 0 -l elk ./static/bespoke-d2/johnwick.d2 ./static/img/generated/johnwick.svg2

0 commit comments

Comments
 (0)