1. Introduction
This article is about Structurizr, a tool that provides a programmatic approach to architectural definitions and visualizations based on the C4 Model.
Structurizr breaks with traditional drag-and-drop approaches of architectural diagram editors such as UML and allows us to describe our architectural artifacts using the tool we know best: Java.
2. Getting Started
To get started, let’s add the JCenter repository to our pom.xml, as the required artifact is not mirrored in Maven Central yet:
<repositories> <repository> <snapshots> <enabled>false</enabled> </snapshots> <id>central</id> <name>bintray</name> <url>http://jcenter.bintray.com</url> </repository> </repositories>
After that, we can add the dependency:
<dependency> <groupId>com.structurizr</groupId> <artifactId>structurizr-core</artifactId> <version>1.0.0-RC3</version> </dependency>
3. Systems
Let’s start modeling a sample architecture. Suppose we’re building a fraud-detection capable payment terminal used by merchants for clearing payments.
First, we need to create a Workspace and a Model:
Workspace workspace = new Workspace("Payment Gateway", "Payment Gateway"); Model model = workspace.getModel();
We also define a user and two software systems within that model:
Person user = model.addPerson("Merchant", "Merchant"); SoftwareSystem paymentTerminal = model.addSoftwareSystem( "Payment Terminal", "Payment Terminal"); user.uses(paymentTerminal, "Makes payment"); SoftwareSystem fraudDetector = model.addSoftwareSystem( "Fraud Detector", "Fraud Detector"); paymentTerminal.uses(fraudDetector, "Obtains fraud score");
Now that our system is defined, we can create a view:
ViewSet viewSet = workspace.getViews(); SystemContextView contextView = viewSet.createSystemContextView( paymentTerminal, "context", "Payment Gateway Diagram"); contextView.addAllSoftwareSystems(); contextView.addAllPeople();
Here we created a view that includes all software systems and persons. Now the view needs to be rendered.
4. View via PlantUML
In the previous section, we created a view of a simple payment gateway.
Next step is to create a human-friendly diagram. Probably the simplest solution for an organization already using PlantUML would be to instruct Structurizr to do a PlantUML export:
StringWriter stringWriter = new StringWriter(); PlantUMLWriter plantUMLWriter = new PlantUMLWriter(); plantUMLWriter.write(workspace, stringWriter); System.out.println(stringWriter.toString());
Here the resulting markup is printed to screen, but it may just as easily be sent to a file. Rendering the data this way produces the diagram below:
5. View via the Structurizr Website
There exists another option for rendering diagrams. An architectural view can be sent to the Structurizr website via a client API. The diagram will be then generated using their rich UI.
First, we need to add client dependency:
<dependency> <groupId>com.structurizr</groupId> <artifactId>structurizr-client</artifactId> <version>0.6.0</version> </dependency>
Thereafter we can create an API client:
StructurizrClient client = new StructurizrClient("key", "secret");
The key and secret parameters are obtained from the workspace dashboard on their website. The workspace can be then be referred to by:
client.putWorkspace(1337, workspace);
Obviously, we need to register on the website and create a workspace. A basic account with a single workspace is free. At the same time, commercial plans are available as well.
6. Containers
Let’s extend our software system by adding some containers. In a C4 model, containers can be web applications, mobile apps, desktop applications, databases and file systems: pretty much anything that holds code and/or data.
First, we create some containers for our payment terminal:
Container f5 = paymentTerminal.addContainer( "Payment Load Balancer", "Payment Load Balancer", "F5"); Container jvm1 = paymentTerminal.addContainer( "JVM-1", "JVM-1", "Java Virtual Machine"); Container jvm2 = paymentTerminal.addContainer( "JVM-2", "JVM-2", "Java Virtual Machine"); Container jvm3 = paymentTerminal.addContainer( "JVM-3", "JVM-3", "Java Virtual Machine"); Container oracle = paymentTerminal.addContainer( "oracleDB", "Oracle Database", "RDBMS");
Next, we define relationships between these newly created elements:
f5.uses(jvm1, "route"); f5.uses(jvm2, "route"); f5.uses(jvm3, "route"); jvm1.uses(oracle, "storage"); jvm2.uses(oracle, "storage"); jvm3.uses(oracle, "storage");
Finally, create a container view that can be fed to a renderer:
ContainerView view = workspace.getViews() .createContainerView(paymentTerminal, "F5", "Container View"); view.addAllContainers();
Rendering the resulting diagram via PlantUML produces:
7. Components
The next level of detail in the C4 model is provided by the component view. Creating one is similar to what we’ve done before.
First, we create some components in a container:
Component jaxrs = jvm1.addComponent("jaxrs-jersey", "restful webservice implementation", "rest"); Component gemfire = jvm1.addComponent("gemfire", "Clustered Cache Gemfire", "cache"); Component hibernate = jvm1.addComponent("hibernate", "Data Access Layer", "jpa");
Next, let’s add some relationships:
jaxrs.uses(gemfire, ""); gemfire.uses(hibernate, "");
Finally, let’s create the view:
ComponentView componentView = workspace.getViews() .createComponentView(jvm1, JVM_COMPOSITION, "JVM Components"); componentView.addAllComponents();
A rendition of the resulting diagram via PlantUML results in:
8. Component Extraction
For existing code-bases using the Spring framework, Structurizr provides an automated way of extracting Spring-annotated components and adding them to the architectural artifacts.
To utilize this feature, we need to add yet another dependency:
<dependency> <groupId>com.structurizr</groupId> <artifactId>structurizr-spring</artifactId> <version>1.0.0-RC3</version> </dependency>
Next, we need to create a ComponentFinder configured with one or more resolution strategies. Resolution strategies affect things such as which components will be added to the model, depth of dependency tree traversal etc.
We can even plug in custom resolution strategies:
ComponentFinder componentFinder = new ComponentFinder( jvm, "com.baeldung.structurizr", new SpringComponentFinderStrategy( new ReferencedTypesSupportingTypesStrategy() ), new SourceCodeComponentFinderStrategy(new File("/path/to/base"), 150));
Finally, we start the finder:
componentFinder.findComponents();
The code above scans the package com.baeldung.structurizr for Spring-annotated beans and adds them as components to the container JVM. Needless to say, we’re free to implement our own scanners, JAX-RS annotated resources and even Google Guice binders.
An example of a simple diagram from a sample project is reproduced below:
9. Conclusion
This quick tutorial covers the basics of the Structurizr for Java project.
And, as always, example code can be found over on GitHub.