https://mermaid.js.org/
https://mermaid.live/
---
title: Sequence Diagram example
---
sequenceDiagram
autonumber
participant cl AS Client
participant avs AS API VCL Service (Edge)
participant dag AS MyApp API Gateway (Edge)
participant ag AS Auth Gateway (Control Plane)
participant er AS Entity Registry (Control Plane)
participant da AS MyApp API (Control Plane)
%% below we put a note across MULTIPLE actors
note over ag,da: here is my note
cl->>+avs: Initial Request
avs->>+dag: Proxy Request
alt JWT Expired
dag<<-->>ag: Exchange API Key for JWT
end
alt EntityID Not Found
dag<<-->>er: Look up EntityId associated with CustomerId
end
dag->>+da: Proxy request to Origin
da->>-dag: Origin response
dag->>-avs: Respond to API VCL Service
avs->>-cl: Respond to client
Note
The yellow box is rendered by use of +
and -
before the target names.
e.g. cl->>+avs: Initial Request
finishes with avs->>-cl: Respond to client
Tip
Use autonumber
to automatically number lines!
Tip
You can comment out lines using %%
.
---
title: Instant Onboarding - API Integration
---
sequenceDiagram
participant ui AS MyApp UI
participant dag AS MyApp API Gateway (Edge)
participant da AS MyApp API (Control Plane)
participant rd AS Redis (Cache)
participant dns AS DNS (Resolution)
ui->>dag: Initial request
note right of ui: GET /internal/domains/v1/tools/lookup/{fqdn}
dag->>+da: Proxy request to the API
da->>rd: Lookup domain in cache
alt No cache record Found
note right of da: Query CNAME and SOA records
da<<-->>dns: DNS lookup
end
da->>-ui: JSON response
---
title: Flowchart example
---
flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
Note
TB
- Top to bottom
TD
- Top-down/ same as top to bottom
BT
- Bottom to top
RL
- Right to left
LR
- Left to right
Tip
For a flowchart, you can use either flowchart
or graph
(see below for example).
---
title: DNS CNAME/SOA Lookup Flow
---
graph TD
A[Check for CNAME] --> B{CNAME Exists?};
B -- Yes --> C[Check SOA of CNAME];
B -- No --> D[Check SOA of APEX];
C --> E{RNAME and MNAME match?};
E -- Yes --> F[Return Hostname];
E -- No --> G{Known RNAME match?};
G -- Yes --> H[Return RNAME Hostname];
G -- No --> I{Known MNAME match?};
I -- Yes --> J[Return MNAME Hostname];
I -- No --> D;
D --> K{RNAME and MNAME match?};
K -- Yes --> L[Return Hostname];
K -- No --> M{Known RNAME match?};
M -- Yes --> N[Return RNAME Hostname];
M -- No --> O{Known MNAME match?};
O -- Yes --> P[Return MNAME Hostname];
O -- No --> Q[Return empty string];
---
title: Current TLS API Overview
---
graph LR
A@{ shape: circle, label: "Customer" } --> B(TLS API);
B --> C[[Spotless]];
C --> D{API Routing};
D --> E@{ shape: braces, label: "Activations" };
D --> F@{ shape: braces, label: "Bulk" };
D --> G@{ shape: braces, label: "Certificates" };
D --> H@{ shape: braces, label: "Configuration" };
D --> I@{ shape: braces, label: "Domains" };
D --> J@{ shape: braces, label: "Mutual Authentication" };
D --> K@{ shape: braces, label: "Private Keys" };
D --> L@{ shape: braces, label: "Subscriptions" };
L --> M{Certificate Authority CA};
M --> N[[Let's Encrypt]]
M --> O[[Certainly]]
M --> P[[GlobalSign]]
---
title: Class Diagram example
---
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}
---
title: State Diagram example
---
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
pie title Pie Chart example
"Dogs" : 386
"Cats" : 85
"Rats" : 15
---
title: API Request Flow
---
erDiagram
Customer ||--o{ TLSAPI : makes-requests-to
TLSAPI ||--o{ SpotlessBackendAPI : communicates-with
SpotlessBackendAPI {
string API_Routing
}
SpotlessBackendAPI ||--o{ Activations : routes-to
SpotlessBackendAPI ||--o{ Bulk : routes-to
SpotlessBackendAPI ||--o{ Certificates : routes-to
SpotlessBackendAPI ||--o{ Configurations : routes-to
SpotlessBackendAPI ||--o{ Domains : routes-to
SpotlessBackendAPI ||--o{ MutualAuthentication : routes-to
SpotlessBackendAPI ||--o{ PrivateKeys : routes-to
SpotlessBackendAPI ||--o{ Subscriptions : routes-to
Subscriptions ||--o{ CertificateAuthority : communicates-with
sequenceDiagram
participant Customer
participant Spotless
participant Ascerta
participant Queue
participant Worker
Customer->>Spotless: POST /tls/subscriptions
Spotless->>+Ascerta: https://<hostname>/internal/tls/v2/certs/...
Note right of Spotless: include callback url
par
Ascerta-->>Queue: send request
end
Note right of Ascerta: include callback url
Ascerta->>-Spotless: response
Spotless->>Customer: 201 Created
par
Worker<<->>Queue: get item
Worker->>Worker: do work
Worker->>Spotless: https://<hostname>/internal/tls/v2/certs/...
Note right of Spotless: the callback url
end
---
title: NSQ Infrastructure
---
graph LR
subgraph "Producer"
A[API] -- push to topic --> CP((topic: priority))
A -- push to topic --> CS((topic: standard))
A -- push to topic --> CSP((topic: special))
%% nsqd is the daemon that receives, queues, and distributes messages.
%% It acts as the message queue server.
CP & CS & CSP -- send message --> N[nsqd]
end
subgraph " "
%% nsqlookupd provides a directory service,
%% allowing consumers to find the nsqd instances that are producing messages
%%for a specific topic.
N -- register topic --> L[nsqlookupd]
end
subgraph "Consumers"
%% The consumers are trying to discover the nsqd instances
%% that are handling the specific topic they're interested in.
L <-- discover producers --> WP@{ shape: procs, label: "priority workers"}
L <-- discover producers --> WS@{ shape: procs, label: "standard workers"}
L <-- discover producers --> WSP@{ shape: procs, label: "special workers"}
WP & WS & WSP <-- retrieve message --> N
end
Below is an example of drawing boxes around specific parts of a system...
sequenceDiagram
participant EU as End User
participant RR as Recursive Resolver
participant Root as Root Servers
participant TLD as .com TLD Servers
participant DS as DNSimple Servers <br/> (IPs via Glue Records for nsX.example.com)
participant Cust as Customer
participant UI as Example UI
participant BE as Example Facade API Service
participant API as DNSimple API
participant DB as DNSimple Database
box LightBlue DNS Resolution Process (Query for www.customerdomain.com)
participant EU
participant RR
participant Root
participant TLD
end
box Pink Record Management Process (Customer updates DNS)
participant Cust
participant UI
participant BE
participant API
end
Note over EU, DS: Initial Setup Prerequisite (Not Shown):<br/>1. Vanity NS (nsX.example.com) configured in DNSimple.<br/>2. Glue Records created at example.com's registrar mapping nsX.example.com to DNSimple IPs.<br/>3. customerdomain.com's NS records point to nsX.example.com.
%% === DNS Resolution Flow ===
EU->>+RR: Resolve www.customerdomain.com?
RR->>+Root: Who handles .com?
Root-->>-RR: TLD Server IPs
RR->>+TLD: Who handles customerdomain.com?
TLD-->>-RR: NS: ns1.example.com @ DNSimple_IP1<br/>NS: ns2.example.com @ DNSimple_IP2<br/>(Info via Glue Records)
RR->>+DS: (Query sent to DNSimple_IP1) A record for www.customerdomain.com?
DS-->>-RR: A record is 192.0.2.100
RR-->>-EU: www.customerdomain.com is 192.0.2.100
EU->>(Web Server): Connect to 192.0.2.100
%% === Record Management Flow ===
Cust->>+UI: Login & Navigate to manage.example.com
Cust->>UI: Request Add/Update Record (e.g., TXT _foo="bar")
UI->>+BE: Submit Record Change Request (domain, type, name, content)
BE->>+API: Create/Update Record via DNSimple API<br/>(Zone: customerdomain.com, Record Details)
API->>+DB: Store/Update record data in DNSimple's database
Note over DB, DS: DNSimple internal propagation<br/>updates authoritative servers (DS).
DB-->>-API: Confirmation of data update
API-->>-BE: Success Response (e.g., Record ID)
BE-->>-UI: Operation Successful
UI-->>-Cust: Display success message / updated record list
Note right of DS: DNSimple Servers (DS)<br/>now serve the<br/>new/updated record<br/>for future queries.
Example of complex architecture with <br>
for line breaks:
graph TD
subgraph "User Interaction"
User[User/Customer]
end
subgraph "Example DNS Platform"
ManagementService[Example DNS Management Service API/UI]
CentralDB[("Central Database: stores zones with 'account' column for partition_id")]
subgraph "DNS Partition ..N"
PDNSN[PowerDNS Instance for PN <br><br> config: SELECT ... WHERE account='PN_id']
NS_PN_Set[NS Servers for P..N: <br><br> nsn1.your-dns.com nsn2.your-dns.com]
end
subgraph "DNS Partition 2"
PDNS2[PowerDNS Instance for P2 <br><br> config: SELECT ... WHERE account='P2_id']
NS_P2_Set[NS Servers for P2: <br><br> nsb1.your-dns.com nsb2.your-dns.com]
end
subgraph "DNS Partition 1"
PDNS1[PowerDNS Instance for P1 <br><br> config: SELECT ... WHERE account='P1_id']
NS_P1_Set[NS Servers for P1: <br><br> nsa1.your-dns.com nsa2.your-dns.com]
end
end
subgraph "External DNS Infrastructure"
Registrar[Domain Registrar]
UserCurrentDNS[User's Current/External DNS; for verification if needed]
end
User -- Manages Zones via --> ManagementService
ManagementService -- Reads/Writes Zone Config --> CentralDB
PDNS1 -- Reads Zone Data <br> (filtered for P1) --> CentralDB
PDNS2 -- Reads Zone Data <br> (filtered for P2) --> CentralDB
PDNSN -- Reads Zone Data <br> (filtered for PN) --> CentralDB
NS_P1_Set -- Served by --> PDNS1
NS_P2_Set -- Served by --> PDNS2
NS_PN_Set -- Served by --> PDNSN
User -- Creates Verification Record --> UserCurrentDNS
User -- Updates NS Records --> Registrar
ManagementService -- Verification Check --> UserCurrentDNS
style User fill:#aliceblue,stroke:#333,stroke-width:2px
style ManagementService fill:#lightcyan,stroke:#333,stroke-width:2px
style CentralDB fill:#honeydew,stroke:#333,stroke-width:2px
style PDNS1 fill:#mistyrose,stroke:#333,stroke-width:2px
style PDNS2 fill:#mistyrose,stroke:#333,stroke-width:2px
style PDNSN fill:#mistyrose,stroke:#333,stroke-width:2px
style NS_P1_Set fill:#lemonchiffon,stroke:#333,stroke-width:2px
style NS_P2_Set fill:#lemonchiffon,stroke:#333,stroke-width:2px
style NS_PN_Set fill:#lemonchiffon,stroke:#333,stroke-width:2px
style Registrar fill:#thistle,stroke:#333,stroke-width:2px
style UserCurrentDNS fill:#whitesmoke,stroke:#333,stroke-width:2px
Example of complex architecture using line colours and ~~~
to force a vertical rendering of the boxes within the "DNS Data Plan" subgraph:
flowchart TD
subgraph user["User Interaction"]
APIUser["User/Customer"]
DNSUser["User/Customer"]
end
subgraph DNSControl["DNS Control Plane"]
APIService["DNS API Service"]
controlPlaneDB[("Control Plane Database: stores zone mappings, entityID information, partition mapping")]
end
subgraph pdnsInstance ["PowerDNS instance"]
pdnsAPIN["PowerDNS Primary/Secondary"]
pdnsDBN[("PowerDNS Partition N MySQL Database: stores zones, records, etc")]
pdnsAPIN --> pdnsDBN
pdnsDBNReplica[("PowerDNS Partition N MySQL Replica DB: stores zones, records, etc")]
pdnsDBN -- Replication --> pdnsDBNReplica
Note["Note: PowerDNS will act as a primary/secondary for a given zone, depending on the configuration"]
end
subgraph DNSDataPlane ["DNS Data Plane"]
subgraph pdnsN["PDNS Partition..N"]
origin_refN["origin instance"]
control_refN["control instance"]
transfer_refN["transfer instance"]
end
subgraph pdns2["PDNS Partition 2"]
origin_ref2["origin instance"]
control_ref2["control instance"]
transfer_ref2["transfer instance"]
end
subgraph pdns1["PDNS Partition 1"]
origin_ref1["origin instance"]
control_ref1["control instance"]
transfer_ref1["transfer instance"]
end
%% Vertical Layout Hint (i.e. force a vertical rendering of the boxes)
pdns1 ~~~ pdns2
pdns2 ~~~ pdnsN
end
subgraph externalDNSProvider["External DNS Platform"]
end
subgraph EdgeDNS["DNS Edge"]
subgraph cacheN["Cache Node ..N"]
xdnsN["xdns (dns proxy): <br> Filters invalid packets"]
dnsdistN["dnsdist: authoritative DNS cache/proxy"]
end
subgraph cache2["Cache Node 2"]
xdns2["xdns (dns proxy): <br> Filters invalid packets"]
dnsdist2["dnsdist: authoritative DNS cache/proxy"]
end
subgraph cache1["Cache Node 1"]
xdns1["xdns (dns proxy): <br> Filters invalid packets"]
dnsdist1["dnsdist: authoritative DNS cache/proxy"]
end
xdns1 <-- valid query --> dnsdist1
dnsdist1 -- Cache misses <br> Xproxy encoded --> origin_ref1
xdns2 <-- valid query --> dnsdist2
dnsdist2 <-- Cache misses <br> Xproxy encoded ---> origin_ref2
xdnsN <-- valid query --> dnsdistN
dnsdistN <-- Cache misses <br> Xproxy encoded ---> origin_refN
end
%% Connections
APIUser -- Manages Zones via API --> APIService
APIUser -- Manages Zones on External DNS Provider --> externalDNSProvider
DNSUser -- Incoming DNS Queries --> EdgeDNS
APIService -- Route DNS request to PowerDNS control instance ---> control_refN
APIService -- Route DNS request to PowerDNS control instance ---> control_ref2
APIService -- Route DNS request to PowerDNS control instance ---> control_ref1
APIService -- Check DB for customer information and mapping--> controlPlaneDB
externalDNSProvider -- Replicates records via XFR --> transfer_ref1
externalDNSProvider -- Replicates records via XFR --> transfer_ref2
externalDNSProvider -- Replicates records via XFR --> transfer_refN
%% Links to PowerDNS Instance
origin_refN -- "is a" --> pdnsInstance
control_refN -- "is a" --> pdnsInstance
transfer_refN -- "is a" --> pdnsInstance
origin_ref1 -- "is a" --> pdnsInstance
control_ref1 -- "is a" --> pdnsInstance
transfer_ref1 -- "is a" --> pdnsInstance
origin_ref2 -- "is a" --> pdnsInstance
control_ref2 -- "is a" --> pdnsInstance
transfer_ref2 -- "is a" --> pdnsInstance
%% Links for origin instances
linkStyle 4 stroke:red,stroke-width:2px,color:grey;
linkStyle 5 stroke:red,stroke-width:2px,color:grey;
linkStyle 6 stroke:red,stroke-width:2px,color:grey;
linkStyle 7 stroke:red,stroke-width:2px,color:grey;
linkStyle 8 stroke:red,stroke-width:2px,color:grey;
linkStyle 9 stroke:red,stroke-width:2px,color:grey;
linkStyle 12 stroke:red,stroke-width:2px,color:grey;
linkStyle 20 stroke:red,stroke-width:2px,color:grey;
linkStyle 23 stroke:red,stroke-width:2px,color:grey;
linkStyle 26 stroke:red,stroke-width:2px,color:grey;
%% Links for control instances
linkStyle 10 stroke:blue,stroke-width:2px,color:grey;
linkStyle 13 stroke:blue,stroke-width:2px,color:grey;
linkStyle 14 stroke:blue,stroke-width:2px,color:grey;
linkStyle 15 stroke:blue,stroke-width:2px,color:grey;
linkStyle 16 stroke:blue,stroke-width:2px,color:grey;
linkStyle 21 stroke:blue,stroke-width:2px,color:grey;
linkStyle 24 stroke:blue,stroke-width:2px,color:grey;
linkStyle 27 stroke:blue,stroke-width:2px,color:grey;
%% Links for transfer instances
linkStyle 11 stroke:orange,stroke-width:2px,color:grey;
linkStyle 17 stroke:orange,stroke-width:2px,color:grey;
linkStyle 18 stroke:orange,stroke-width:2px,color:grey;
linkStyle 19 stroke:orange,stroke-width:2px,color:grey;
linkStyle 22 stroke:orange,stroke-width:2px,color:grey;
linkStyle 25 stroke:orange,stroke-width:2px,color:grey;
linkStyle 28 stroke:orange,stroke-width:2px,color:grey;
Here's a simplified version of the above:
flowchart TD
subgraph user["Customer Interaction"]
APIUser["API User"]
DNSUser["DNS User"]
ExternalUser["External User"]
end
subgraph DNSControl["DNS Control Plane"]
APIService["DNS API Service"]
controlPlaneDB[("Control Plane Database: stores zone mappings, entityID information, partition mapping")]
end
subgraph DNSDataPlane ["DNS Data Plane"]
subgraph pdnsN["Partitions ..N"]
origin["PowerDNS:<br>origin instance"]
control["PowerDNS:<br>control instance"]
transfer["PowerDNS:<br>transfer instance"]
pdnsDBOrigin[("PowerDNS:<br>Database")]
pdnsDBControl[("PowerDNS:<br>Database")]
pdnsDBTransfer[("PowerDNS:<br>Database")]
origin --> pdnsDBOrigin
control --> pdnsDBControl
transfer --> pdnsDBTransfer
end
end
DNSDataPlane --> pdnsInstance
subgraph pdnsInstance ["PowerDNS instance"]
pdnsAPIN["PowerDNS Primary/Secondary"]
pdnsDBN[("PowerDNS Partition N MySQL Database: stores zones, records, etc")]
pdnsAPIN --> pdnsDBN
pdnsDBNReplica[("PowerDNS Partition N MySQL Replica DB: stores zones, records, etc")]
pdnsDBN -- Replication --> pdnsDBNReplica
Note["Note: PowerDNS will act as a primary/secondary for a given zone, depending on the configuration"]
end
subgraph externalDNSProvider["External DNS Platform"]
end
subgraph EdgeDNS["DNS Edge"]
subgraph cacheN["Cache Node ..N"]
xdnsN["xdns (dns proxy):<br>filters invalid packets"]
dnsdistN["dnsdist:<br>authoritative DNS cache/proxy"]
end
xdnsN <-- valid query --> dnsdistN
dnsdistN <-- cache miss<br>(xproxy encoded) ---> pdnsN
end
%% Connections
APIUser -- Manages Zones via API --> APIService
ExternalUser -- Manages Zones on External DNS Provider --> externalDNSProvider
DNSUser -- Incoming DNS Queries --> EdgeDNS
APIService -- Route DNS request to PowerDNS control instance ---> control
APIService -- Route DNS request to PowerDNS control instance ---> control
APIService -- Route DNS request to PowerDNS control instance ---> control
APIService -- Check DB for customer information and mapping--> controlPlaneDB
externalDNSProvider -- Replicates records via XFR --> transfer
externalDNSProvider -- Replicates records via XFR --> transfer
externalDNSProvider -- Replicates records via XFR --> transfer