Alpha Version: You are viewing the ALPHA documentation. This is an experimental version and may contain breaking changes.
Skip to main content

D2 Diagram Guide

This guide explains how to create D2 diagrams in the Reventless documentation, what classes are available, and the conventions to follow so every diagram looks and reads consistently.


How diagrams are rendered

D2 diagrams are embedded as fenced code blocks in any markdown file:

```d2
direction: down

cmd_topic: Command Topic { class: command-topic }
aggregate: Aggregate { class: aggregate }

cmd_topic -> aggregate: commands
```

The Docusaurus build runs a preprocessing plugin (d2PrependStyles in packages/doc/docusaurus.config.js) that automatically prepends the contents of packages/doc/d2/reventless.d2 to every d2 code block before passing it to the D2 compiler. This means:

  • No import line is needed in the markdown. Just write the diagram body.
  • All classes defined in reventless.d2 are always available.
  • VS Code preview works without errors (it renders the diagram without colors, since it runs D2 directly on the raw code block, but no import resolution error occurs because there is no import to resolve).

The shared styles file lives at packages/doc/d2/reventless.d2. That is the single source of truth for all colors, shapes, and container styles. Never put hex color values or shape overrides directly in a diagram. Assign a class instead.

Diagram sizing on the built site

Sequence diagrams (fenced d2 blocks containing shape: sequence_diagram) are tagged with a sequence-diagram CSS class by a rehype plugin, and src/css/custom.css caps their rendered width (via --seq-diagram-max-width) so they don't stretch to the full content column — and balloon — on wide screens. This cap is site CSS only: it lives in the Docusaurus bundle and does not affect any editor preview (see below). Keep the diagram body compact (short actor labels, terse edge labels) so it reads well before that cap scales it down.


Previewing diagrams while authoring (VS Code)

A VS Code preview renders the raw d2 code block — it does not run the Docusaurus pipeline. Two consequences trip people up:

  • No theme colors. The shared reventless.d2 styles are prepended only at build time, so previews render uncolored (this is expected — not an error).
  • No site CSS. The sequence-diagram width cap above lives in the site bundle, so a preview will never apply it. The built site is the source of truth for final colors and sizing; treat the editor preview as a quick structural check.

If you want a closer-to-final preview, pick an extension by how it renders:

ExtensionRenders intoCustom CSS / sizing
D2 in MarkdownVS Code's built-in markdown previewHonors the standard markdown.styles setting — point it at a small local CSS file to cap width
Markdown Preview Enhancedits own webviewPer-block options after the d2 fence tag (e.g. layout=elk theme=200 sketch) plus its own "Customize CSS" (style.less)
Terrastruct D2dedicated .d2-file previewPrimarily for standalone .d2 files; rendering fenced d2 blocks inside markdown preview is limited

Gotcha — don't reuse the site selector. The sequence-diagram class is injected by our Docusaurus rehype plugin, not by any extension. In an editor preview the diagram is just a plain img/svg, so a local cap must target a generic selector, e.g.:

/* a local CSS file referenced from markdown.styles — NOT the site's custom.css */
.markdown-body img { max-width: 460px; height: auto; }

These are per-machine editor preferences (VS Code settings / a local CSS file under the gitignored .vscode/), independent of the committed site CSS.


Color system — Event Storming post-it mapping

The color palette is derived from Event Storming post-it conventions so that the color of a node — and its connections — immediately communicates its DDD role.

ColorDDD roleNode classesConnection class
BlueCommand / Command infrastructurecommand, command-topic, command-generatorcommand-flow
YellowAggregate / Write-side logicaggregate, state-change-slice
OrangeEvent / Event storageevent-log, dcb-event-log, event-topic, event-collectorevent-flow, replay
GreenRead Model / Projectionread-model, state-view-slice, query-dbprojection-flow
PurplePolicy / Reaction / UIevent-mapper, counter, api, client
PinkExternal System / Side Effectside-effect, task, external-system
TealCross-context boundaryextension-point, extensioncross-plugin
GrayScheduling / Infrastructurescheduler, heartbeat, adapter

Node classes

These are applied to individual nodes with { class: <name> }.

Commands — blue

ClassShapeUse for
commandrectangleA command message type
command-topicqueueThe CommandTopic message queue
command-generatorrectangleCommandGenerator bridging API to CommandTopic

Aggregates — yellow

ClassShapeUse for
aggregaterectangleAn Aggregate root
state-change-slicerectangleA StateChangeSlice in a DCB plugin

Events and event storage — orange

ClassShapeUse for
event-logcylinderEventLog (per-aggregate event store)
dcb-event-logcylinderDcbEventLog (shared event store across slices)
event-topicqueueEventTopic fan-out channel
event-collectorrectangleEventCollector consuming from an EventTopic

Read models — green

ClassShapeUse for
read-modelrectangleA ReadModel projection
state-view-slicerectangleA StateViewSlice in a DCB plugin
query-dbcylinderQueryDb (read-side storage)

Policies and reactions — purple

ClassShapeUse for
event-mapperrectangleEventMapper mapping events to commands
counterrectangleCounter for deduplication or metrics
apirectangleGraphQL API / AppSync endpoint
clientrectangleGraphQL client / browser / external caller

External systems and side effects — pink

ClassShapeUse for
side-effectrectangleSideEffectHandler
taskrectangleTask (escape-hatch for side effects)
external-systemrectangleExternal third-party system (dashed border)

Cross-context boundary — teal

ClassShapeUse for
extension-pointhexagonExtensionPoint exposing a plugin's interface
extensionrectangleExtension consuming a remote ExtensionPoint

Scheduling and infrastructure — gray

ClassShapeUse for
schedulerrectangleScheduler for time-based command publishing
heartbeatrectangleHeartbeat for periodic health signals
adapterrectangleAdapter interface or adapter implementation node

Container classes

Containers group related nodes. Apply a container class alongside direction:

d2 diagram
ClassColorUse for
write-sideamberWrite-side area (CommandTopic → Aggregate → EventLog)
read-sidegreenRead-side area (EventCollector → ReadModel → QueryDb)
side-effects-areapinkSide effects group (collector → handler → task)
event-processing-areapurpleEvent processing group (collector → mapper → counter)
plugin-areatealPlugin System (ExtensionPoint + Extensions)
extension-point-areacyanGroup of ExtensionPoints exposed by a plugin
scheduling-areastoneScheduling group (Heartbeat + Scheduler)
adapter-areagrayAdapter interfaces or adapter implementation group
reventless-arealight blueReventless core framework package group
reventless-aws-areablueReventless AWS adapter package group
slices-areayellowGroup of StateChangeSlices inside a write side
view-slices-areagreenGroup of StateViewSlices inside a read side
query-dbs-areagreenGroup of QueryDbs inside a read side
api-areapurpleAPI + Client group

Layout helpers

ClassUse for
placeholderInvisible grid cell used as a spacer in grid layouts

Connection classes

Connections carry semantic meaning through their label and their color. Apply a connection class the same way as a node class — with { class: <name> } at the end of the edge declaration. Both the arrow line and the label text take the class color.

d2 diagram

Semantic flow classes

ClassColorUse for
event-floworangeAny edge that carries domain events — from event log to topic, topic to collector, collector to handler
command-flowblueAny edge that carries commands — from client/API to command topic, from mapper to command topic
projection-flowgreenEdges that write projected state — from read model / view slice to query DB

Structural modifier classes

These encode a structural characteristic (dashed line) in addition to a color.

ClassColorStyleUse for
replayorangedashed (3)Event log replaying past events back into an aggregate or slice to reconstruct state
cross-plugintealdashed (4)Any edge that crosses a plugin boundary — extension commands back to an extension point, or extension point commands into a foreign command topic

When to leave a connection unclassed

Not every edge needs a class. Leave the connection plain (no class) when the edge is infrastructural or doesn't carry a domain-meaningful payload:

  • api -> query_db: queries — a read query with no color identity
  • handler -> task: trigger — an internal side-effect invocation
  • handler -> external: calls — an outbound call to a third-party system

Never add style.stroke or style.font-color directly to a connection. Use a class or leave it unclassed.


Layout patterns

Simple diagrams — direction: down or direction: right

For diagrams with a clear linear or hierarchical flow, set direction at the top level and let D2's default layout engine (dagre) arrange everything. Containers size to their content naturally.

d2 diagram

Complex diagrams — ELK grid layout

Use a grid when the diagram has multiple independent areas that need to stay spatially separated (e.g., the DCB overview with write side, read side, plugin system, and scheduling all coexisting).

Rules:

  • Set vars: { d2-config: { layout-engine: elk } } at the top.
  • Wrap everything in a transparent root container with grid-rows and grid-columns.
  • Define items in reading order (left-to-right, then top-to-bottom) — D2 fills the grid by the order items are declared, not by their key names.
  • Use { class: placeholder } for empty grid cells, not { style.opacity: 0 }.
  • Keep the grid at most 3 × 3 at the top level.
  • Note that grid cells always stretch to fill their allocated cell. If a container is significantly smaller than its neighbors, consider whether a non-grid layout is more appropriate, or place the container's nodes directly as grid cells.
d2 diagram

Walkthrough: Aggregate-Based Plugin diagram

This is the simpler of the two overview diagrams. It uses D2's default layout engine with direction: down at the top level.

Structure:

  • client and api are top-level nodes, not inside any container.
  • write_side groups the command/event infrastructure (direction: right so components flow left to right).
  • consumers is a transparent wrapper (no class — just style.fill: "transparent") grouping three sub-areas: side_effects, event_processing, and read_side.
  • plugins and scheduling are independent top-level containers.
  • Edges at the bottom connect the top-level containers to each other.

Key pattern — transparent wrapper container:

When you need to group sub-areas for layout purposes without a visible border, skip the class and set transparency directly:

d2 diagram

Walkthrough: DCB-Based Plugin diagram

This diagram has more areas that need controlled placement, so it uses an ELK grid.

Grid layout (3 × 3):

Row 1:  placeholder  │  scheduling      │  placeholder
Row 2: plugins │ write_side │ graphql (API + Client)
Row 3: placeholder │ read_side │ (empty)

Items are declared in that exact order inside dcb_layout so D2 fills the grid correctly. The # ── Row N ── comments mark the boundaries.

Key pattern — nested grid inside a grid cell:

The graphql container uses grid-rows: 2 so client and api stack vertically within their single grid cell. This is fine for two nodes but note that the cell will stretch to match the height of the tallest cell in that row.

d2 diagram

Cross-cell edges:

Edges between nodes in different grid cells are declared at the bottom of dcb_layout, after all the cell definitions. Reference nested nodes with dot notation:

d2 diagram

Quick reference

Node classes

Commands — blue

d2 diagram

Aggregates — yellow

d2 diagram

Events and event storage — orange

d2 diagram

Read models — green

d2 diagram

Policies and UI — purple

d2 diagram

External systems and side effects — pink

d2 diagram

Cross-context boundary — teal

d2 diagram

Scheduling and infrastructure — gray

d2 diagram

Container classes

Write side

d2 diagram

Read side

d2 diagram

Event consumers

d2 diagram

Cross-cutting areas

d2 diagram

Layout helper

d2 diagram

Connection classes

Semantic flows

d2 diagram

Structural modifiers

d2 diagram

Unclassed — plain black arrow

Use for infrastructure edges that carry no domain-meaningful payload:

d2 diagram