pull/428/head
Mohammed Yahya 3 years ago
commit 97377b37ba

@ -43,7 +43,7 @@ To be able to develop and run diagrams locally on you Mac device, you should hav
1. Go to diagrams root directory.
2. Install poetry, the Python project management packge used by diagrams.
2. Install poetry, the Python project management package used by diagrams.
```shell
pip install poetry

@ -37,6 +37,7 @@ Diagrams lets you draw the cloud system architecture **in Python code**. It was
![generic provider](https://img.shields.io/badge/Generic-orange?color=5f87bf)
![programming provider](https://img.shields.io/badge/Programming-orange?color=5f87bf)
![saas provider](https://img.shields.io/badge/SaaS-orange?color=5f87bf)
![c4 provider](https://img.shields.io/badge/C4-orange?color=5f87bf)
## Getting Started

@ -411,7 +411,8 @@ ALIASES = {
},
"programming": {
"framework": {
"Fastapi": "FastAPI"
"Fastapi": "FastAPI",
"Graphql": "GraphQL"
},
"language": {
"Javascript": "JavaScript",

@ -0,0 +1,97 @@
"""
A set of nodes and edges to visualize software architecture using the C4 model.
"""
import html
import textwrap
from diagrams import Cluster, Node, Edge
def _format_node_label(name, key, description):
"""Create a graphviz label string for a C4 node"""
title = f'<font point-size="12"><b>{html.escape(name)}</b></font><br/>'
subtitle = f'<font point-size="9">[{html.escape(key)}]<br/></font>' if key else ""
text = f'<br/><font point-size="10">{_format_description(description)}</font>' if description else ""
return f"<{title}{subtitle}{text}>"
def _format_description(description):
"""
Formats the description string so it fits into the C4 nodes.
It line-breaks the description so it fits onto exactly three lines. If there are more
than three lines, all further lines are discarded and "..." inserted on the last line to
indicate that it was shortened. This will also html-escape the description so it can
safely be included in a HTML label.
"""
wrapper = textwrap.TextWrapper(width=40, max_lines=3)
lines = [html.escape(line) for line in wrapper.wrap(description)]
lines += [""] * (3 - len(lines)) # fill up with empty lines so it is always three
return "<br/>".join(lines)
def _format_edge_label(description):
"""Create a graphviz label string for a C4 edge"""
wrapper = textwrap.TextWrapper(width=24, max_lines=3)
lines = [html.escape(line) for line in wrapper.wrap(description)]
text = "<br/>".join(lines)
return f'<<font point-size="10">{text}</font>>'
def C4Node(name, technology="", description="", type="Container", **kwargs):
key = f"{type}: {technology}" if technology else type
node_attributes = {
"label": _format_node_label(name, key, description),
"labelloc": "c",
"shape": "rect",
"width": "2.6",
"height": "1.6",
"fixedsize": "true",
"style": "filled",
"fillcolor": "dodgerblue3",
"fontcolor": "white",
}
# collapse boxes to a smaller form if they don't have a description
if not description:
node_attributes.update({"width": "2", "height": "1"})
node_attributes.update(kwargs)
return Node(**node_attributes)
def Container(name, technology="", description="", **kwargs):
return C4Node(name, technology=technology, description=description, type="Container")
def Database(name, technology="", description="", **kwargs):
return C4Node(name, technology=technology, description=description, type="Database", shape="cylinder", labelloc="b")
def System(name, description="", external=False, **kwargs):
type = "External System" if external else "System"
fillcolor = "gray60" if external else "dodgerblue4"
return C4Node(name, description=description, type=type, fillcolor=fillcolor)
def Person(name, description="", external=False, **kwargs):
type = "External Person" if external else "Person"
fillcolor = "gray60" if external else "dodgerblue4"
style = "rounded,filled"
return C4Node(name, description=description, type=type, fillcolor=fillcolor, style=style)
def SystemBoundary(name, **kwargs):
graph_attributes = {
"label": html.escape(name),
"bgcolor": "white",
"margin": "16",
"style": "dashed",
}
graph_attributes.update(kwargs)
return Cluster(name, graph_attr=graph_attributes)
def Relationship(label="", **kwargs):
edge_attribtues = {"style": "dashed", "color": "gray60"}
if label:
edge_attribtues.update({"label": _format_edge_label(label)})
edge_attribtues.update(kwargs)
return Edge(**edge_attribtues)

@ -36,6 +36,10 @@ class Flutter(_Framework):
_icon = "flutter.png"
class Graphql(_Framework):
_icon = "graphql.png"
class Laravel(_Framework):
_icon = "laravel.png"
@ -67,3 +71,4 @@ class Vue(_Framework):
# Aliases
FastAPI = Fastapi
GraphQL = Graphql

@ -6,7 +6,7 @@ RUN apk update && apk add --no-cache \
gcc libc-dev g++ graphviz git bash go imagemagick inkscape ttf-opensans curl fontconfig xdg-utils
# install go package.
RUN go get github.com/mingrammer/round
RUN go install github.com/mingrammer/round@latest
# install fonts
RUN curl -O https://noto-website.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip \

@ -0,0 +1,77 @@
---
id: c4
title: C4
---
## C4 Diagrams
[C4](https://c4model.com/) is a standardized model to visualize software architecture.
You can generate C4 diagrams by using the node and edge classes from the `diagrams.c4` package:
```python
from diagrams import Diagram
from diagrams.c4 import Person, Container, Database, System, SystemBoundary, Relationship
graph_attr = {
"splines": "spline",
}
with Diagram("Container diagram for Internet Banking System", direction="TB", graph_attr=graph_attr):
customer = Person(
name="Personal Banking Customer", description="A customer of the bank, with personal bank accounts."
)
with SystemBoundary("Internet Banking System"):
webapp = Container(
name="Web Application",
technology="Java and Spring MVC",
description="Delivers the static content and the Internet banking single page application.",
)
spa = Container(
name="Single-Page Application",
technology="Javascript and Angular",
description="Provides all of the Internet banking functionality to customers via their web browser.",
)
mobileapp = Container(
name="Mobile App",
technology="Xamarin",
description="Provides a limited subset of the Internet banking functionality to customers via their mobile device.",
)
api = Container(
name="API Application",
technology="Java and Spring MVC",
description="Provides Internet banking functionality via a JSON/HTTPS API.",
)
database = Database(
name="Database",
technology="Oracle Database Schema",
description="Stores user registration information, hashed authentication credentials, access logs, etc.",
)
email = System(name="E-mail System", description="The internal Microsoft Exchange e-mail system.", external=True)
mainframe = System(
name="Mainframe Banking System",
description="Stores all of the core banking information about customers, accounts, transactions, etc.",
external=True,
)
customer >> Relationship("Visits bigbank.com/ib using [HTTPS]") >> webapp
customer >> Relationship("Views account balances, and makes payments using") >> [spa, mobileapp]
webapp >> Relationship("Delivers to the customer's web browser") >> spa
spa >> Relationship("Make API calls to [JSON/HTTPS]") >> api
mobileapp >> Relationship("Make API calls to [JSON/HTTPS]") >> api
api >> Relationship("reads from and writes to") >> database
api >> Relationship("Sends email using [SMTP]") >> email
api >> Relationship("Makes API calls to [XML/HTTPS]") >> mainframe
customer << Relationship("Sends e-mails to") << email
```
It will produce the following diagram:
![c4](/img/c4.png)

@ -161,8 +161,8 @@ Node classes list of onprem provider.
- **diagrams.onprem.monitoring.Dynatrace**
- **diagrams.onprem.monitoring.Grafana**
- **diagrams.onprem.monitoring.Humio**
- **diagrams.onprem.monitoring.Newrelic**
- **diagrams.onprem.monitoring.Nagios**
- **diagrams.onprem.monitoring.Newrelic**
- **diagrams.onprem.monitoring.PrometheusOperator**
- **diagrams.onprem.monitoring.Prometheus**
- **diagrams.onprem.monitoring.Sentry**

@ -41,6 +41,7 @@ Node classes list of programming provider.
- **diagrams.programming.framework.Fastapi**, **FastAPI** (alias)
- **diagrams.programming.framework.Flask**
- **diagrams.programming.framework.Flutter**
- **diagrams.programming.framework.Graphql**, **GraphQL** (alias)
- **diagrams.programming.framework.Laravel**
- **diagrams.programming.framework.Micronaut**
- **diagrams.programming.framework.Rails**

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,64 @@
import os
import random
import string
import unittest
from diagrams import Diagram
from diagrams import setcluster, setdiagram
from diagrams.c4 import Person, Container, Database, System, SystemBoundary, Relationship
class C4Test(unittest.TestCase):
def setUp(self):
self.name = "diagram-" + "".join([random.choice(string.hexdigits) for n in range(7)])
def tearDown(self):
setdiagram(None)
setcluster(None)
try:
os.remove(self.name + ".png")
except FileNotFoundError:
pass
def test_nodes(self):
with Diagram(name=self.name, show=False):
person = Person("person", "A person.")
container = Container("container", "Java application", "The application.")
database = Database("database", "Oracle database", "Stores information.")
def test_external_nodes(self):
with Diagram(name=self.name, show=False):
external_person = Person("person", external=True)
external_system = System("external", external=True)
def test_systems(self):
with Diagram(name=self.name, show=False):
system = System("system", "The internal system.")
system_without_description = System("unknown")
def test_edges(self):
with Diagram(name=self.name, show=False):
c1 = Container("container1")
c2 = Container("container2")
c1 >> c2
def test_edges_with_labels(self):
with Diagram(name=self.name, show=False):
c1 = Container("container1")
c2 = Container("container2")
c1 >> Relationship("depends on") >> c2
c1 << Relationship("is depended on by") << c2
def test_edge_without_constraint(self):
with Diagram(name=self.name, show=False):
s1 = System("system 1")
s2 = System("system 2")
s1 >> Relationship(constraint="False") >> s2
def test_cluster(self):
with Diagram(name=self.name, show=False):
with SystemBoundary("System"):
Container("container", "type", "description")

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Loading…
Cancel
Save