Architektur als Code mit C4 + PlantUML
Was ist Architektur als Code?
Seit mehr als 4 Jahren bin ich ein großer Fan von Simon Browns C4-Modell und dessen Implementierung Structurizr.
Kurz zusammengefasst von seiner Homepage:
Das C4-Modell ist ein „Abstraction-First”-Ansatz zur Diagrammerstellung von Softwarearchitektur, basierend auf Abstraktionen, die widerspiegeln, wie Softwarearchitekten und Entwickler über Software nachdenken und sie aufbauen.
Und über Structurizr:
Structurizr baut auf „Diagramme als Code” auf und ermöglicht es, mithilfe einer Reihe von Tools und Programmiersprachen mehrere Diagramme aus einem einzigen Modell zu erstellen.
Falls ihr euch fragt, warum wir das brauchen, schaut euch diesen Tweet und seine Kommentare an:
https://twitter.com/simonbrown/status/1011895634654367744
In den letzten Monaten haben wir einige Add-ons zum Structurizr Java-Client und seiner Export-Bibliothek als Open Source veröffentlicht, die Boilerplate-Code bereitstellen, um einfach ein modulares Modell eurer Softwarearchitektur zu erstellen und es dann entweder nach Structurizr Web oder PlantUML zu exportieren.
Einführung von Structurizr-AutoConfigure
Wer den Anweisungen der Standard-Bibliothek folgt, landet schnell bei Klassen mit Hunderten von Codezeilen ohne jede Struktur. Wir haben Structurizr AutoConfigure entwickelt, das Spring Boot verwendet, um:
- eure C4-Komponenten in handhabbare Abschnitte zu gliedern
- Modell- und View-Generierung voneinander zu trennen und sicherzustellen, dass Views erst nach vollständiger Initialisierung des Modells erstellt werden
- Hilfscode bereitzustellen, der es euch ermöglicht, euren Workspace entweder nach Structurizr Web oder in PlantUML-Dateien mithilfe von C4-PlantUML zu exportieren
Dieser Blogbeitrag bietet euch ein Schritt-für-Schritt-Tutorial zur Einrichtung eures Projekts. Für den vollständigen Code könnt ihr euch außerdem das Beispielprojekt auf GitHub ansehen.
Gradle einrichten
Meine Wahl für das Dependency-Management ist Gradle, aber natürlich kann auch Maven verwendet werden. Die einzige Bibliothek, die ihr benötigt, ist Structurizr-AutoConfigure – sie kümmert sich um alle transitiven Abhängigkeiten.
repositories {
mavenCentral()
}
dependencies {
implementation("io.cloudflight.structurizr:structurizr-autoconfigure:1.0.1")
}
Die Anwendung konfigurieren
Als Nächstes erstellen wir eine Datei namens application.yaml im Ordner src/main/resources mit folgendem Inhalt:
structurizr:
workspace:
name: My project name
description: My project description
Sowohl den Namen als auch die Beschreibung anpassen.
Die Hauptklasse erstellen
Wir verwenden Kotlin zur Erstellung unseres C4-Modells, aber auch Java ist möglich. Wir empfehlen dennoch Kotlin – es hält den Code deutlich sauberer. Erstellt zunächst eine Datei Architecture.kt mit folgendem Inhalt:
package io.cloudflight.architecture
import io.cloudflight.architecture.structurizr.SpringStructurizr
import org.springframework.boot.SpringApplication
@SpringBootApplication
class Architecture
fun main(args: Array<String>) {
SpringStructurizr.run(Architecture::class.java)
}
Das benötigen wir einmalig, um Spring Boot zu starten.
Jetzt sind wir bereit, unser Modell zu befüllen.
Einen einfachen C4-Workspace erstellen
Die Idee besteht darin, einfache Spring-@Components zu erstellen und sie miteinander zu verknüpfen. Wir verwenden eine vereinfachte Version der Cloudflight Coding Contest Platform, um die Verwendung dieser Bibliothek zu veranschaulichen.
Das Modell befüllen
Wir beginnen mit unseren Personas – wir haben einen normalen Benutzer und einen Administrator.
@Component
class Personas(model: Model) {
val user = model.addPerson(Location.External, "Contest Participant", "")
val admin = model.addPerson(Location.Internal, "Administrator", "")
}
Zu beachten ist, dass das Structurizr-Model in unsere Komponenten autowired werden kann – es wurde hinter den Kulissen von der StructurizrAutoConfiguration erstellt.
Als Nächstes erstellen wir unser SoftwareSystem und einige Container:
@Component
class CodingContest(model: Model, personas: Personas) {
private val platform = model.addSoftwareSystem("Coding Contest Platform")
init {
val registration = platform.addContainer("Registration",
"maintains all users and contests, provides SSO")
val catCoder = platform.addContainer("CatCoder",
"provides the possibility to solve coding challenges")
registration.uses(catCoder, "fetches contests", "REST")
with(personas.user) {
uses(registration, "performs login")
uses(catCoder, "solves coding challenges")
}
with(personas.admin) {
uses(registration, "creates public contests")
uses(catCoder, "maintains coding games")
}
}
}
Wir haben zwei Container erstellt, unsere Personas-Komponente injiziert und alles miteinander verknüpft.
Wenn wir die Anwendung jetzt starten, passiert nichts, da wir noch keine View erstellt haben. Das ist unser nächster Schritt.
Views erstellen
Views in Structurizr müssen erstellt werden, bevor das Modell vollständig initialisiert ist. Da wir unser C4-Modell in mehrere Spring-Komponenten modularisiert haben, implementieren wir einfach das Interface ViewProvider mit seiner Methode createViews. Structurizr-AutoConfigure ruft diese Methode auf, nachdem alle Modelle initialisiert wurden.
@Component
class CodingContest(model: Model, personas: Personas) : ViewProvider {
private val platform = model.addSoftwareSystem("Coding Contest Platform")
init {
// init the model like above
}
override fun createViews(viewSet: ViewSet) {
viewSet.createContainerView(platform, "ccp", "Coding Contest Platform").apply {
addAllContainersAndInfluencers()
addAllPeople()
}
}
}
Wir erhalten hier das ViewSet, erstellen eine neue ContainerView für unser SoftwareSystem und fügen alle Container und Personen hinzu.
Jetzt sind wir bereit, unseren Workspace zu exportieren, um endlich einige Diagramme zu sehen.
Den Workspace exportieren
Structurizr-AutoConfigure bietet zwei Exportziele für euren C4-Workspace:
- Export nach Structurizr
- Export als PlantUML-Dateien
Export nach Structurizr
Der Webserver von Structurizr ist die leistungsfähigste Option zur Darstellung eines C4-Workspace. Erstellt einen Benutzer unter https://structurizr.com/ – es gibt verschiedene Preismodelle (einschließlich einer On-Premise-Lösung). Nach erfolgreicher Registrierung dort einen neuen Workspace erstellen.
Dann zurück zum Code und die Anwendung in der application.yaml konfigurieren:
structurizr:
workspace:
name: My project name
description: My project description
export:
structurizr:
enabled: true
id: xxx
key: "any"
secret: "any"
Damit wird ein StructurizrClient konfiguriert, wie in der Dokumentation beschrieben. id, key und secret von Structurizr übernehmen.
Dann die Anwendung starten – der Workspace wird mit Structurizr synchronisiert. Es muss kein StructurizrClient manuell erstellt und konfiguriert werden.
Zurück zu Structurizr und einen Blick auf den Workspace werfen. Die Auto-Layout-Funktionalität einmal aktivieren, und es erscheint ein Bild wie dieses:

Wir gehen hier nicht im Detail auf die zahlreichen Bearbeitungsmöglichkeiten des Structurizr Diagram Editors ein – schaut euch die Dokumentation an oder probiert es einfach selbst aus, das ist wirklich großartig!
Export als PlantUML-Dateien
Eine zweite Möglichkeit zur Darstellung des Workspace besteht darin, seine Diagramme als PlantUML-Dateien zu exportieren. Es gibt Plugins für IntelliJ IDEA, Confluence und viele andere Tools, um diese Dateien in Bilder umzuwandeln. Simon hat bereits eine Export-Bibliothek für PlantUML veröffentlicht, und wir haben eine weitere darauf aufbauend veröffentlicht, mit einigen zusätzlichen Features, die speziell auf die großartige C4-PlantUML-Bibliothek ausgerichtet sind.
Um den PlantUML-Export zu aktivieren, die application.yaml wie folgt anpassen:
structurizr:
workspace:
name: "Coding Contest Platform"
description: ""
export:
c4-plant-uml:
enabled: true
Die Anwendung erneut starten – es wird eine Datei namens build/c4PlantUml/ccp.puml mit folgendem Inhalt ausgegeben:
@startuml
title Coding Contest Platform - Containers
top to bottom direction
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
Person_Ext(ContestParticipant, "Contest Participant", "", $tags="Person+Element")
Person(Administrator, "Administrator", "", $tags="Person+Element")
System_Boundary("CodingContestPlatform_boundary", "Coding Contest Platform") {
Container(CodingContestPlatform.Registration, "Registration", "maintains all users and contests, provides SSO", $tags="spring+Container+Element")[[https://register.codingcontest.org/]]
Container(CodingContestPlatform.CatCoder, "CatCoder", "provides the possibility to solve coding challenges", $tags="spring+Container+Element")[[https://catcoder.codingcontest.org/]]
}
Rel_D(Administrator, CodingContestPlatform.Registration, "creates public contests", $tags="Relationship")
Rel_D(Administrator, CodingContestPlatform.CatCoder, "maintains coding games", $tags="Relationship")
Rel_D(CodingContestPlatform.Registration, CodingContestPlatform.CatCoder, "fetches contests", "REST", $tags="Relationship")
Rel_D(ContestParticipant, CodingContestPlatform.Registration, "performs login", $tags="Relationship")
Rel_D(ContestParticipant, CodingContestPlatform.CatCoder, "solves coding challenges", $tags="Relationship")
SHOW_LEGEND()
@enduml
Mit PlantUML dieses Diagramm in ein Bild umwandeln und folgendes Ergebnis erhalten:

Zu beachten ist, dass wir im Vergleich zur Structurizr-Version hier kein Layout vorgenommen haben – das hat PlantUML vollständig für uns übernommen. Das ist sowohl ein Vor- als auch ein Nachteil. Für kleinere Diagramme mag das gut funktionieren, aber bei größeren Layouts ist man mit dem automatischen Layout schnell unzufrieden – und im Vergleich zu Structurizr Web hat man hier keinerlei Möglichkeit, das Layout zu beeinflussen.
Erweiterte C4-Modelle
Man könnte jetzt denken, dass das bisher Gelesene für ein kleines Diagramm mit 4 Kästchen und einigen Linien ein bisschen viel Aufwand ist. „Das hätte ich mit Visio, PowerPoint oder Draw.io viel schneller gemacht.” Stimmt.
Aber die wahre Stärke von Structurizr/C4 zeigt sich, wenn man größere, reale Architekturmodelle erstellt. Das folgende Kapitel gibt einen Eindruck davon:
Mehrere Views aus demselben Modell erstellen
Wer das Kapitel „Maps of your code” auf der C4-Modell-Website gelesen hat, versteht die Idee, mehrere Views auf dasselbe Modell zu haben, je nach Granularitätsstufe.
Die höchste Abstraktionsebene ist der SystemLandscapeView. Um diesen zu erstellen, implementieren wir einen weiteren ViewProvider:
@Component
class ViewConfigurer: ViewProvider {
override fun createViews(viewSet: ViewSet) {
viewSet.createSystemLandscapeView("codingcontest", "").also {
it.addAllElements()
}
}
}
Das Modell bleibt unverändert.
Die Anwendung starten und mithilfe des PlantUML-Exporters folgendes zusätzliches Diagramm erhalten:

Wir kümmern uns nicht mehr darum, dass die Coding Contest Platform aus zwei Anwendungen hinter den Kulissen besteht – uns interessiert nur die Außenansicht.
Tags und Styles verwenden
Ähnlich wie CSS können Tags und Styles zum Workspace hinzugefügt werden, um Elemente zu gruppieren und ihre Darstellung zu ändern. Den ViewConfigurer wie folgt erweitern:
@Component
class ViewConfigurer(workspace: Workspace) : ViewProvider {
init {
with(workspace.views.configuration.styles) {
addElementStyle(MyTags.Database).shape(Shape.Cylinder)
addElementStyle(Tags.PERSON).shape(Shape.Person)
}
}
override fun createViews(viewSet: ViewSet) {
// like before
}
}
object MyTags {
const val FileSystem = "FileSystem"
const val Database = "Database"
}
Dann fügen wir neue Komponenten zu unserer CodingContest-Komponente mit einigen dieser Tags hinzu:
@Component
class CodingContest(model: Model, personas: Personas) : ViewProvider {
private val platform = model.addSoftwareSystem("Coding Contest Platform")
init {
// create registration and catCoder like before
val catCoderDb = platform.addContainer("CatCoder-DB", "", "MariaDB").apply {
addTags(MyTags.Database)
catCoder.uses(this, "reads and updates contest data", "JDBC")
}
val registrationDb = platform.addContainer("Registration-DB", "", "MariaDB").apply {
addTags(MyTags.Database)
registration.uses(this, "reads and updates user data", "JDBC")
}
}
}
Das ergibt folgendes Bild:

Die Styles der Datenbankkomponenten wurden angewendet und sie werden als Zylinder dargestellt. Und noch besser: Ohne das Modell zu ändern, kann eine weitere View erstellt werden, die alle Datenbanken aus dieser neuen View ausschließt:
@Component
class CodingContest(model: Model, personas: Personas) : ViewProvider {
override fun createViews(viewSet: ViewSet) {
viewSet.createContainerView(platform, "ccp", "Coding Contest Platform").apply {
addAllContainersAndInfluencers()
addAllPeople()
}
viewSet.createContainerView(platform, "ccpNoDB", "Platform without DB").apply {
addAllContainersAndInfluencers()
addAllPeople()
removeElementsWithTag(MyTags.Database)
}
}
}
Die wichtige Zeile hier ist removeElementsWithTag(MyTags. in der zweiten View. Während die View Database)ccp alle Container (einschließlich der Datenbanken) wie oben gezeigt rendert, lässt die neue View ccpNoDB alle Komponenten mit dem Tag Database weg. Dennoch operieren beide auf demselben Modell. Wenn also ein weiterer ApplicationServer oder eine neue Abhängigkeit zum Modell hinzugefügt wird, ist das in beiden Diagrammen sichtbar.
Icons hinzufügen
In den frühen Jahren von Structurizr hörte ich oft das Argument, dass man mit anderen Tools viel schönere Diagramme erstellen kann, besonders was Icons betrifft. Spätestens seit Structurizr Themes unterstützt, stimmt das nicht mehr. Man kann diese Themes für Zielplattformen wie AWS, GCP oder Azure direkt verwenden und erhält großartige Gestaltung einschließlich Farben und Icons.
Um etwas mehr Unterstützung und Typsicherheit zu bieten, haben wir die Architecture-Icons-Bibliothek veröffentlicht, die als transitive Abhängigkeit von Structurizr-AutoConfigure bereitgestellt wird.
Um sie zu nutzen, muss lediglich das Theme zum Styles-Objekt hinzugefügt und dann die Tags aus dieser Bibliothek verwendet werden:
import io.cloudflight.architectureicons.azure.AzureMonoIcons
import io.cloudflight.architectureicons.tupadr3.DevIcons2
@Component
class ViewConfigurer(workspace: Workspace) : ViewProvider {
init {
with(workspace.views.configuration.styles) {
addElementStyle(MyTags.Database).shape(Shape.Cylinder)
addElementStyle(MyTags.FileSystem).shape(Shape.Folder)
addElementStyle(Tags.PERSON).shape(Shape.Person)
addElementStyle(DevIcons2.SPRING.name)
.background("#6DB33F").color("#000000")
}
with(workspace.views.configuration) {
addTheme(AzureMonoIcons.STRUCTURIZR_THEME_URL)
addTheme(DevIcons2.STRUCTURIZR_THEME_URL)
}
}
// createViews
}
Themes wurden hinzugefügt, und wir haben auch einen neuen ElementStyle namens DevIcons2.SPRING eingeführt. Jetzt einige Tags zu unseren Komponenten hinzufügen:
@Component
class CodingContest(model: Model, personas: Personas) : ViewProvider {
private val platform = model.addSoftwareSystem("Coding Contest Platform")
init {
val registration = platform.addContainer("Registration", "").apply {
addTags(DevIcons2.SPRING.name)
}
val catCoder = platform.addContainer("CatCoder", "").apply {
addTags(DevIcons2.SPRING.name)
registration.uses(this, "fetches contests", "REST")
}
val catCoderDb = platform.addContainer("CatCoder-DB", "", "MariaDB").apply {
addTags(
MyTags.Database,
AzureMonoIcons.Databases.AZURE_DATABASE_FOR_MARIA_DB.name
)
catCoder.uses(this, "reads and updates contest data", "JDBC")
}
val registrationDb = platform.addContainer("Registration-DB", "", "MariaDB").apply {
addTags(
MyTags.Database,
AzureMonoIcons.Databases.AZURE_DATABASE_FOR_MARIA_DB.name
)
registration.uses(this, "reads and updates user data", "JDBC")
}
}
}
Das ergibt folgendes PlantUML-Diagramm:

Natürlich funktioniert das auch in Structurizr Web einwandfrei:

Und hier hat man wieder den großen Vorteil von viel mehr Gestaltungsmöglichkeiten und manueller Layoutgestaltung, was mit PlantUML nicht möglich ist.
Vollständiges Beispiel
Wer jemals mit Azure oder einem anderen Hyperscaler gearbeitet hat, weiß, dass die Dinge heutzutage viel komplexer sind. Kein Problem mit Structurizr/C4 – einfach alle Container hinzufügen und miteinander verknüpfen. Man kann bei einer ContainerView wie der folgenden enden:

Auf Basis desselben Modells können auch separate Deployment-Diagramme erstellt werden:

Zu beachten ist, dass dieses Diagramm die Realität noch nicht zu 100 % widerspiegelt und viele Abhängigkeiten fehlen. Ich wollte für die Demonstration einfach keine zu große Unübersichtlichkeit erzeugen.
Das vollständige Code-Beispiel findet sich auf GitHub:
https://github.com/cloudflightio/structurizr-autoconfigure-sample
Zusammenfassung
Obwohl die anfängliche Einrichtung etwas Zeit erfordert und grundlegende Programmierkenntnisse benötigt werden, bietet die Pflege der Softwarearchitektur als Code viele Vorteile – besonders wenn Softwaresysteme wachsen:
- Code bedeutet Versionskontrolle und damit eine nachvollziehbare Historie im Zeitverlauf.
- Das Modell und die Views können programmatisch erstellt werden (mit Schleifen, If-Anweisungen und all dem).
- Es können verschiedene Views auf dasselbe Modell erstellt werden. Alle Änderungen am Modell werden in allen Views widergespiegelt. Es gibt stets ein konsistentes Bild.
- Das Architekturmodell kann sogar automatisch aus dem Code erstellt werden.
- Es kann in verschiedene Zielsysteme exportiert werden.
Obwohl verschiedene Tools existieren, ist Structurizr unsere bevorzugte Wahl. Schaut es euch an!
Agent Hub
Gehen Sie über vereinzelte AI-Piloten hinaus und schaffen Sie eine sichere, skalierbare Grundlage für Agentic AI in Ihrer gesamten Organisation.
Zur Demo




