Diagrams and components¶
Note
You can find a detailed description of the different C4 diagram types and components in the corresponding sections of the documentation.
Diagrams¶
Diagram is a primary object representing a diagram.
from c4.diagrams.core import Diagram
with Diagram("Simple Diagram") as diagram:
# Declare diagram components here
Each C4 diagram type is implemented as a subclass of Diagram:
from c4 import (
SystemContextDiagram,
SystemLandscapeDiagram,
ContainerDiagram,
ComponentDiagram,
DynamicDiagram,
DeploymentDiagram,
)
Note
Diagram is a low-level abstraction and is not intended to be instantiated directly.
Always use one of the concrete diagram classes (SystemContextDiagram,
ContainerDiagram, ComponentDiagram, etc.), as they define the semantics,
constraints, and rendering rules specific to each C4 diagram type.
Elements¶
Element represents a C4 abstraction such as a System, Container, or Component.
Elements must be declared within a diagram context:
from c4.diagrams.core import Diagram, Element
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
assert diagram.elements == [element1, element2]
Each C4 component type is implemented as a subclass of Element:
from c4 import (
Person,
PersonExt,
System,
SystemDb,
SystemDbExt,
SystemExt,
SystemQueue,
SystemQueueExt,
Component,
ComponentDb,
ComponentDbExt,
ComponentExt,
ComponentQueue,
ComponentQueueExt,
Container,
ContainerDb,
ContainerDbExt,
ContainerExt,
ContainerQueue,
ContainerQueueExt,
DeploymentNode,
DeploymentNodeLeft,
DeploymentNodeRight,
Node,
NodeLeft,
NodeRight,
)
Relationships¶
Relationship represents a connection between elements and may include additional
properties such as direction and label.
A relationship object contains four primary attributes: label, relationship_type, from_element, and to_element.
There are shortcut classes for different relationship types and directions:
from c4.diagrams.core import (
Rel,
RelL,
RelLeft,
RelR,
RelRight,
RelU,
RelUp,
RelD,
RelDown,
RelNeighbor,
BiRel,
BiRelD,
BiRelDown,
BiRelL,
BiRelLeft,
BiRelNeighbor,
BiRelR,
BiRelRight,
BiRelU,
BiRelUp,
RelBack,
RelBackNeighbor,
)
Relationships must be declared within a diagram context:
from c4.diagrams.core import Diagram, Element, Rel
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element1 >> Rel("Uses") >> element2
assert diagram.relationships[0].label == "Uses"
assert diagram.relationships[0].from_element == element1
assert diagram.relationships[0].to_element == element2
There are several ways to declare relationships:
Simple cases¶
A relationship can be declared using its label:
from c4.diagrams.core import Diagram, Element
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element1 >> "Uses" >> element2
Alternatively, a relationship can be declared using an explicit relationship object:
from c4.diagrams.core import Diagram, Element, Rel
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element1 >> Rel("Uses") >> element2
Rel attributes
Rel (and other Relationship subclasses like RelUp, RelLeft, etc.)
lets you specify additional relationship attributes such as technology,
description, link, and tags.
In most cases, a plain string label ("Uses") is enough.
Using the pipe syntax:
from c4.diagrams.core import Diagram, Element
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element1 >> element2 | "Uses"
Or by calling the method:
from c4.diagrams.core import Diagram, Element
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element1.uses(element2, label="Uses")
All syntaxes above produce the same Relationship in the diagram.
Reversed direction¶
from c4.diagrams.core import Diagram, Element, Rel
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element2 << Rel("Uses") << element1
Or by calling the method:
from c4.diagrams.core import Diagram, Element
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element2.used_by(element1, label="Uses")
One-to-many relationships¶
from c4.diagrams.core import Diagram, Element, Rel
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element3 = Element(label="Element 3")
element1 >> Rel("Uses") >> [element2, element3]
assert diagram.relationships[0].label == "Uses"
assert diagram.relationships[0].from_element == element1
assert diagram.relationships[0].to_element == element2
assert diagram.relationships[1].label == "Uses"
assert diagram.relationships[1].from_element == element1
assert diagram.relationships[1].to_element == element3
or reversed:
from c4.diagrams.core import Diagram, Element, Rel
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element3 = Element(label="Element 3")
[element2, element3] << Rel("Uses") << element1
Many-to-one relationships¶
from c4.diagrams.core import Diagram, Element, Rel
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element3 = Element(label="Element 3")
[element2, element3] >> Rel("Uses") >> element1
or reversed:
from c4.diagrams.core import Diagram, Element, Rel
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
element2 = Element(label="Element 2")
element3 = Element(label="Element 3")
element1 << Rel("Uses") << [element2, element3]
Boundaries¶
Boundary allows grouping (or clustering) elements into an isolated logical container.
Elements that belong to a boundary must be created within a boundary context:
from c4.diagrams.core import Diagram, Element, Boundary, Rel
with Diagram("Simple Diagram") as diagram:
element1 = Element(label="Element 1")
with Boundary(label="Boundary") as boundary:
element2 = Element(label="Element 2")
element3 = Element(label="Element 3")
element1 >> Rel("Uses") >> [element2, element3]
assert diagram.elements == [element1, element2]
There are several types of boundaries:
from c4 import (
SystemBoundary,
ContainerBoundary,
EnterpriseBoundary,
Node,
NodeLeft,
NodeRight,
DeploymentNode,
DeploymentNodeLeft,
DeploymentNodeRight,
)
Examples¶
You can find all the examples on the examples page.
System context diagram¶
from c4 import SystemContextDiagram, Person, System, SystemExt
with SystemContextDiagram("Internet Banking System") as diagram:
client = Person(
label="Personal Banking Customer",
description="A customer of the bank with one or more personal bank accounts"
)
banking_system = System(
label="Internal Banking System",
description=(
"Allow customers to view information about their bank accounts "
"and make payments via the web."
)
)
aws_email = SystemExt(
label="Amazon Web Services Simple Email Service",
description="Cloud-based email service provider."
)
core_banking_system = System(
label="Core Banking System",
description=(
"Handles core banking functions including customer information, "
"bank account management, transactions, etc."
)
)
client >> "Views account balances and makes payments using" >> banking_system
banking_system >> "Sends e-mails to customers using" >> aws_email
aws_email >> "Sends e-mails to" >> client
banking_system >> "Gets bank account information from and makes payments using" >> core_banking_system
The diagram above can be rendered into the following PlantUML diagram:
Container diagram¶
from c4 import (
ContainerDiagram,
Person,
System,
SystemExt,
ContainerBoundary,
Container,
ContainerDb,
Rel,
RelDown,
RelRight,
RelUp,
RelLeft,
)
with ContainerDiagram("Internet Banking System") as diagram:
client = Person(
label="Personal Banking Customer",
description="A customer of the bank with one or more personal bank accounts"
)
aws_email = SystemExt(
label="Amazon Web Services Simple Email Service",
description="Cloud-based email service provider."
)
core_banking_system = System(
label="Core Banking System",
description=(
"Handles core banking functions including customer information, "
"bank account management, transactions, etc."
)
)
with ContainerBoundary(label="Internal Banking System"):
static = Container(
label="Static Content",
technology="Directory",
description="HTML, CSS, JavaScript, etc.",
)
ui = Container(
label="UI",
technology="JavaScript and Angular",
description=(
"Single-page app that provides Internet banking functionality "
"to customers via their web browser."
),
)
backend = Container(
label="Backend",
technology="Java and Spring Boot",
description=(
"Provides Internet banking functionality via a JSON/HTTP API."
)
)
statement_store = Container(
label="Statement Store",
technology="Amazon Web Services S3 Bucket",
description="Bank account statements rendered as PDF files."
)
db = ContainerDb(
label="Database",
technology="MySQL",
description="User account information, access logs, etc.",
)
client >> RelDown("Loads the UI form" ) >> static
client >> RelDown("Views account balances and makes payments using" ) >> ui
static >> RelRight("Delivers") >> ui
backend >> RelLeft("Sends e-mails to customers using", technology="AWS SES API/HTTP") >> aws_email
aws_email >> RelUp("Sends e-mails to") >> client
ui >> Rel("Makes API requests to", technology="JSON/HTTP") >> backend
backend >> RelRight("Makes API requests to", technology="XML/HTTPS") >> core_banking_system
backend >> Rel("Reads from and writes to", technology="MySQL protocol") >> db
backend >> Rel("Reads from and writes to", technology="AWS S3 API/HTTP") >> statement_store
The diagram above can be rendered into the following PlantUML diagram: