exportStructurizr

Max Hofer

3 minutes to read

About This Task

Structurizr builds upon "diagrams as code", allowing you to create multiple diagrams from a single model, using a number of tools and programming languages. Structurizr is specifically designed to support the C4 model for visualising software architecture.

This task exports PlantUML (respective C4-PlantUML) diagrams from a software architecture model described with the Structurizr DSL. The generated diagrams can be integrated into the AsciiDoc documentation.

The software architecture model is integral part of the software architecture documentation. As such we strongly suggest to put the Structurizr workspace file under revision control integrating it in the src/docs directory. The user would edit the software architecture model by this file.

This Structurizr DSL example below creates two diagrams, based upon a single set of elements and relationships.

workspace {

    model {
        user = person "User"
        softwareSystem = softwareSystem "Software System" {
            webapp = container "Web Application" {
                user -> this "Uses"
            }
            container "Database" {
                webapp -> this "Reads from and writes to"
            }
        }
    }

    views {
        systemContext softwareSystem {
            include *
            autolayout lr
        }

        container softwareSystem {
            include *
            autolayout lr
        }

        theme default
    }
}

And here the diagrams defined by the views in the example above rendered by the Structurizr web renderer.

System Context Diagram
Container Diagram

Configuration

Config.groovy
// Configuration for Structurizr related tasks
structurizr = [:]

structurizr.with {

    // Configure where `exportStructurizr` looks for the Structurizr model.
    workspace = {
        // The directory in which the Structurizr workspace file is located.
        // path = 'src/docs/structurizr'

        // By default `exportStructurizr` looks for a file '${structurizr.workspace.path}/workspace.dsl'
        // You can customize this behavior with 'filename'. Note that the workspace filename is provided without '.dsl' extension.
        // filename = 'workspace'
    }

    export = {
        // Directory for the exported diagrams.
        //
        // WARNING: Do not put manually created/changed files into this directory.
        // If a valid Structurizr workspace file is found the directory is deleted before the diagram files are generated.
        // outputPath = 'src/docs/structurizr/diagrams'

        // Format of the exported diagrams. Defaults to 'plantuml' if the parameter is not provided.
        //
        // Following formats are supported:
        // - 'plantuml': the same as 'plantuml/structurizr'
        // - 'plantuml/structurizr': exports views to PlantUML
        // - 'plantuml/c4plantuml': exports views to PlantUML with https://github.com/plantuml-stdlib/C4-PlantUML
        // format = 'plantuml'
    }
}

Example Configuration

The example below shows a possible directory layout with a src/docs/structurizr directory containing the workspace.dsl file.

.
├── docToolchainConfig.groovy
├── dtcw
└── src
    └── docs
        ├── example
        │   └── example.adoc
        ├── images
        │   ├── some-pics-1.png
        │   └── some-pics-2.png
        └── structurizr
            └── workspace.dsl

The minimal configuration for the exportStructurizr task in your docToolchainConfig.groovy would look like

structurizr = [:]

structurizr.with {
    workspace = {
        path = 'src/docs/structurizr'
    }
    export = {
        outputPath = "src/docs/structurizr/diagrams"
        // The format is optional.
        // format = 'plantuml'
    }
}

You probably want to put the directory configured with structurizr.export.outputPath into your .gitignore file.

Warning
Do not put manually created/changed files into the directory provided with structurizr.export.outputPath. If a valid Structurizr workspace file is provided the directory is deleted before the diagram files are generated.

Calling ./dtcw exportStructurizr generates the diagrams in the structurizr.export.outputPath directory.

Directory layout after exporting the diagrams
├── docToolchainConfig.groovy
├── dtcw
└── src
    └── docs
        ├── example
        │   └── example.adoc
        ├── images
        │   ├── some-pics-1.png
        │   └── some-pics-2.png
        └── structurizr
            ├── diagrams
            |   ├── Container-1-key.puml
            |   ├── Container-1.puml
            |   ├── SystemContext-1-key.puml
            |   └── SystemContext-1.puml
            └── workspace.dsl

Following our example the exported diagrams may be included in the Asciidoc document example.adoc with

plantuml::../structurizr/diagrams/SystemContext-1.puml["structurizr-SystemContext",format=svg]

plantuml::../structurizr/diagrams/Container-1.puml["structurizr-Container",format=svg]

Source

exportStructurizr.gradle
task exportStructurizr (
        group: 'docToolchain',
        description: 'exports the views of a Structurizr DSL file to diagramms'
) {
    doLast {
        logger.debug("\n=====================\nStructurizr Config - before property replacement:\n=====================")
        logger.debug("structurizr.workspace.path: ${config.structurizr.workspace.path}")
        logger.debug("structurizr.workspace.filename: ${config.structurizr.workspace.filename}")
        logger.debug("structurizr.export.outputPath: ${config.structurizr.export.outputPath}")
        logger.debug("structurizr.export.format: ${config.structurizr.export.format}")

        // First we check the parameters
        def workspacePath = findProperty("structurizr.workspace.path")?:config.structurizr.workspace.path
        if (!workspacePath) {
            throw new GradleException("Missing configuration parameter 'structurizr.workspace.path': please provide the path where the Structurizr workspace file is located.")
        }
        // If 'workspace.filename' is not provided, default to 'workspace' (without extension).
        def filename = (findProperty("structurizr.workspace.filename")?:config.structurizr.workspace.filename)?:'workspace'

        def outputPath = findProperty("structurizr.export.outputPath")?:config.structurizr.export.outputPath
        if (!outputPath) {
            throw new GradleException("Missing configuration parameter 'structurizr.export.outputPath': please provide the directory where the diagrams should be exported.")
        }

        // If 'format' parameter is not provided, default to 'plantuml'.
        def format = (findProperty("structurizr.export.format")?:config.structurizr.export.format)?:'plantuml'
        //  Assure valid 'format' configuration parameter.
        DiagramExporter exporter
        switch(format) {
            case 'plantuml':
            case 'plantuml/structurizr':
                exporter = new StructurizrPlantUMLExporter()
                break
            case 'plantuml/c4plantuml':
                exporter = new C4PlantUMLExporter()
                break
            default:
                throw new GradleException("unknown structurizr.format '${format}': supported formats are 'plantuml' and 'plantuml/c4plantuml'.")
        }

        logger.info("\n=====================\nStructurizr Config:\n=====================")
        logger.info("structurizr.workspace.path: ${workspacePath}")
        logger.info("structurizr.workspace.filename: ${filename}")
        logger.info("structurizr.export.outputPath: ${outputPath}")
        logger.info("structurizr.export.format: ${format}")

        def workspaceFile = new File(docDir, workspacePath+'/'+filename+'.dsl')
        logger.info("Parsing Structurizr workspace file '${workspaceFile}'")
        StructurizrDslParser parser = new StructurizrDslParser()
        // TODO: provide better error output in case parsing fails
        parser.parse(workspaceFile)
        Workspace workspace = parser.getWorkspace()
        ThemeUtils.loadThemes(workspace)

        // Cleanup existing diagrams and then make sure the directory exists where the diagrams are exported
        new File(docDir, outputPath).deleteDir()
        // Create a readme to clarify things
        def readme = """This folder contains exported diagrams from a model described with Structurizr DSL.

Please note that these are generated files but reside in the `src`-folder in order to be versioned.

# Warning!

**The contents of this folder will be overwritten with each re-export!**

use `gradlew exportStructurizr` to re-export the diagrams
"""
        new File(docDir, outputPath).mkdirs()
        new File(docDir, outputPath+'/README.adoc').write(readme)

        Collection<Diagram> diagrams = exporter.export(workspace);
        diagrams.each { diagram ->
            def file = new File(docDir, outputPath+"/"+diagram.key+'.'+diagram.getFileExtension())
            file.write(diagram.definition)
            if (diagram.legend) {
                def legend = new File(docDir, outputPath+"/"+diagram.key+"-key."+diagram.getFileExtension())
                legend.write(diagram.legend.definition)
            }
        }
    }
}