Diagrams as code

with

Structurizr - diagrams as code
Example diagram as code
Example diagram as code
Example diagram as code

Code - an executable architecture description language

There has been a trend over the past few years towards text-based tooling, with the most popular examples including PlantUML, WebSequenceDiagrams and Mermaid. With these tools, the diagram source is provided as text using a special domain-specific language, which the tool then visualises, typically with an automatic layout algorithm.

These tools generally have a low barrier to entry, and the source text is easily version controlled. Also, it's relatively straightforward to automate the use of these tools in order to generate diagrams and documentation during your build process.

However, each diagram needs to be defined separately, typically in a separate text file. If you have the same element on two diagrams, and you want to change the name of that element, you need to make sure that you change the name everywhere it's used. The global search and replace features in most developer tooling does make this less of a problem, but it's just one way that a collection of diagrams can easily become inconsistent if not managed properly.

When you think about it, code is just text, and another type of domain-specific language. The open source Structurizr client libraries allow you to create your software architecture model, and associated documentation, via code. For example, using Structurizr for Java:


public static void main(String[] args) throws Exception {
    Workspace workspace = new Workspace("Getting Started", "This is a model of my software system.");
    Model model = workspace.getModel();

    Person user = model.addPerson("User", "A user of my software system.");
    SoftwareSystem softwareSystem = model.addSoftwareSystem("Software System", "My software system.");
    user.uses(softwareSystem, "Uses");

    ViewSet viewSet = workspace.getViews();
    SystemContextView contextView = viewSet.createSystemContextView(softwareSystem, "SystemContext", "An example of a System Context diagram.");
    contextView.addAllSoftwareSystems();
    contextView.addAllPeople();

    Styles styles = viewSet.getConfiguration().getStyles();
    styles.addElementStyle(Tags.SOFTWARE_SYSTEM).background("#1168bd").color("#ffffff");
    styles.addElementStyle(Tags.PERSON).background("#08427b").color("#ffffff").shape(Shape.Person);

    StructurizrClient structurizrClient = new StructurizrClient("key", "secret");
    structurizrClient.putWorkspace(25441, workspace);
}


This program creates a model containing elements and relationships, creates a single view, adds some styling, and uploads it to the Structurizr cloud service via the JSON-based web API. Here's the resulting diagram when you open it in Structurizr, where the layout of the diagrams can be modified.


A simple diagram
A simple diagram
A diagram key is automatically generated for you, based upon the styles and shapes defined in the model.

This code, which was used to create the software architecture model, can be thought of as an executable domain specific language, or an executable architecture description language.

Although using code to create simple diagrams might seem verbose when compared to static text-based approaches, the real power becomes evident when you start to think in terms of creating a model of your software system, rather than a disconnected collection of static diagrams. Rather than copying and pasting elements to reuse them across multiple diagrams, a model-based approach lets you reuse an element across multiple views (diagrams). The result is that diagrams stay in sync when you rename elements.

Also, using code provides a number of unique opportunities over using a static textual description. For example, rather than explicitly specifying which elements should be shown on a diagram, it's easy to do things like "create a system context diagram for a software system, where only the immediately related elements are added". For example, with Structurizr for Java:


SystemContextView view = viewSet.createSystemContextView(softwareSystem, "SystemContext", "An example of a System Context diagram.");
view.addNearestNeighbours(softwareSystem);

With static text-based approaches (e.g. textual DSLs, JSON, YAML, etc), you likely need to manually create each and every element, relationship, and diagram. With code, you can programmatically manipulate the model and views using the power of your programming language.

Benefits of using code to create software architecture models

Rather than argue over which diagramming tool you're going to use, why not use them all? A huge benefit of creating software architecture models as code is that you can visualise the views in that model using multiple output formats. For example, here are four versions of the same view (a C4 model container diagram), each created from the same code, and rendered in different diagramming tools.

You can also do the same with diagrams showing collaboration. Again, these were all generated from the same code, and rendered with different diagramming tools.

PlantUML

PlantUML

WebSequenceDiagrams

WebSequenceDiagrams


In summary, the benefits of using code to create software architecture models include:

  • Code is familiar: Code is familiar to us as software developers, so let's take advantage of this rather than creating another language with which to represent a software architecture model.
  • Flexibility for creating models: In addition to manually writing code to create a software architecture model, we can also write code to extract architectural concepts (e.g. components) from our production codebase using techniques such as reflection, introspection and static analysis.
  • Multiple output formats: Rather than being locked into a single tool, creating your model as code provides a way to export your views to multiple formats.
  • Flexibility for visualising models: Writing code to create the views of a software architecture model provides you with the ability to slice and dice the model as needed. For example, showing all components for a large system will result in a very cluttered diagram. Instead, you can simply write some code to programmatically create a number of smaller, simpler diagrams; perhaps one per vertical slice, web controller, user story, etc. You can also opt to include or exclude any elements as necessary.
  • Versionable: Since the models are code, they are also versionable alongside your codebase in your version control system.
  • Living documentation: The code to generate the model can be integrated with your automated build system to keep your models up to date; providing accurate, up-to-date, living software architecture diagrams that actually reflect the code.

Trade-offs of using code to create software architecture models

Like any approach or tool, using code to create software architecture models is not a perfect solution, and there are a few trade-offs to be aware of:

  • Least common denominator: The trade-off with multiple output formats is that you take a "least common denominator" approach when defining your diagrams. This makes it harder to use features that are not supported by all of the rendering tools; such as different relationship types on UML diagrams (aggregation vs composition), or lifeline start and end on UML sequence diagrams. Arguably, few teams seem to use such features, but it's still worth bearing in mind.
  • Choice of programming language: Not everybody is going to know or like whichever programming language you choose, especially in polyglot environments. This is also true if you choose text-based DSLs such as PlantUML or Mermaid though.

Only you can decide whether the trade-offs are appropriate for your use cases.

Use cases

The Structurizr client libraries include a number of classes that allow you create the elements that you need to describe your software architecture with the C4 model (people, software systems, containers and components), along with methods/functions to create relationships between elements. You can either create your software architecture models manually like the example above, or:

  • Use reflection and static analysis techniques to "extract" components from your production codebase, based upon naming conventions or machine readable metadata (e.g. Java Annotations, C# Attributes, etc).
  • Parse log files or observability data to create a model of your distributed/microservices architecture.
  • Parse CloudFormation or Terraform scripts to create a model of your distributed/microservices architecture.
  • Extract the dependency graph from your central application/service register to create a model of your system landscape.
  • Parse software architecture model definitions from other tools.
  • Parse software architecture model definitions from other formats (e.g. plain text, YAML, XML, etc).

Some of these features are available out of the box with some of the client library implementations, others you will need to build yourself.

Implementations

The following implementations support the core concepts of the C4 model, and are compatible with the web API used by the Structurizr cloud service and on-premises installation, via an intermediary JSON format. Some client library implementations also provide support for other diagram output formats. All are open source.


Library Input formats
(create models using)
Output formats
(render diagrams using)
Structurizr for Java Java and other JVM compatible languages
(e.g. Groovy, Kotlin, Scala)
Structurizr cloud service and on-premises installation,
Structurizr for .NET .NET compatible languages
(Framework and Core)
Structurizr cloud service and on-premises installation,
plus PlantUML, and C4PlantUML
Structurizr for TypeScript TypeScript Structurizr cloud service and on-premises installation
Structurizr for PHP PHP Structurizr cloud service and on-premises installation
Structurizr for Python Python Structurizr cloud service and on-premises installation
Arch as code YAML Structurizr cloud service and on-premises installation

See the GitHub repos linked above for getting started guides and code examples.