Architecture patterns
A pattern is a ready-made, checked diagram for a common architecture: a three-tier network, a two-zone network, a serverless API, a delivery pipeline, a sign-in flow, a starter database schema. An agent finds the one that fits the request, renames the parts, and has a diagram that already validates with no warnings. It is written for an AI agent first, so each step names the tool to call and says what comes back. A person can follow the same steps by hand and copy the source from any picture below.
TL;DR
| Question | Answer |
|---|---|
| Why start from a pattern? | The groups, icons, edge styles, and layout are already right, so the work is naming things, not drawing them. Every pattern validates with no warnings in every flavour. |
| How do I find one? | Call find_pattern with the request in plain words, such as serverless api or two availability zones. The best match is first. |
| What do I get back? | An id, a title, a one-sentence summary, the providers it comes in, and tags. Never the source, so a search stays small. |
| How do I read one? | Call get_pattern with the id and a flavour in provider. It returns the source, every slot with its meaning, advice on what to keep, rename, and split, and the diagram as a tree. |
| How do I draw one? | Call draw_from_pattern with the id, a flavour, and the slot values you want to change. It fills, validates, and renders in one call. |
| What is a slot? | A named part of the diagram, such as office or database. A slot value replaces the label and nothing else, so it can never break the source. |
| What is a flavour? | The way a pattern is drawn. Every pattern has a neutral flavour, with generic icons, and many also have one provider's flavour: aws, azure, or gcp. You choose it with the provider field. |
| Which pattern is too small or too big? | Start from the closest one. If the request needs more than about 25 nodes, draw two diagrams and say so; each pattern says where to split. |
1. The recipe
Follow these steps in order. Each one ends with a check.
- Find the pattern. Call
find_patternwith the words of the request. Addproviderto keep only the patterns that come in that flavour,kindforflow,sequence, orschema, andtagsto require a tag. Read the summaries and pick one. If nothing fits, write the diagram from the language reference instead. - Read it. Call
get_patternwith the id and the flavour you want inprovider. A flavour the pattern does not have is a problem that lists the ones it has. Read the slots, then the three notes: what to keep, what to rename, and when to split. - Choose the slot values. Use the names from the request. A value is plain text of at most 60 characters, with no quotes, backslashes, or line breaks. A value that breaks a rule comes back as a problem with the slot named, never as an exception.
- Draw it. Call
draw_from_patternwith the id, the flavour, and the values. It returnsvalid, the warnings, a summary for the picture's description, an outline to check against the request, and the SVG for the themes you asked for. - Adapt what the slots cannot. To add a node, remove a service, or change an edge, take the
sourcethatget_patternreturned, edit it, and callvalidate_diagramuntil no warning is left. Keep the shape of the groups: they carry the meaning. - Check the outline. Call
outline_diagram, or read the outline in the answer, and compare it with the request line by line. Every service the request names is a node, and every node is something the request names.
A session for the request "a serverless API with a queue for slow work, on Google Cloud" looks like this:
| Call | Input | What comes back |
|---|---|---|
find_pattern | {"query": "serverless api queue", "provider": "gcp"} | serverless-api first, with its summary and tags |
get_pattern | {"id": "serverless-api", "provider": "gcp"} | The source, the slots, the notes, and the group tree |
draw_from_pattern | {"id": "serverless-api", "provider": "gcp", "slots": {"title": "Order API"}} | valid: true, no warnings, an outline, and the SVG |
2. Slots
A slot is a named part of a pattern. Every pattern has title and description: the first is the name of the diagram, and the second is the sentence that a screen reader reads in place of the picture. The others are named for what they hold, such as office, load-balancer, or primary-database.
| Rule | Why |
|---|---|
| A slot value replaces a label or a second line, nothing else | A value cannot change an id, a type, an icon, or an arrow, so a bad value cannot break the diagram. |
A value has at most 60 characters, except description | A box grows to fit its text, and a long label makes the diagram wide. |
| A value has no quotes, backslashes, or line breaks | The label is a quoted text in the source. |
| An unknown slot is a problem with the nearest slot name | office and ofice are one letter apart; the answer says which you meant. |
| A slot that is not set keeps its default | The defaults are placeholders such as Web tier and 10.0.0.0/16, drawn in the pictures below. |
A slot used in several places, such as the name of a load balancer drawn once for each tier, is set once and changes everywhere.
3. The patterns
| Pattern | Use it for | Id | Flavours |
|---|---|---|---|
| Three-tier network behind load balancers | An office network joins a cloud network through a VPN gateway, and three tiers each put a load balancer in front of a redundant set of machines. | three-tier-network | Neutral, Azure |
| Two-zone network with a replicated database | A network spans two availability zones, each with a public, a private, and an isolated subnet, joined by one balancer set, one scaling group, and a replicated database. | two-zone-network | Neutral, AWS |
| Hub and spoke network | A hub network with a firewall and shared services links an office to several isolated spoke networks, so all traffic between them passes one place. | hub-and-spoke-network | Neutral, Azure |
| Web application on a cloud | A single-page app is served through a CDN, backed by an API on a hosting plan, a database, a queue and worker, identity, storage, a delivery pipeline, and monitoring. | web-app-on-cloud | Neutral, Azure |
| Serverless API | A client calls an API gateway that checks sign-in and routes to three functions, which keep records in a managed database and files in object storage. | serverless-api | Neutral, AWS, Google Cloud |
| Microservices behind an API gateway | Clients call one API gateway that checks identity and routes to small services, which talk to each other through a message bus and each keep their own database. | microservices-gateway | Neutral, AWS, Azure, Google Cloud |
| Container platform | A delivery pipeline pushes images to a registry, and a managed cluster runs three services on a redundant node pool behind an ingress, with configuration, secrets, and monitoring. | container-platform | Neutral, Google Cloud |
| Queue and worker pool | A web API puts jobs on a queue, a pool of workers takes them and writes results to a database, failed jobs go to a dead-letter queue, and an alarm watches the queue depth. | queue-worker | Neutral, AWS |
| Event-driven pipeline | Several services publish events to an event bus, three independent consumers react to them, and events that keep failing go to a dead-letter queue. | event-driven-pipeline | Neutral, Google Cloud |
| Static site behind a CDN | Visitors resolve a name through DNS and reach a content delivery network with a certificate, which serves files from a storage bucket that a build pipeline updates and clears from the cache. | static-site-cdn | Neutral, AWS |
| Data lake and analytics | Sources feed an ingestion stage into a raw zone, a processing stage cleans the data into a curated zone, and a warehouse serves analysts through dashboards, with a catalog that describes the datasets. | data-lake-analytics | Neutral, Google Cloud |
| Multi-region failover | A global routing service sends users to a primary region and fails over to a standby region, which holds replicated data and an identical stack. | multi-region-failover | Neutral, AWS |
| CI/CD delivery pipeline with an approval gate | A commit is built, tested, scanned, and stored as an image, then deployed to staging, held for an approval, and released to production. | ci-cd-delivery-pipeline | Neutral, Azure |
| Sign-in with an authorization code and proof key | A browser app signs a person in through an identity provider with the authorization code flow and a proof key, calls a backend API with the access token, and refreshes it when it expires. | sign-in-token-flow | Neutral |
| Multi-tenant application schema | Organizations own projects and an audit log, and users join organizations through a memberships table that carries a role. | database-schema-starter | Neutral |
Every id is stable. Tools and links use it, so it never changes.
4. The gallery
Each pattern is drawn below in its neutral flavour, with its slots. The defaults are placeholders: documentation address ranges such as 192.0.2.0/24 and generic names such as web-tier.
Three-tier network behind load balancers
An office network joins a cloud network through a VPN gateway, and three tiers each put a load balancer in front of a redundant set of machines.
When to use it. Start here when the request is a classic web, application, and data layering inside a private network, especially when an office or data center connects to it. It shows what each tier holds, where the redundancy is, and the one way operators get in.
Id and flavours. three-tier-network, drawn below in its neutral flavour. It also comes in: Azure.
flow "Three-tier network behind load balancers" direction=down desc="An office network reaches a cloud network through a VPN gateway. Inside it, a web tier, an application tier, and a data tier each put a load balancer in front of a redundant set of three machines. Operators reach the machines only through a management subnet."
group office "Office network" type=on-premises { node desks client "Staff workstations" node office-gw gateway "Office gateway"}
group link "Gateway subnet" type=subnet sub="10.0.0.0/27" { node vpn gateway "VPN gateway" icon=lucide:router}
group cloud "Cloud side" type=cloud-boundary sub="10.0.0.0/16" { group web "Web tier" type=web-tier sub="10.0.1.0/24" stack=lucide:shield-check { node lb-web gateway "Load balancer" node web1 service "Web 1" node web2 service "Web 2" node web3 service "Web 3" } group app "Application tier" type=app-tier sub="10.0.2.0/24" stack=lucide:shield-check { node lb-app gateway "Load balancer" node app1 service "App 1" node app2 service "App 2" node app3 service "App 3" } group data "Data tier" type=data-tier sub="10.0.3.0/24" stack=lucide:shield-check { node lb-data gateway "Load balancer" node data1 database "Data 1" node data2 database "Data 2" node data3 database "Data 3" } group mgmt "Management subnet" type=management-subnet sub="10.0.4.0/27" stack=lucide:shield-check { node jump service "Jump host" }}
overlay web-set "Web machines" type=scale-group members=web1,web2,web3overlay app-set "Application machines" type=scale-group members=app1,app2,app3overlay data-set "Data machines" type=scale-group members=data1,data2,data3
desks <-> office-gwoffice-gw <-> vpnvpn -> lb-webvpn --> jump : "admin access"lb-web -> web1lb-web -> web2lb-web -> web3web2 -> lb-applb-app -> app1lb-app -> app2lb-app -> app3app2 -> lb-datalb-data -> data1lb-data -> data2lb-data -> data3Nodes:
- On premises: Office network
- Staff workstations, client
- Office gateway, gateway
- Subnet: Gateway subnet (10.0.0.0/27)
- VPN gateway, gateway
- Cloud boundary: Cloud side (10.0.0.0/16)
- Web tier: Web tier (10.0.1.0/24), stack: shield-check
- Load balancer, gateway
- Web 1, service
- Web 2, service
- Web 3, service
- Application tier: Application tier (10.0.2.0/24), stack: shield-check
- Load balancer, gateway
- App 1, service
- App 2, service
- App 3, service
- Data tier: Data tier (10.0.3.0/24), stack: shield-check
- Load balancer, gateway
- Data 1, database
- Data 2, database
- Data 3, database
- Management subnet: Management subnet (10.0.4.0/27), stack: shield-check
- Jump host, service
- Web tier: Web tier (10.0.1.0/24), stack: shield-check
Overlays:
- Scale group overlay: Web machines, around Web 1, Web 2, Web 3
- Scale group overlay: Application machines, around App 1, App 2, App 3
- Scale group overlay: Data machines, around Data 1, Data 2, Data 3
Connections:
- Staff workstations with Office gateway
- Office gateway with VPN gateway
- VPN gateway to Load balancer
- VPN gateway to Jump host (dashed): admin access
- Load balancer to Web 1
- Load balancer to Web 2
- Load balancer to Web 3
- Web 2 to Load balancer
- Load balancer to App 1
- Load balancer to App 2
- Load balancer to App 3
- App 2 to Load balancer
- Load balancer to Data 1
- Load balancer to Data 2
- Load balancer to Data 3
| Slot | Default | What goes there |
|---|---|---|
office | Office network | The network on your side of the link: an office, a data center, or a branch. |
workstations | Staff workstations | Who or what uses the system from the office side. |
office-gateway | Office gateway | The router or firewall that ends the link on your side. |
link | Gateway subnet | The subnet that holds the cloud end of the link. |
link-address | 10.0.0.0/27 | The address range of that subnet, as a plain sample value. |
vpn-gateway | VPN gateway | The cloud end of the link between the office and the cloud network. |
cloud | Cloud side | The name of the cloud network that everything else sits in. |
cloud-address | 10.0.0.0/16 | The address range of the whole cloud network. |
web-tier | Web tier | The first tier, which users reach. |
web-address | 10.0.1.0/24 | The address range of the web tier. |
app-tier | Application tier | The middle tier, which runs the application code. |
app-address | 10.0.2.0/24 | The address range of the application tier. |
data-tier | Data tier | The last tier, which holds the data. |
data-address | 10.0.3.0/24 | The address range of the data tier. |
load-balancer | Load balancer | The name of the balancer in front of each tier. |
web-machine | Web | What a machine of the web tier is called; a number follows it. |
app-machine | App | What a machine of the application tier is called; a number follows it. |
data-machine | Data | What a machine of the data tier is called; a number follows it. |
web-set | Web machines | The name of the redundant set in the web tier. |
app-set | Application machines | The name of the redundant set in the application tier. |
data-set | Data machines | The name of the redundant set in the data tier. |
management | Management subnet | The subnet operators use to reach the machines. |
management-address | 10.0.4.0/27 | The address range of the management subnet. |
jump-host | Jump host | The one machine operators sign in to. |
Keep. Keep the order of the tiers, the edge from the middle machine of one tier to the load balancer of the next (it keeps the tiers level), the three overlays, and direction=down. Keep every machine in a tier as a member of that tier's overlay.
Rename. Rename the tiers, the machines, and the address ranges through the slots. Change the number of machines by adding a node, an edge from the load balancer, and the member id in the overlay, in all three places. Swap the generic icons for a provider's with the flavour you pick.
Split. Past about four machines in a tier, or a fourth tier, draw the tier alone in its own diagram and keep one machine of it here. Draw the management and monitoring paths in a second diagram if they pull in more than two nodes.
Two-zone network with a replicated database
A network spans two availability zones, each with a public, a private, and an isolated subnet, joined by one balancer set, one scaling group, and a replicated database.
When to use it. Start here when the request is a network that survives the loss of a zone: public subnets for the balancers, private subnets for the servers, isolated subnets for the data. It shows which resources are per zone, which span both, and how the data is kept in step.
Id and flavours. two-zone-network, drawn below in its neutral flavour. It also comes in: AWS.
flow "Two-zone network with a replicated database" direction=down desc="Traffic enters through an internet gateway and reaches load balancers in the public subnets of two availability zones. Each balancer sends it to an application server in a private subnet, and the two servers form one scaling group. Each server writes to a database in an isolated subnet, and the primary database replicates to the replica in the other zone."
node internet external "Internet"
group cloud "Cloud" type=cloud { node igw gateway "Internet gateway" icon=lucide:door-open
group vpc "Application network" type=network sub="10.0.0.0/16" { group za "Zone A" type=zone { group pub-a "Public subnet A" type=public-subnet sub="10.0.0.0/24" { node lb-a gateway "Load balancer A" } group app-a "Private subnet A" type=private-subnet sub="10.0.1.0/24" { node web-a service "Application server A" } group db-za "Isolated subnet A" type=isolated-subnet sub="10.0.2.0/24" { node db-a database "Primary database" } } group zb "Zone B" type=zone { group pub-b "Public subnet B" type=public-subnet sub="10.0.10.0/24" { node lb-b gateway "Load balancer B" } group app-b "Private subnet B" type=private-subnet sub="10.0.11.0/24" { node web-b service "Application server B" } group db-zb "Isolated subnet B" type=isolated-subnet sub="10.0.12.0/24" { node db-b database "Replica database" } } }}
overlay balancer "Load balancer" type=edge members=lb-a,lb-boverlay scaling "Scaling group" type=scale-group members=web-a,web-b
internet <-> igwigw -> lb-aigw -> lb-blb-a -> web-alb-b -> web-bweb-a -> db-aweb-b -> db-bdb-a ..> db-b : "replication"Nodes:
- Internet, external
- Cloud: Cloud
- Internet gateway, gateway
- Virtual network: Application network (10.0.0.0/16)
- Availability zone: Zone A
- Public subnet: Public subnet A (10.0.0.0/24)
- Load balancer A, gateway
- Private subnet: Private subnet A (10.0.1.0/24)
- Application server A, service
- Isolated subnet: Isolated subnet A (10.0.2.0/24)
- Primary database, database
- Public subnet: Public subnet A (10.0.0.0/24)
- Availability zone: Zone B
- Public subnet: Public subnet B (10.0.10.0/24)
- Load balancer B, gateway
- Private subnet: Private subnet B (10.0.11.0/24)
- Application server B, service
- Isolated subnet: Isolated subnet B (10.0.12.0/24)
- Replica database, database
- Public subnet: Public subnet B (10.0.10.0/24)
- Availability zone: Zone A
Overlays:
- Edge overlay: Load balancer, around Load balancer A, Load balancer B
- Scale group overlay: Scaling group, around Application server A, Application server B
Connections:
- Internet with Internet gateway
- Internet gateway to Load balancer A
- Internet gateway to Load balancer B
- Load balancer A to Application server A
- Load balancer B to Application server B
- Application server A to Primary database
- Application server B to Replica database
- Primary database to Replica database (dotted): replication
| Slot | Default | What goes there |
|---|---|---|
internet | Internet | Where the traffic comes from. |
cloud | Cloud | The outermost frame: the provider cloud, or your account in it. |
gateway | Internet gateway | The gateway that connects the network to the internet. |
network | Application network | The private network that holds both zones. |
network-address | 10.0.0.0/16 | The address range of the network, as a plain sample value. |
zone-a | Zone A | The name of the first availability zone. |
zone-b | Zone B | The name of the second availability zone. |
public-a | Public subnet A | The public subnet of the first zone. |
public-a-address | 10.0.0.0/24 | The address range of that subnet. |
public-b | Public subnet B | The public subnet of the second zone. |
public-b-address | 10.0.10.0/24 | The address range of that subnet. |
private-a | Private subnet A | The private subnet of the first zone, which holds the application server. |
private-a-address | 10.0.1.0/24 | The address range of that subnet. |
private-b | Private subnet B | The private subnet of the second zone, which holds the application server. |
private-b-address | 10.0.11.0/24 | The address range of that subnet. |
isolated-a | Isolated subnet A | The isolated subnet of the first zone, which holds the database. |
isolated-a-address | 10.0.2.0/24 | The address range of that subnet. |
isolated-b | Isolated subnet B | The isolated subnet of the second zone, which holds the database. |
isolated-b-address | 10.0.12.0/24 | The address range of that subnet. |
balancer | Load balancer | The name of the balancer; a zone letter follows it on each node, and it names the dashed set that spans both zones. |
server | Application server | What the server in each private subnet is called; a zone letter follows it. |
scaling | Scaling group | The name of the dashed set that spans the servers of both zones. |
primary | Primary database | The database that takes the writes, in the first zone. |
replica | Replica database | The database that follows the primary, in the second zone. |
replication | replication | What the dotted link between the two databases carries. |
Keep. Keep both zones as siblings with the same three subnets in the same order, one node in each subnet, and the two dashed sets: they are what shows the balancer and the scaling group span both zones. Keep the replication link between the two databases.
Rename. Rename the zones, subnets, and resources through the slots, and replace the sample address ranges with the real ones. Give a subnet more than one node by adding nodes inside it and edges to them; add the new servers to the scaling set's members.
Split. Past about three nodes in a subnet, or a third zone, draw one zone alone in a second diagram and show only the balancer, one server, and the database here. Draw the outbound path (a NAT gateway and the routes) in its own diagram.
Hub and spoke network
A hub network with a firewall and shared services links an office to several isolated spoke networks, so all traffic between them passes one place.
When to use it. Start here when several workloads need their own networks but share connectivity, security, and services: each workload gets a spoke, and the hub holds what they share. It shows that spokes reach each other and the office only through the hub firewall.
Id and flavours. hub-and-spoke-network, drawn below in its neutral flavour. It also comes in: Azure.
flow "Hub and spoke network" direction=down desc="An office network reaches a hub network through a VPN gateway. Every packet between the office, the shared services, and the three spoke networks passes the hub firewall. Each spoke holds one workload and its database, and spokes never connect to each other directly."
group office "Office network" type=on-premises { node staff client "Staff" node office-gw gateway "Office gateway" icon=lucide:router}
group cloud "Cloud side" type=cloud-boundary { group hub "Hub network" type=network tone=violet sub="10.0.0.0/22" { node vpn gateway "VPN gateway" icon=lucide:router node firewall gateway "Central firewall" icon=lucide:shield node shared service "Shared services" icon=lucide:server-cog } group spoke-a "Spoke A" type=network sub="10.1.0.0/20" { node app-a service "Orders application" node db-a database "Database" } group spoke-b "Spoke B" type=network sub="10.2.0.0/20" { node app-b service "Reporting application" node db-b database "Database" } group spoke-c "Spoke C" type=network sub="10.3.0.0/20" { node app-c service "Partner portal" node db-c database "Database" }}
staff <-> office-gwoffice-gw <-> vpnvpn -> firewallfirewall -> shared : "name lookups"firewall -> app-afirewall -> app-bfirewall -> app-capp-a -> db-aapp-b -> db-bapp-c -> db-cNodes:
- On premises: Office network
- Staff, client
- Office gateway, gateway
- Cloud boundary: Cloud side
- Virtual network: Hub network (10.0.0.0/22)
- VPN gateway, gateway
- Central firewall, gateway
- Shared services, service
- Virtual network: Spoke A (10.1.0.0/20)
- Orders application, service
- Database, database
- Virtual network: Spoke B (10.2.0.0/20)
- Reporting application, service
- Database, database
- Virtual network: Spoke C (10.3.0.0/20)
- Partner portal, service
- Database, database
- Virtual network: Hub network (10.0.0.0/22)
Connections:
- Staff with Office gateway
- Office gateway with VPN gateway
- VPN gateway to Central firewall
- Central firewall to Shared services: name lookups
- Central firewall to Orders application
- Central firewall to Reporting application
- Central firewall to Partner portal
- Orders application to Database
- Reporting application to Database
- Partner portal to Database
| Slot | Default | What goes there |
|---|---|---|
staff | Staff | Who uses the workloads from the office. |
office | Office network | The network on your side of the link. |
office-gateway | Office gateway | The router or firewall that ends the link on your side. |
cloud | Cloud side | The name of the frame around the hub and the spokes. |
hub | Hub network | The shared network every spoke connects through. |
hub-address | 10.0.0.0/22 | The address range of the hub, as a plain sample value. |
vpn-gateway | VPN gateway | The cloud end of the link to the office. |
firewall | Central firewall | The firewall every route between networks passes. |
shared | Shared services | The services every spoke uses, such as name resolution and directory. |
spoke-a | Spoke A | The name of the first spoke network. |
spoke-a-address | 10.1.0.0/20 | The address range of the first spoke. |
spoke-b | Spoke B | The name of the second spoke network. |
spoke-b-address | 10.2.0.0/20 | The address range of the second spoke. |
spoke-c | Spoke C | The name of the third spoke network. |
spoke-c-address | 10.3.0.0/20 | The address range of the third spoke. |
workload-a | Orders application | The workload that runs in the first spoke. |
workload-b | Reporting application | The workload that runs in the second spoke. |
workload-c | Partner portal | The workload that runs in the third spoke. |
database | Database | The database each spoke keeps for its workload. |
Keep. Keep every spoke connected to the hub firewall and to nothing else outside its own group: that is what the pattern says. Keep the hub in its own tone, and keep the office behind the gateway.
Rename. Rename the spokes after the workloads or teams that own them, and give each its real address range. Add a spoke by copying one group, its two nodes, and its two edges.
Split. Past about five spokes, draw the hub with two spokes here and list the others in a table, or draw a second hub for a second region in its own diagram.
Web application on a cloud
A single-page app is served through a CDN, backed by an API on a hosting plan, a database, a queue and worker, identity, storage, a delivery pipeline, and monitoring.
When to use it. Start here when the request is a typical web application and the reader needs to see every managed service around it: where files live, where code runs, how users sign in, how slow work is queued, and how changes ship. It is the usual first diagram of a cloud system.
Id and flavours. web-app-on-cloud, drawn below in its neutral flavour. It also comes in: Azure.
flow "Web application on a cloud" direction=down desc="Visitors load a single-page app from storage through a content delivery network and sign in through an identity service. The app calls an API on an application plan, which stores uploads, reads a database, and hands slow work to a queue that a background worker drains. A pipeline builds and deploys the API, and a monitoring service collects telemetry."
node visitors client "Visitors" sub="Browsers"
group edge "Edge" type=edge { node cdn gateway "Content delivery"}
group sign-in "Sign-in" type=identity-boundary { node idp identity "Identity service"}
group files "Storage" type=storage-account { node site storage "Web app files" stack=simple:react,simple:typescript node uploads storage "Uploads"}
group plan "Application plan" type=service-plan { node api service "Orders API" stack=simple:nodedotjs}
group messaging "Messaging" type=messaging { node jobs queue "Job queue"}
node worker function "Background worker" stack=simple:nodedotjsnode db database "Database"
group delivery "Delivery pipeline" type=ci-cd { node repo step "Source code" node build step "Build pipeline"}
group monitoring "Monitoring" type=observability { node telemetry service "Metrics and logs"}
visitors -> cdn : "HTTPS"visitors -> idp : "sign in" role=identitycdn -> sitecdn -> api : "API calls"api -> uploads : "store files" role=dataapi -> jobs : "enqueue" role=asyncjobs ..> worker : "event"worker -> db : "writes" role=dataapi -> db : "reads" role=datarepo -> buildbuild ..> api : "deploys image"api ..> telemetry : "telemetry"Nodes:
- Visitors (Browsers), client
- Background worker, function, stack: nodedotjs
- Database, database
- Edge: Edge
- Content delivery, gateway
- Identity: Sign-in
- Identity service, identity
- Storage: Storage
- Web app files, storage, stack: react, typescript
- Uploads, storage
- Service plan: Application plan
- Orders API, service, stack: nodedotjs
- Messaging: Messaging
- Job queue, queue
- CI and CD: Delivery pipeline
- Source code, step
- Build pipeline, step
- Observability: Monitoring
- Metrics and logs, service
Connections:
- Visitors to Content delivery: HTTPS
- Visitors to Identity service (identity line): sign in
- Content delivery to Web app files
- Content delivery to Orders API: API calls
- Orders API to Uploads (stored data line): store files
- Orders API to Job queue (asynchronous line): enqueue
- Job queue to Background worker (dotted): event
- Background worker to Database (stored data line): writes
- Orders API to Database (stored data line): reads
- Source code to Build pipeline
- Build pipeline to Orders API (dotted): deploys image
- Orders API to Metrics and logs (dotted): telemetry
| Slot | Default | What goes there |
|---|---|---|
visitors | Visitors | Who uses the application. |
visitors-sub | Browsers | A short second line for the visitors, such as the kind of client. |
edge | Edge | The name of the group in front of the application. |
cdn | Content delivery | The content delivery network that serves files and forwards API calls. |
sign-in | Sign-in | The name of the group that holds the identity service. |
identity | Identity service | The service that signs users in and issues tokens. |
storage | Storage | The name of the group that holds the stored files. |
site | Web app files | The files of the single-page app. |
uploads | Uploads | Where files that users upload are kept. |
plan | Application plan | The name of the hosting plan that runs the API. |
api | Orders API | The service the app calls; name it for what it does. |
messaging | Messaging | The name of the group that holds the queue. |
queue | Job queue | The queue the API puts slow work on. |
worker | Background worker | The function that drains the queue. |
database | Database | Where the application keeps its records. |
delivery | Delivery pipeline | The name of the group that builds and deploys the API. |
source | Source code | Where the code lives. |
build | Build pipeline | The step that builds and deploys the image. |
monitoring | Monitoring | The name of the group that watches the application. |
telemetry | Metrics and logs | The service that collects what the API reports. |
Keep. Keep one group for each concern (edge, sign-in, storage, hosting plan, messaging, delivery, monitoring), the queue between the API and the worker as a dotted link, and the deploy link from the pipeline to the API. Keep direction=down.
Rename. Rename the services for what the application does, and the stack marks for its technologies (up to three on a node). Add a node to a group for a second service of the same kind, with an edge from the node that uses it.
Split. Past about 20 nodes, move the data stores and the queue into a second diagram that starts at the API, and keep only the API here. Draw the pipeline alone when it has more than three steps.
Serverless API
A client calls an API gateway that checks sign-in and routes to three functions, which keep records in a managed database and files in object storage.
When to use it. Start here when the request is a small HTTP API built from managed services with no servers to run: a gateway, short-lived functions, a managed database, and a bucket. It shows where sign-in is checked and where telemetry goes. Start from the three-tier network pattern instead when the services run on machines you manage.
Id and flavours. serverless-api, drawn below in its neutral flavour. It also comes in: AWS, Google Cloud.
flow "Serverless API" direction=down desc="A client calls an API gateway, which checks the caller with a sign-in service and routes each request to one of three functions. The functions keep records in a managed database and files in object storage, and send logs and metrics to monitoring."
node client client "Client app" sub="Web or mobile"node gateway gateway "Orders API" sub="API gateway"
group fns "Functions" type=app-tier { node create function "Create order" node list function "List orders" node get function "Get order"}
group signin "Sign-in" type=identity-boundary { node auth identity "User directory" sub="Issues tokens"}
group data "Data stores" type=data-tier { node bucket storage "Order files" sub="Object storage" node db database "Orders database" sub="Managed database"}
group obs "Monitoring" type=observability { node monitor service "Logs and metrics" icon=lucide:activity}
client -> gateway : "HTTPS"gateway -> create : "POST /orders"gateway -> list : "GET /orders"gateway -> get : "GET /orders/id"gateway -> auth : "verify token" role=identitycreate -> bucket : "upload" role=datacreate -> db : "write" role=datalist -> dbget -> dbget ..> monitor : "logs and metrics"Nodes:
- Client app (Web or mobile), client
- Orders API (API gateway), gateway
- Application tier: Functions
- Create order, function
- List orders, function
- Get order, function
- Identity: Sign-in
- User directory (Issues tokens), identity
- Data tier: Data stores
- Order files (Object storage), storage
- Orders database (Managed database), database
- Observability: Monitoring
- Logs and metrics, service
Connections:
- Client app to Orders API: HTTPS
- Orders API to Create order: POST /orders
- Orders API to List orders: GET /orders
- Orders API to Get order: GET /orders/id
- Orders API to User directory (identity line): verify token
- Create order to Order files (stored data line): upload
- Create order to Orders database (stored data line): write
- List orders to Orders database
- Get order to Orders database
- Get order to Logs and metrics (dotted): logs and metrics
| Slot | Default | What goes there |
|---|---|---|
client | Client app | The browser, mobile app, or program that calls the API. |
client-sub | Web or mobile | A second line that says what kind of client it is. |
gateway | Orders API | The public name of the API that the gateway fronts. |
path | /orders | The base path of the API, shown on the routes from the gateway. |
sign-in | Sign-in | The name of the group that holds the identity service. |
auth | User directory | The service that holds users and issues the tokens the gateway checks. |
functions | Functions | The name of the group that holds the three request handlers. |
fn-list | List orders | The function that returns many records. |
fn-get | Get order | The function that returns one record. |
fn-create | Create order | The function that stores a new record and its file. |
data | Data stores | The name of the group that holds the database and the object storage. |
database | Orders database | The managed database that keeps the records. |
storage | Order files | The object storage that keeps uploaded files. |
monitoring | Monitoring | The name of the group that holds the telemetry service. |
monitor | Logs and metrics | The service that collects logs, metrics, and traces. |
Keep. Keep the order of the groups (functions, sign-in, data stores, monitoring), the order of the functions (create, list, get), the sign-in check as the last edge from the gateway, the one dotted telemetry edge, which stands for every function, and direction=down. Keep the database and the storage in one group, because a group for each makes the arrows from the functions bunch at one point.
Rename. Rename the functions, the database, and the storage through the slots. Add a function by adding a node in the functions group, an edge from the gateway, and an edge to what it uses. Pick the aws or gcp flavour to swap the generic glyphs for that provider's icons.
Split. Past about five functions, draw the functions as one node here and give each route group its own diagram. Draw a deployment pipeline in a second diagram.
Microservices behind an API gateway
Clients call one API gateway that checks identity and routes to small services, which talk to each other through a message bus and each keep their own database.
When to use it. Start here when the request is a system split into small services that own their data: one public entry point, a sign-in check, and events between services instead of shared tables. It shows who may be called from outside and who only reacts to events. Start from the serverless API pattern instead when the services are short-lived functions.
Id and flavours. microservices-gateway, drawn below in its neutral flavour. It also comes in: AWS, Azure, Google Cloud.
flow "Microservices behind an API gateway" direction=down desc="Client apps call one API gateway, which checks each caller with an identity provider and routes the request to a catalog service or a booking service. The booking service publishes an event to a message bus, and a billing service reacts to it. Every service keeps its own database, and no service reads another service's database."
node clients client "Client apps" sub="Web and mobile"node gateway gateway "Public API" sub="API gateway"
group sign-in "Sign-in" type=identity-boundary { node idp identity "Identity provider" sub="Issues tokens"}
group public "Public services" type=app-tier { node catalog service "Catalog service" node booking service "Booking service"}
group messaging "Messaging" type=messaging { node bus queue "Message bus" sub="Events between services"}
group internal "Internal services" type=app-tier { node billing service "Billing service"}
node catalog-db database "Catalog database" sub="Private to the catalog"node booking-db database "Booking database" sub="Private to bookings"node billing-db database "Billing database" sub="Private to billing"
clients -> gateway : "HTTPS"gateway -> idp : "verify token" role=identitygateway -> catalog : "/catalog"gateway -> booking : "/bookings"catalog -> catalog-dbbooking -> booking-dbbooking ..> bus : "booking created"bus ..> billingbilling -> billing-dbNodes:
- Client apps (Web and mobile), client
- Public API (API gateway), gateway
- Catalog database (Private to the catalog), database
- Booking database (Private to bookings), database
- Billing database (Private to billing), database
- Identity: Sign-in
- Identity provider (Issues tokens), identity
- Application tier: Public services
- Catalog service, service
- Booking service, service
- Messaging: Messaging
- Message bus (Events between services), queue
- Application tier: Internal services
- Billing service, service
Connections:
- Client apps to Public API: HTTPS
- Public API to Identity provider (identity line): verify token
- Public API to Catalog service: /catalog
- Public API to Booking service: /bookings
- Catalog service to Catalog database
- Booking service to Booking database
- Booking service to Message bus (dotted): booking created
- Message bus to Billing service (dotted)
- Billing service to Billing database
| Slot | Default | What goes there |
|---|---|---|
clients | Client apps | The browsers, mobile apps, or programs that call the API. |
clients-sub | Web and mobile | A second line that says what kind of client it is. |
gateway | Public API | The public name of the API that the gateway fronts. |
catalog-path | /catalog | The path the gateway routes to the catalog service. |
booking-path | /bookings | The path the gateway routes to the booking service. |
sign-in | Sign-in | The name of the group that holds the identity provider. |
identity | Identity provider | The service that holds users and issues the tokens the gateway checks. |
public-services | Public services | The name of the group of services the gateway exposes. |
catalog | Catalog service | The service that answers read requests for the things you offer. |
booking | Booking service | The service that accepts requests and publishes an event for each one. |
messaging | Messaging | The name of the group that holds the message bus. |
bus | Message bus | The topic or bus that carries events from one service to the others. |
event | booking created | The event the booking service publishes and the billing service reads. |
internal-services | Internal services | The name of the group of services that no client calls directly. |
billing | Billing service | The service that reacts to events and has no public route. |
catalog-database | Catalog database | The database that only the catalog service reads and writes. |
booking-database | Booking database | The database that only the booking service reads and writes. |
billing-database | Billing database | The database that only the billing service reads and writes. |
Keep. Keep one database for each service and no edge from a service to another service's database, because that is what the pattern says. Keep the dotted edges through the message bus, the public and internal groups, the identity check as an edge from the gateway only, and direction=down.
Rename. Rename the services, the databases, the routes, and the event through the slots. Add a public service by adding a node in the public group, an edge from the gateway, and a database with an edge from the service. Pick the aws, azure, or gcp flavour to swap the generic glyphs for that provider's icons.
Split. Past about five services, draw the gateway and the public services in one diagram and give each group of related services its own, with the bus shown as an external node. Draw tracing, deployment, and the service mesh in separate diagrams.
Container platform
A delivery pipeline pushes images to a registry, and a managed cluster runs three services on a redundant node pool behind an ingress, with configuration, secrets, and monitoring.
When to use it. Start here when the request is a Kubernetes or managed container platform: where images come from, how traffic reaches the services, and what the services depend on. It does not show networks, multiple environments, or the data stores the services use.
Id and flavours. container-platform, drawn below in its neutral flavour. It also comes in: Google Cloud.
flow "Container platform" direction=down desc="A delivery pipeline builds container images and pushes them to a registry. A managed cluster pulls the images and runs three services on a redundant node pool behind an ingress. The API service reads configuration and secrets, and a monitoring service collects metrics."
group delivery "Delivery pipeline" type=ci-cd { node repo step "Source repository" icon=lucide:git-branch node build step "Build and test" icon=lucide:cog}
node registry storage "Image registry" icon=lucide:package
node users client "Users"
group cluster "Managed cluster" type=cluster sub="Managed control plane" { node ingress gateway "Ingress" node web service "Web service" node api service "API service" node admin service "Admin service" node config storage "Configuration" icon=lucide:settings node secrets secret "Secrets"}
overlay pool "Node pool" type=scale-group members=web,api,admin
group watch "Monitoring" type=observability { node monitor service "Monitoring service" icon=lucide:activity}
repo -> buildbuild -> registry : "push image"registry ..> webusers -> ingress : "HTTPS"ingress -> webingress -> apiingress -> adminapi -> config : "reads" icon=lucide:eyeweb ..> monitor : "metrics"api -> secrets : "reads secrets" role=identityNodes:
- Image registry, storage
- Users, client
- CI and CD: Delivery pipeline
- Source repository, step
- Build and test, step
- Cluster: Managed cluster (Managed control plane)
- Ingress, gateway
- Web service, service
- API service, service
- Admin service, service
- Configuration, storage
- Secrets, secret
- Observability: Monitoring
- Monitoring service, service
Overlays:
- Scale group overlay: Node pool, around Web service, API service, Admin service
Connections:
- Source repository to Build and test
- Build and test to Image registry: push image
- Image registry to Web service (dotted)
- Users to Ingress: HTTPS
- Ingress to Web service
- Ingress to API service
- Ingress to Admin service
- API service to Configuration: reads
- Web service to Monitoring service (dotted): metrics
- API service to Secrets (identity line): reads secrets
| Slot | Default | What goes there |
|---|---|---|
users | Users | Who sends traffic to the platform from outside. |
pipeline | Delivery pipeline | The name of the group that builds and publishes images. |
repository | Source repository | Where the application code lives. |
build | Build and test | The pipeline stage that builds an image and runs its tests. |
registry | Image registry | The store that holds the built container images. |
cluster | Managed cluster | The name of the container cluster that runs the services. |
cluster-detail | Managed control plane | A short fact about the cluster, such as who runs its control plane or its size. |
ingress | Ingress | The entry point that routes outside traffic to the services. |
node-pool | Node pool | The name of the redundant set of machines the services run on. |
web | Web service | The first service behind the ingress, for example the user interface. |
api | API service | The middle service, which reads configuration and secrets. |
admin | Admin service | The third service behind the ingress. |
config | Configuration | The settings the services read when they start. |
secrets | Secrets | The credentials and keys the services read. |
observability | Monitoring | The name of the group that watches the cluster. |
monitor | Monitoring service | The service that collects metrics and raises alerts. |
Keep. Keep the order from pipeline to registry to cluster to monitoring, the three services as the members of the node pool overlay, and direction=down. Keep a label on both the push edge and the traffic edge, or on neither, so the registry stays level with the first service. Keep the dotted edges: the image pull and the metrics are asynchronous.
Rename. Rename the services, the cluster, and the pipeline stages through the slots. Add a service by adding a node, an edge from the ingress, and its id in the overlay members. The pull and metrics edges each stand for every service. Swap the generic icons for a provider's with the flavour you pick.
Split. Past about five services, draw the cluster alone and show one service of it here. Draw the data stores, the network, and each environment in their own diagrams.
Queue and worker pool
A web API puts jobs on a queue, a pool of workers takes them and writes results to a database, failed jobs go to a dead-letter queue, and an alarm watches the queue depth.
When to use it. Start here when the request is background work: a request is accepted quickly and the slow part runs later on workers that can scale out. It shows the hand-off through a queue, the redundant pool, the retry path, and the alarm that tells you the workers fall behind. Start from the event-driven pipeline pattern instead when many independent consumers react to the same event.
Id and flavours. queue-worker, drawn below in its neutral flavour. It also comes in: AWS.
flow "Queue and worker pool" direction=down desc="A client sends a request to a web API, which puts a job on a queue instead of doing the work itself. A pool of three workers takes jobs from the queue and writes the results to a database. A job that still fails after its retries goes to a dead-letter queue, and an alarm watches the queue depth."
node client client "Client app" sub="Web or mobile"node api service "Web API" sub="Accepts requests"
group messaging "Messaging" type=messaging { node queue queue "Job queue" sub="Holds pending jobs"}
node w1 service "Worker 1"node w2 service "Worker 2"node w3 service "Worker 3"node alarm service "Queue depth alarm" sub="Watches queue depth" icon=lucide:bell-ring
overlay pool "Workers" type=scale-group members=w1,w2,w3
group data "Results" type=data-tier { node db database "Results database" sub="Managed database"}
node dlq queue "Dead-letter queue" sub="Failed jobs" tone=red
client -> api : "POST /jobs"api ..> queue : "enqueue job" role=asyncqueue ..> w1queue ..> w2queue ..> w3queue ..> alarmw2 -> db : "write result" role=dataw3 ..> dlq : "after 3 tries"Nodes:
- Client app (Web or mobile), client
- Web API (Accepts requests), service
- Worker 1, service
- Worker 2, service
- Worker 3, service
- Queue depth alarm (Watches queue depth), service
- Dead-letter queue (Failed jobs), queue
- Messaging: Messaging
- Job queue (Holds pending jobs), queue
- Data tier: Results
- Results database (Managed database), database
Overlays:
- Scale group overlay: Workers, around Worker 1, Worker 2, Worker 3
Connections:
- Client app to Web API: POST /jobs
- Web API to Job queue (dotted, asynchronous line): enqueue job
- Job queue to Worker 1 (dotted)
- Job queue to Worker 2 (dotted)
- Job queue to Worker 3 (dotted)
- Job queue to Queue depth alarm (dotted)
- Worker 2 to Results database (stored data line): write result
- Worker 3 to Dead-letter queue (dotted): after 3 tries
| Slot | Default | What goes there |
|---|---|---|
client | Client app | The browser, mobile app, or program that submits work. |
submit | POST /jobs | The request the client sends to submit a job. |
api | Web API | The service that accepts a request and enqueues a job. |
messaging | Messaging | The name of the group that holds the job queue. |
queue | Job queue | The queue that holds jobs until a worker takes one. |
pool | Workers | The name of the redundant set of workers. Keep it short, about eight characters, so its tab stays clear of the first arrow. |
worker | Worker | What a worker is called; a number follows it. |
data | Results | The name of the group that holds the database. |
database | Results database | The database that keeps what the workers produce. |
dlq | Dead-letter queue | The queue that keeps jobs that failed after every retry. |
retries | 3 | How many tries a job gets before it moves to the dead-letter queue, as a number. |
alarm | Queue depth alarm | The alarm that fires when too many jobs wait in the queue. |
Keep. Keep the overlay around the workers, and the one edge from the pool to the database and the one to the dead-letter queue, because each stands for every worker. Keep the edge from the queue to the alarm without a label, because a label pushes the alarm one row below the pool. Keep the pool name short, because a long name makes a tab wide enough to cover the first arrow. Keep direction=down.
Rename. Rename the services, the queue, and the stores through the slots, and set the retry count in the retries slot. Change the number of workers by adding a node, a dotted edge from the queue, and the member id in the overlay, in all three places. Pick the aws flavour to swap the generic glyphs for AWS icons.
Split. Past about five workers, keep three nodes and say how many more in the pool name. Draw the scaling rules and what the alarm triggers, such as paging or adding workers, in a second diagram.
Event-driven pipeline
Several services publish events to an event bus, three independent consumers react to them, and events that keep failing go to a dead-letter queue.
When to use it. Start here when the request is publish and subscribe: services that emit events without knowing who listens, and consumers that work on their own. It shows the fan-out from one bus, the asynchronous links, and where failed events end up. Start from the queue and worker pattern instead when one service hands a job to a pool of workers.
Id and flavours. event-driven-pipeline, drawn below in its neutral flavour. It also comes in: Google Cloud.
flow "Event-driven pipeline" direction=down desc="Three services publish events to an event bus. Three independent consumers react: one filters the events and archives them to storage, one processes them into a database, and one sends notifications. Events that keep failing go to a dead-letter queue. Every link out of a producer or the bus is asynchronous."
group producers "Producers" type=app-tier { node orders service "Orders service" node payments service "Payments service" node inventory service "Inventory service"}
group messaging "Messaging" type=messaging { node bus queue "Event bus" sub="One subscription per consumer"}
group consumers "Consumers" type=app-tier { node archiver function "Archive filter" sub="Matches and archives" node processor function "Event processor" sub="Transforms and stores" node notifier function "Notifier" sub="Sends alerts"}
node dlq queue "Dead-letter queue" sub="Failed events" tone=red
group data "Data stores" type=data-tier { node archive storage "Event archive" sub="Object storage" node db database "Events database" sub="Managed database"}
group outside "Third party" type=external-systems { node mail external "Email provider" icon=lucide:mail}
orders ..> buspayments ..> businventory ..> busbus ..> archiver : "matching events"bus ..> processor : "all events"bus ..> notifier : "alerts"bus ..> dlq : "after retries"archiver -> archive : "archive"processor -> db : "write" role=datanotifier ..> mail : "send" role=asyncNodes:
- Dead-letter queue (Failed events), queue
- Application tier: Producers
- Orders service, service
- Payments service, service
- Inventory service, service
- Messaging: Messaging
- Event bus (One subscription per consumer), queue
- Application tier: Consumers
- Archive filter (Matches and archives), function
- Event processor (Transforms and stores), function
- Notifier (Sends alerts), function
- Data tier: Data stores
- Event archive (Object storage), storage
- Events database (Managed database), database
- External systems: Third party
- Email provider, external
Connections:
- Orders service to Event bus (dotted)
- Payments service to Event bus (dotted)
- Inventory service to Event bus (dotted)
- Event bus to Archive filter (dotted): matching events
- Event bus to Event processor (dotted): all events
- Event bus to Notifier (dotted): alerts
- Event bus to Dead-letter queue (dotted): after retries
- Archive filter to Event archive: archive
- Event processor to Events database (stored data line): write
- Notifier to Email provider (dotted, asynchronous line): send
| Slot | Default | What goes there |
|---|---|---|
producers | Producers | The name of the group that holds the services that publish events. |
producer-1 | Orders service | The first service that publishes events. |
producer-2 | Payments service | The second service that publishes events. |
producer-3 | Inventory service | The third service that publishes events. |
messaging | Messaging | The name of the group that holds the event bus. |
bus | Event bus | The topic or bus that receives every event and hands it to each consumer. |
dlq | Dead-letter queue | The queue that keeps events a consumer could not handle after its retries. |
consumers | Consumers | The name of the group that holds the independent consumers. |
archiver | Archive filter | The consumer that keeps only matching events and archives them. |
processor | Event processor | The consumer that transforms every event and stores the result. |
notifier | Notifier | The consumer that turns some events into notifications. |
data | Data stores | The name of the group that holds the archive and the database. |
archive | Event archive | The object storage that keeps archived events. |
database | Events database | The database that keeps processed events. |
external | Third party | The name of the group for the service you call but do not run. |
email | Email provider | The outside service that delivers the notifications. |
Keep. Keep the dotted arrows from the producers to the bus and from the bus to each consumer, because they say the links are asynchronous, and keep the edge from the bus to the dead-letter queue. Keep the second line of the bus node about as long as it is, because a narrow node makes the three arrows into it bunch at one point.
Rename. Rename the services, the consumers, and the stores through the slots. Add a consumer by adding a node in the consumers group, a dotted edge from the bus, and an edge to what it writes. Pick the gcp flavour to swap the generic glyphs for Google Cloud icons.
Split. Past four producers or four consumers, draw the producers as one node and give each consumer its own diagram, or split by event type. Draw how dead letters are inspected and replayed in a second diagram.
Static site behind a CDN
Visitors resolve a name through DNS and reach a content delivery network with a certificate, which serves files from a storage bucket that a build pipeline updates and clears from the cache.
When to use it. Start here when the request is a static website, a documentation site, or a single-page app served from object storage through a CDN. It shows the name lookup, the HTTPS certificate, the origin, and how new files reach visitors. It does not show dynamic back ends or APIs behind the site.
Id and flavours. static-site-cdn, drawn below in its neutral flavour. It also comes in: AWS.
flow "Static site behind a CDN" direction=down desc="Visitors resolve the site name through DNS and reach a content delivery network that holds a TLS certificate. The network fetches files from a storage bucket that is its origin. A publishing pipeline builds the site, uploads the new files to the bucket, and clears the network cache."
group pipeline "Publishing pipeline" type=ci-cd { node repo step "Source repository" icon=lucide:git-branch node build step "Build site" icon=lucide:cog node publish step "Publish files" sub="Then clears the cache" icon=lucide:upload}
node visitors client "Visitors"
group edge "Public edge" type=edge { node dns service "DNS zone" icon=lucide:book-open node cert secret "TLS certificate" icon=lucide:badge-check node cdn gateway "Content delivery network" icon=lucide:satellite-dish}
group origin "Origin" type=storage-account { node bucket storage "Site bucket" sub="Static files"}
repo -> build : "on push"build -> publishvisitors -> dns : "resolve name"dns -> cdncert -> cdnpublish ..> cdncdn -> bucket : "fetch files"publish -> bucket : "upload" role=dataNodes:
- Visitors, client
- CI and CD: Publishing pipeline
- Source repository, step
- Build site, step
- Publish files (Then clears the cache), step
- Edge: Public edge
- DNS zone, service
- TLS certificate, secret
- Content delivery network, gateway
- Storage: Origin
- Site bucket (Static files), storage
Connections:
- Source repository to Build site: on push
- Build site to Publish files
- Visitors to DNS zone: resolve name
- DNS zone to Content delivery network
- TLS certificate to Content delivery network
- Publish files to Content delivery network (dotted)
- Content delivery network to Site bucket: fetch files
- Publish files to Site bucket (stored data line): upload
| Slot | Default | What goes there |
|---|---|---|
visitors | Visitors | Who opens the site: browsers, readers, or customers. |
edge | Public edge | The name of the group that faces visitors. |
dns | DNS zone | The service that turns the site name into the address of the delivery network. |
cert | TLS certificate | The certificate that lets the delivery network answer over HTTPS. |
cdn | Content delivery network | The cache that serves the files from locations near visitors. |
origin | Origin | The name of the group that holds the source of the files. |
bucket | Site bucket | The object storage that holds the built site. |
bucket-detail | Static files | A short second line for the bucket, such as what it holds. |
pipeline | Publishing pipeline | The name of the group that builds and publishes the site. |
repository | Source repository | Where the site source lives. |
build | Build site | The step that turns the source into static files. |
publish | Publish files | The step that uploads the files and clears the cache. |
publish-detail | Then clears the cache | A short second line that says what happens after the upload. |
Keep. Keep the visitor path as the right column and the pipeline as the left column, the dotted edge from the publish step to the delivery network, and direction=down. Keep one label on each of the two chains that lead to the middle row, so the publish step stays level with the delivery network.
Rename. Rename the services, the bucket, and the pipeline steps through the slots. Add a step to the pipeline by adding a node and an edge before the publish step, and label no more than one edge in the chain. Swap the generic icons for a provider's with the flavour you pick.
Split. Add an API or a function behind the delivery network in a second diagram, and keep this one for the static path. Draw several sites, regions, or environments one per diagram, and show the extra origin here only when it changes how visitors are served.
Data lake and analytics
Sources feed an ingestion stage into a raw zone, a processing stage cleans the data into a curated zone, and a warehouse serves analysts through dashboards, with a catalog that describes the datasets.
When to use it. Start here when the request is a data platform: where data comes from, how it is landed and cleaned, and how people query it. It shows the raw and curated zones as separate stores. It does not show orchestration, access control, or machine learning workloads.
Id and flavours. data-lake-analytics, drawn below in its neutral flavour. It also comes in: Google Cloud.
flow "Data lake and analytics" direction=down desc="An application database, event streams, and file drops feed an ingestion stage that lands data in a raw zone. A processing stage cleans it into a curated zone, a warehouse loads the curated data, and analysts read it through dashboards. A data catalog describes the curated datasets."
group sources "Data sources" type=external-systems { node appdb database "Application database" node events queue "Event streams" icon=lucide:radio node files storage "File drops" icon=lucide:folder}
node ingest service "Ingestion" icon=lucide:download
group lake "Data lake" type=resource-group { group raw "Raw zone" type=storage-account sub="Data as received" { node rawstore storage "Raw files" }
node process service "Clean and transform" icon=lucide:funnel
group curated "Curated zone" type=storage-account sub="Cleaned and typed" { node curatedstore storage "Curated tables" }}
node warehouse database "Warehouse" icon=lucide:warehousenode dashboards service "Dashboards" icon=lucide:gaugenode analysts person "Analysts"node catalog service "Data catalog" icon=lucide:book-open
appdb -> ingestevents -> ingestfiles -> ingestingest -> rawstorerawstore -> processprocess -> curatedstorecuratedstore -> warehousewarehouse -> dashboardsdashboards -> analystscuratedstore ..> catalog : "metadata"Nodes:
- Ingestion, service
- Warehouse, database
- Dashboards, service
- Analysts, person
- Data catalog, service
- External systems: Data sources
- Application database, database
- Event streams, queue
- File drops, storage
- Resource group: Data lake
- Clean and transform, service
- Storage: Raw zone (Data as received)
- Raw files, storage
- Storage: Curated zone (Cleaned and typed)
- Curated tables, storage
Connections:
- Application database to Ingestion
- Event streams to Ingestion
- File drops to Ingestion
- Ingestion to Raw files
- Raw files to Clean and transform
- Clean and transform to Curated tables
- Curated tables to Warehouse
- Warehouse to Dashboards
- Dashboards to Analysts
- Curated tables to Data catalog (dotted): metadata
| Slot | Default | What goes there |
|---|---|---|
sources | Data sources | The name of the group that holds everything the data comes from. |
app-db | Application database | The operational database that the business applications write to. |
events | Event streams | The continuous feeds of events, such as clicks, sensor readings, or messages. |
files | File drops | Files that partners or other teams deliver on a schedule. |
ingest | Ingestion | The stage that reads every source and lands the data unchanged. |
lake | Data lake | The name of the group that holds the storage zones and the processing between them. |
raw-zone | Raw zone | The name of the zone that keeps the data exactly as it arrived. |
raw-detail | Data as received | A short line under the raw zone name that says what the zone guarantees. |
raw-store | Raw files | The storage that holds the raw data. |
process | Clean and transform | The stage that validates, cleans, and reshapes the raw data. |
curated-zone | Curated zone | The name of the zone that holds trusted, ready-to-use data. |
curated-detail | Cleaned and typed | A short line under the curated zone name that says what the zone guarantees. |
curated-store | Curated tables | The storage that holds the curated data. |
warehouse | Warehouse | The analytical database that answers queries over the curated data. |
dashboards | Dashboards | The reports and charts that analysts open. |
analysts | Analysts | The people who read the data and ask questions of it. |
catalog | Data catalog | The register that describes each dataset: its owner, its meaning, and its schema. |
Keep. Keep the single column from ingestion to analysts, the two zones as groups inside the data lake group, and direction=down. Keep the catalog as a leaf with one dotted edge from the curated store, because an edge into the raw zone pulls the whole column out of line.
Rename. Rename the sources, the stages, the zones, and the stores through the slots. Add a source by adding a node in the sources group and an edge to the ingestion stage. Swap the generic icons for a provider's with the flavour you pick.
Split. Past about five sources, group them by kind and show one node per kind. Draw orchestration, access control, and streaming paths in their own diagrams, and add a third zone only if the picture stays one column.
Multi-region failover
A global routing service sends users to a primary region and fails over to a standby region, which holds replicated data and an identical stack.
When to use it. Start here when the request is to survive the loss of a whole region: it shows which region serves normally, what switches, and what is copied. Use it to discuss recovery time, because the replication links are the part that decides how much data a failover loses.
Id and flavours. multi-region-failover, drawn below in its neutral flavour. It also comes in: AWS.
flow "Multi-region failover" direction=down desc="Users reach a global routing service that sends traffic to the primary region and switches to the standby region when health checks fail. Each region runs a load balancer, application servers, and a database, and the primary region replicates its database to the standby."
node users client "Users"node routing gateway "Global routing" sub="Health-checked" icon=lucide:globe
group primary "Primary region" type=region sub="Active" { node lb-p gateway "Load balancer" node app-p service "Application servers" node db-p database "Primary database"}
group standby "Standby region" type=region sub="Passive" { node lb-s gateway "Load balancer" node app-s service "Application servers" node db-s database "Replica database"}
users -> routingrouting -> lb-p : "normal traffic"routing --> lb-s : "failover"lb-p -> app-plb-s -> app-sapp-p -> db-papp-s -> db-sdb-p ..> db-s : "replication"Nodes:
- Users, client
- Global routing (Health-checked), gateway
- Region: Primary region (Active)
- Load balancer, gateway
- Application servers, service
- Primary database, database
- Region: Standby region (Passive)
- Load balancer, gateway
- Application servers, service
- Replica database, database
Connections:
- Users to Global routing
- Global routing to Load balancer: normal traffic
- Global routing to Load balancer (dashed): failover
- Load balancer to Application servers
- Load balancer to Application servers
- Application servers to Primary database
- Application servers to Replica database
- Primary database to Replica database (dotted): replication
| Slot | Default | What goes there |
|---|---|---|
users | Users | Who uses the application. |
routing | Global routing | The service that chooses a region by health, such as DNS routing or a global load balancer. |
routing-sub | Health-checked | A short second line for the routing service. |
primary | Primary region | The name of the region that serves traffic normally. |
primary-sub | Active | A short second line for the primary region. |
standby | Standby region | The name of the region that takes over. |
standby-sub | Passive | A short second line for the standby region. |
balancer | Load balancer | The balancer in front of the servers of each region. |
servers | Application servers | The servers of each region. |
database | Primary database | The database in the primary region, which takes the writes. |
replica | Replica database | The database in the standby region, which follows the primary. |
replication | replication | What the dotted link between the two databases carries. |
Keep. Keep both regions with the same three nodes in the same order, the dashed failover link beside the solid normal link, and the dotted replication link between the two databases, which stay side by side.
Rename. Rename the regions and the services through the slots. Keep one replicated pair in view: a second pair draws its link across the first, so put it in a second diagram.
Split. Past about five nodes in a region, draw one region alone in its own diagram and keep a single node per region here. Draw the failover procedure as a sequence diagram instead.
CI/CD delivery pipeline with an approval gate
A commit is built, tested, scanned, and stored as an image, then deployed to staging, held for an approval, and released to production.
When to use it. Start here for a continuous integration and delivery flow with a manual gate before production. It shows which steps belong to the pipeline, which environments hold running code, and where people are notified. Draw a different diagram for a pipeline that releases straight to production or for one with many parallel jobs.
Id and flavours. ci-cd-delivery-pipeline, drawn below in its neutral flavour. It also comes in: Azure.
flow "CI/CD delivery pipeline with an approval gate" direction=down desc="A commit is built, tested, and scanned, and the image is stored in a registry. It is deployed to staging, held until a reviewer approves, and then deployed to production. The security scan and the approval request notify people."
group pipeline "Delivery pipeline" type=ci-cd { node repo storage "Source repository" sub="Commit to the main branch" icon=lucide:git-branch node build step "Build" sub="Compile and package" icon=lucide:cog node test step "Test" sub="Unit and integration" icon=lucide:clipboard-check node scan step "Security scan" sub="Code and dependencies" icon=lucide:shield-check}
node registry storage "Image registry" sub="Versioned images" icon=lucide:packagenode alerts external "Security channel" icon=lucide:bell
group staging "Staging" type=environment { node staging-deploy step "Deploy to staging" icon=lucide:rocket node staging-app service "Staging app"}
node approval step "Release approval" sub="A reviewer signs off" icon=lucide:stampnode reviewers external "Release channel" icon=lucide:bell
group production "Production" type=environment { node prod-deploy step "Deploy to production" icon=lucide:rocket node prod-app service "Production app"}
repo -> buildbuild -> testtest -> scanscan -> registry : "image"registry -> staging-deploystaging-deploy -> staging-appstaging-app -> approvalapproval -> prod-deploy : "approved"prod-deploy -> prod-app
scan ..> alerts : "findings"approval ..> reviewers : "request"Nodes:
- Image registry (Versioned images), storage
- Security channel, external
- Release approval (A reviewer signs off), step
- Release channel, external
- CI and CD: Delivery pipeline
- Source repository (Commit to the main branch), storage
- Build (Compile and package), step
- Test (Unit and integration), step
- Security scan (Code and dependencies), step
- Environment: Staging
- Deploy to staging, step
- Staging app, service
- Environment: Production
- Deploy to production, step
- Production app, service
Connections:
- Source repository to Build
- Build to Test
- Test to Security scan
- Security scan to Image registry: image
- Image registry to Deploy to staging
- Deploy to staging to Staging app
- Staging app to Release approval
- Release approval to Deploy to production: approved
- Deploy to production to Production app
- Security scan to Security channel (dotted): findings
- Release approval to Release channel (dotted): request
| Slot | Default | What goes there |
|---|---|---|
pipeline | Delivery pipeline | The name of the pipeline that builds and checks the code. |
source | Source repository | Where the code lives and where a change starts. |
source-note | Commit to the main branch | A short second line for the repository: what starts the pipeline. |
build | Build | The step that compiles the code and packages it. |
build-note | Compile and package | A short second line for the build step. |
test | Test | The step that runs the automated tests. |
test-note | Unit and integration | A short second line for the test step. |
scan | Security scan | The step that checks the code and its dependencies for known problems. |
scan-note | Code and dependencies | A short second line for the scan step. |
registry | Image registry | Where the built image is stored and versioned. |
registry-note | Versioned images | A short second line for the registry. |
alerts | Security channel | Who hears about scan findings: a chat channel or a mailing list. |
staging | Staging | The name of the pre-production environment. |
deploy-staging | Deploy to staging | The step that puts the image on the staging environment. |
staging-app | Staging app | The application as it runs in staging. |
approval | Release approval | The manual gate between staging and production. |
approval-note | A reviewer signs off | A short second line for the approval: who decides. |
reviewers | Release channel | Who is asked to approve: a chat channel or a mailing list. |
production | Production | The name of the live environment. |
deploy-production | Deploy to production | The step that puts the approved image on production. |
production-app | Production app | The application as it runs in production. |
Keep. Keep the order of the steps, the group around the pipeline steps, one group for each environment, and the approval node outside both environments. Keep the dotted arrows for notifications, because they mark a message that nothing waits for.
Rename. Rename the steps, environments, and channels through the slots. Add a step such as a lint or a performance test by adding a node and an edge between its neighbours, inside the pipeline group. Add an environment by copying the staging group and its two edges.
Split. Past about four steps in the pipeline or a third environment, draw the pipeline alone in one diagram and the environments with their rollout in another. Draw rollback and failure paths in a second diagram.
Sign-in with an authorization code and proof key
A browser app signs a person in through an identity provider with the authorization code flow and a proof key, calls a backend API with the access token, and refreshes it when it expires.
When to use it. Start here for the sign-in of a single-page or mobile app that has no client secret and calls its own backend API. It shows where the proof key is made and checked, what the API verifies, and how an expired token turns into a refresh and a retry. Draw a different diagram for a server-rendered app that keeps a client secret, or for machine-to-machine access with no person.
Id and flavours. sign-in-token-flow, drawn below in its neutral flavour. It has no provider flavour.
sequence "Sign-in with an authorization code and proof key" desc="A browser app sends a person to an identity provider with a proof key challenge, receives a code, and exchanges it for tokens. It calls a backend API with the access token and uses the refresh token when the access token expires."
actor user person "Person" sub="At the browser"actor idp identity "Identity provider" sub="Issues the tokens"actor app client "Browser app" sub="Public client"actor api service "Backend API" sub="Checks every token"
user -> app : "Choose sign in"app -> app : "Create verifier and challenge"app -> idp : "Redirect with challenge"idp -> user : "Show sign-in page"user -> idp : "Enter credentials"idp --> app : "Redirect back with code"app -> idp : "Send code and verifier"note over idp : "Hashes the verifier and compares it to the challenge"idp --> app : "Access and refresh tokens"
app -> api : "Request with access token"note over api : "Checks the signature and the expiry"
alt "token is valid" { api --> app : "Data"} else "token has expired" { api --> app : "401 token expired" app -> idp : "Send refresh token" idp --> app : "New access token" app -> api : "Retry with new token" api --> app : "Data"}Participants:
- Person (At the browser), person
- Identity provider (Issues the tokens), identity
- Browser app (Public client), client
- Backend API (Checks every token), service
Steps:
-
- Person to Browser app: Choose sign in
-
- Browser app to Browser app: Create verifier and challenge
-
- Browser app to Identity provider: Redirect with challenge
-
- Identity provider to Person: Show sign-in page
-
- Person to Identity provider: Enter credentials
-
- Identity provider to Browser app (dashed): Redirect back with code
-
- Browser app to Identity provider: Send code and verifier
- Note over Identity provider: Hashes the verifier and compares it to the challenge
-
- Identity provider to Browser app (dashed): Access and refresh tokens
-
- Browser app to Backend API: Request with access token
- Note over Backend API: Checks the signature and the expiry
- alt: token is valid
- 10. Backend API to Browser app (dashed): Data
- else: token has expired
- 11. Backend API to Browser app (dashed): 401 token expired
-
- Browser app to Identity provider: Send refresh token
-
- Identity provider to Browser app (dashed): New access token
-
- Browser app to Backend API: Retry with new token
-
- Backend API to Browser app (dashed): Data
| Slot | Default | What goes there |
|---|---|---|
person | Person | Who signs in. |
person-note | At the browser | A short second line for the person. |
identity-provider | Identity provider | The service that signs the person in and issues tokens. |
identity-provider-note | Issues the tokens | A short second line for the identity provider. |
app | Browser app | The app that has no client secret and starts the sign-in. |
app-note | Public client | A short second line for the app. |
api | Backend API | The service that accepts the access token and returns data. |
api-note | Checks every token | A short second line for the API. |
api-data | Data | What the API returns when the token is accepted. |
Keep. Keep the order of the messages: the verifier and challenge are made before the redirect, the code travels back by redirect, and only the exchange carries the verifier. Keep the alt block, because the expired branch is the part readers look for, and keep the retry after the refresh.
Rename. Rename the four participants, their second lines, and the data the API returns through the slots. To add a scope or an audience, change the message text by hand. Add a second API as a fifth participant to the right of the first, with its own request and response.
Split. Draw sign-out, a revoked refresh token, and a first sign-in with consent as separate diagrams. Past five participants, draw the backend services on their own in a second diagram and keep one participant for them here.
Multi-tenant application schema
Organizations own projects and an audit log, and users join organizations through a memberships table that carries a role.
When to use it. Start here for a software-as-a-service product where several customers share one database and every row belongs to an organization. It shows the join table for a many-to-many link with a role, the foreign key that scopes each row to its tenant, and an append-only log. Draw a different schema when each customer has its own database or when users belong to no organization.
Id and flavours. database-schema-starter, drawn below in its neutral flavour. It has no provider flavour.
schema "Multi-tenant application schema" desc="Organizations own projects and an audit log. Users join organizations through memberships, a join table that stores each user's role. Every tenant table carries the organization id."
table organizations "Organizations" sub="The tenant that owns data" { id uuid pk name text slug text unique note="Used in addresses" plan text created_at timestamptz}
table users "Users" sub="People who sign in" { id uuid pk email text unique display_name text created_at timestamptz disabled_at timestamptz nullable note="Set when the account is closed"}
table memberships "Memberships" sub="Joins users to organizations" { id uuid pk organization_id uuid fk user_id uuid fk role text note="owner, admin, or member" created_at timestamptz}
table projects "Projects" sub="Work owned by one organization" { id uuid pk organization_id uuid fk name text description text nullable archived_at timestamptz nullable created_at timestamptz}
table audit_log "Audit log" sub="Append-only record of changes" { id uuid pk organization_id uuid fk actor_id uuid nullable note="No foreign key, so history outlives the user" action text target_type text target_id uuid nullable created_at timestamptz}
memberships.organization_id -> organizations.id : "belongs to"memberships.user_id -> users.id : "held by"projects.organization_id -> organizations.id : "owned by"audit_log.organization_id -> organizations.id : "scoped to"Tables:
- Organizations
- id uuid (primary key)
- name text
- slug text (unique): Used in addresses
- plan text
- created_at timestamptz
- Users
- id uuid (primary key)
- email text (unique)
- display_name text
- created_at timestamptz
- disabled_at timestamptz (nullable): Set when the account is closed
- Memberships
- id uuid (primary key)
- organization_id uuid (foreign key)
- user_id uuid (foreign key)
- role text: owner, admin, or member
- created_at timestamptz
- Projects
- id uuid (primary key)
- organization_id uuid (foreign key)
- name text
- description text (nullable)
- archived_at timestamptz (nullable)
- created_at timestamptz
- Audit log
- id uuid (primary key)
- organization_id uuid (foreign key)
- actor_id uuid (nullable): No foreign key, so history outlives the user
- action text
- target_type text
- target_id uuid (nullable)
- created_at timestamptz
Relations:
- memberships.organization_id references organizations.id (many to one): belongs to
- memberships.user_id references users.id (many to one): held by
- projects.organization_id references organizations.id (many to one): owned by
- audit_log.organization_id references organizations.id (many to one): scoped to
| Slot | Default | What goes there |
|---|---|---|
organizations | Organizations | The label of the tenant table: the customer, team, or account that owns data. |
organizations-note | The tenant that owns data | A short second line for the tenant table. |
users | Users | The label of the table of people who sign in. |
users-note | People who sign in | A short second line for the users table. |
memberships | Memberships | The label of the join table between users and tenants. |
memberships-note | Joins users to organizations | A short second line for the join table; say what the role controls. |
projects | Projects | The label of the main business table that belongs to a tenant, such as projects, workspaces, or sites. |
projects-note | Work owned by one organization | A short second line for that business table. |
audit-log | Audit log | The label of the append-only table that records who did what. |
audit-log-note | Append-only record of changes | A short second line for the audit table. |
Keep. Keep the organization_id foreign key on every table that holds tenant data, the pair of foreign keys in the join table, and the nullable flags on columns that are empty for a good reason. Keep the audit table append-only, and keep actor_id without a foreign key so the history survives when a user is deleted.
Rename. Rename the tables and their second lines through the slots. To rename a column, change it in the source by hand, in the table and in every relation that names it. Add business tables the same way projects is drawn: an id, an organization_id foreign key, and a relation to organizations.
Split. Past six tables, draw each area on its own: tenancy and access in one diagram, the business tables in another, and billing in a third. Keep organizations in each one so the foreign keys still have a parent.
5. Adapting a pattern
| Do | Do not |
|---|---|
| Rename through slots first, then edit the source for what slots cannot change | Rewrite the source from scratch and lose the checked groups and icons |
| Keep the group tree: cloud, network, zones, tiers, and subnets carry the meaning | Flatten groups to save space, or colour them by hand |
| Keep a replicated pair, such as a primary and a replica, as one pair in a diagram | Add a second replicated pair in the same diagram; draw a second diagram instead |
Split a pattern at the line its Split note names | Raise the node count by shrinking text |
Give every node the icon of its product, found with search_icons | Invent an icon name; validate_diagram rejects it with the nearest valid one |
Run validate_diagram after every edit and fix each warning | Ignore a warning because the picture looks fine |
A pattern is a starting point, not a template to fill in blindly. If the request names a service that the pattern has no node for, add a node for it with its own icon. If the pattern has a node that the request does not mention, remove it.
See also
| Doc | Why |
|---|---|
| Cloud architecture | Group types, provider icons, and the icon pages behind every pattern |
| Writing diagrams with an agent | The validate-and-fix loop an agent follows |
| Writing diagrams | The whole language, kind by kind |
| Tool reference | find_pattern, get_pattern, draw_from_pattern, and the other tools |
| Language reference | Every group type, overlay option, arrow, and diagnostic code |