The Software Scaffold

Streamline workflows. Prevent regressions.

Executable Documentation with ArchUnit and PlantUML


In modern software development, effective communication between various roles—such as developers, architects, and QA engineers—is crucial to building robust and maintainable systems. This is where Executable Documentation comes into play. Rather than relying on static documents that often become outdated or disconnected from the code, executable documentation uses automated, up-to-date tools that ensure all members of a team are aligned on architectural principles.

What is Executable Documentation?

Executable Documentation is a concept that extends beyond traditional documentation. It integrates architectural guidelines, design decisions, and domain rules directly into the development and testing process. By doing so, it creates a living set of guidelines that:

  • Stay in sync with the codebase: Automated tools ensure that documentation reflects the current state of the system.
  • Facilitate collaboration: Different roles in a software project—such as developers, architects, and testers—can work together more effectively, as the documentation is actionable and always up-to-date.
  • Catch deviations early: Architectural and design constraints are enforced automatically, preventing costly issues later.

Tools and Practices for Executable Documentation

Executable documentation can encompass a wide range of tools and practices that serve to improve collaboration and enforce consistency. Here are some key examples:

  1. Automated Architectural Tests (like ArchUnit): These tests ensure that architectural constraints and dependencies are respected across the codebase, catching violations early in the development process.
  2. Visual Modeling Tools (like PlantUML): PlantUML diagrams offer a straightforward way to create and share visual models of the system’s architecture. These diagrams act as a single source of truth, helping the team to understand and adhere to architectural designs.
  3. Code Generation Tools: Developed by experienced or senior developers, these tools can significantly enhance productivity and ensure code consistency. Here’s how:
    • Quick Onboarding: New developers can use these tools to generate boilerplate code that follows established patterns and conventions. This minimizes the learning curve and speeds up the development process.
    • Consistency Across the Codebase: Code generation tools help maintain a uniform structure and style, reducing the risk of deviation from architectural standards.
    • Reduced Coupling and Enhanced Flexibility: Instead of building and embedding a monolithic framework into every microservice, it is often more effective to develop coding assistants that generate code on demand. These assistants ensure that development is efficient and consistent but remain separate from the actual production code, preserving the flexibility of the system.

A Better Alternative to Internal Frameworks

In many organizations, internal frameworks are built to enforce architectural decisions and streamline development. However, these frameworks can become rigid and difficult to maintain, especially in a microservices architecture where flexibility is essential.

By opting for code generation tools instead:

  • Reduced Coupling: You avoid tightly coupling your codebase to an overarching framework. The generated code is independent, allowing each microservice to evolve independently while still following consistent patterns.
  • Separate from Production Code: Since these tools generate code rather than actively running in the system, they don’t add complexity or runtime dependencies, making the system more maintainable.
  • Faster Development Cycles: Developers can focus on implementing business logic rather than boilerplate, reducing overall development time.

Implementing Executable Documentation with ArchUnit and PlantUML

ArchUnit

ArchUnit is a Java library that provides a way to test architectural rules within your code. With ArchUnit, you can write tests that enforce architectural guidelines, such as package dependencies, layer constraints, and naming conventions..

PlantUML

PlantUML is a tool that allows you to create UML diagrams from a simple textual description. By using PlantUML, you can visualize your architecture and use it as a foundation for architectural rules enforced by ArchUnit.

Setting Up the Project

Step 1: Add Dependencies

In your build.gradle (or pom.xml), add dependencies for ArchUnit:

dependencies {
    testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
}

Step 2: Define Your Component Diagram

Create a PlantUML diagram to represent the architectural model you want to enforce and save it as overview.puml.

@startuml
top to bottom direction

[EntryPoint] <<.._start>> as main
[Repository Internals] <<..repository.config..>> <<..repository.impl..>> as repo_internals
[Repository API] <<..repository.api..>> as repo_api
[Service] <<..service..>> as svc
[REST API] <<..web.rest..>> as rest

main --> repo_api
main --> repo_internals
main --> svc
main --> rest
repo_internals -> repo_api
svc --> repo_api
rest --> svc
@enduml

You can visualize the rendering of the above code below. The most important elements are the component stereotypes, as they represent the package specifiers that ArchUnit uses when checking dependencies. Specifically, the diagram enforces that the REST API layer (represented by packages containing .web.rest.) depends on the service layer (.service.), which in turn depends on the repository API layer (.repository.api.). Any dependency that violates this order will be flagged by the unit test as a breach of the architectural constraints.

PlantUML Syntax:
top to bottom direction
[EntryPoint] <<.._start>> as main
[Repository Internals] <<..repository.config..>> <<..repository.impl..>> as repo_internals
[Repository API] <<..repository.api..>> as repo_api
[Service] <<..service..>> as svc
[REST API] <<..web.rest..>> as rest
main --> repo_api
main --> repo_internals
main --> svc
main --> rest
repo_internals -> repo_api
svc --> repo_api
rest --> svc

Step 3: Use Your PlantUML Model into and ArchUnit Test

You can use Java to parse the PlantUML model and convert it into a format that ArchUnit can understand. Like this:

package ro.iugori.arch;

import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import org.springframework.core.io.ResourceLoader;

import java.net.URL;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.library.plantuml.rules.PlantUmlArchCondition.Configuration.*;
import static com.tngtech.archunit.library.plantuml.rules.PlantUmlArchCondition.adhereToPlantUmlDiagram;

@AnalyzeClasses(packages = "ro.iugori.yadvs", importOptions = {ImportOption.DoNotIncludeTests.class})
public class UMLDiagramTest {

    private static final URL REFERENCE_DIAGRAM = ResourceLoader.class.getResource("/arch/overview.puml");

    @ArchTest
    static ArchRule UML_COMPLIANCE = classes()
            .should(adhereToPlantUmlDiagram(REFERENCE_DIAGRAM, consideringOnlyDependenciesInDiagram()));

}

Step 4: Run Your Tests

Run these tests using your preferred test runner (e.g., JUnit). If the tests pass, your architecture is compliant. If they fail, you’ll know exactly where the violations are.

The complete example is available on GitHub.

Benefits of Using ArchUnit and PlantUML Together

  • Clear visualization: PlantUML diagrams give you a visual representation of your architecture.
  • Automatic enforcement: ArchUnit tests ensure that your architecture remains intact as your code evolves.
  • Living documentation: Your documentation stays up-to-date, as any deviation will be flagged by the tests.

Conclusion

Combining ArchUnit and PlantUML as part of an Executable Documentation strategy ensures your architectural rules are both enforceable and clearly communicated. ArchUnit provides real-time enforcement of architectural constraints, while PlantUML keeps your architecture visually accessible and up to date. Together, they create a powerful system that keeps your codebase consistent and aligned with your design principles, fostering collaboration and maintaining architectural integrity across your team.


Leave a Reply

Your email address will not be published. Required fields are marked *