//

Architecture docs as code with Structurizr & Asciidoctor. Part 2: Asciidoctor

13.10.2022 | 9 minutes of reading time

You are reading the second part of this article series about architecture documentation as code. In this article, we will implement the Asciidoctor-related part of the workflow highlighted in the following figure.

The workflow we will implement in this article series.

We will use AsciiDoc to write architecture documentation based on the arc42 template and automate the generation of HTML documents out of these files using Asciidoctor and Gradle. Furthermore, we prepare the integration of the C4-PlantUML diagrams, which we will create using Structurizr in part three of this series.

The motivation behind this workflow, the main tools used, and their core concepts are explained in part one of this series.

The code can be found on GitHub. The architecture documentation we create in this article series can be found on GitHub Pages.

Project setup

When treating our documentation like code, it makes sense to store it together with the code it documents. As a result, we can integrate the documentation in review processes, keeping documentation and code in sync more easily and build the documentation together with our application. Furthermore, this allows us to generate parts of the documentation automatically from our application code and configuration, which will be covered in one of the next parts of this series.

For more complex projects than the one used in this article, techniques like multi-module projects, Git-Submodules or shared documentation libraries could be used. For the simple example used in this article, it's sufficient to store the documentation code in a separate source-set next to the test and implementation code.

Therefore, we create a new source-set called docs by adding the following lines to the build.gradle.kts file.

1sourceSets {
2    create("docs") {
3        kotlin {
4            compileClasspath += main.get().output
5            runtimeClasspath += output + compileClasspath
6        }
7    }
8}
9

To use the compiled application code, as well as related dependencies and configurations, we add the main source-set output path to the compile and runtime classpath of the docs source-set. As a result, the src folder should have the following structure.

The source folders of the example project.

Documentation

We will write our documentation using AsciiDoc. To edit AsciiDoc files like our code, we should first install a WYSIWYG (What You See Is What You Get) editor plugin for our IDE. Fortunately, there are plugins available for most IDEs. For example, VS Code, Eclipse, or IntelliJ. After the installation, we can edit AsciiDoc documents using features like syntax highlighting, auto-completion and a live preview. The following figure shows the editor provided by the IntelliJ plugin.

The IntelliJ AsciiDoc plugin.

What should be documented?

One of the main questions when writing architecture documentation is:

What should we document about our architecture?

This question is not easy to answer. While there are aspects of the architecture like quality requirements, architecture decisions, or the system context which are essential parts of an architecture documentation, other aspects might not be relevant. What is relevant or not depends on things like the system to be documented, external factors like legal requirements, and the documentation's target audience.

Another aspect to consider it the level of detail. Detailed information tends to outdate faster than more general information. As a result, detailed documents need to be updated more often, resulting in more efforts to keep the documentation up-to-date. Therefore, the goal is to find the right balance between what’s really relevant and the efforts needed to maintain the documentation.

However, architecture document templates can help with answering the question of what should be documented. Such templates describe the contents as well as the document structure of an architecture documentation. Multiple of those templates are freely available on the internet.

The arc42 template

In this article, we will use the arc42 template. The document structure defined by this template is shown in the following figure.

The chapters of the arc42 template. Source: www.arc42.org

Apart from the template itself, the arc42 website provides numerous useful tips and examples regarding how to use the template. Furthermore, it describes how the template can be tailored depending on whether you want to follow a lean or a thorough approach or if you only want to document the essential aspects of your software architecture.

Besides other formats and languages, there is a ready to use AsciiDoc version of the template available in English. We will use this template. After downloading it, we copy all AsciiDoc files, including the ones in the src folder, to the src/docs/resources project folder. This flat folder structure will make it easier to convert the AsciiDoc files to HTML. Afterwards, the folder should have the structure shown in the following figure.

The structure of the src/docs/resources folder.

Each section of the template is reflected by an AsciiDoc file. Feel free to tailor the template to your needs by removing section files and the corresponding include statements in arc42-template.adoc file. Afterwards, we rename the arc42-template.adoc to index.adoc, since this file will result in the index.html file of our generated HTML documentation. Next, we open the index.adoc file and replace the following line with our own document title.

1= image:arc42-logo.png[arc42] Template
2

The following lines can also be removed, since the containing CSS only relate to the arc42 help we don’t use.

1ifdef::backend-html5[]
2++++
3<style>
4#header,
5#content,
6#footnotes,
7#footer{
8    width:80%;
9    margin-left:auto;
10    margin-right:auto;
11    margin-top:0;
12    margin-bottom:0;
13    max-width:70em;
14    *zoom:1;
15    position:relative;
16    padding-left:.9375em;
17    padding-right:.9375em
18}
19</style>
20++++
21endif::backend-html5[]
22

The following two lines can be removed as well, together with the related documents. That's because we don’t need an about arc42 section, and we will configure the attributes stored in the config.adoc file using Gradle and Asciidoctor later.

1include::src/config.adoc[]
2 
3include::src/about-arc42.adoc[]
4

Finally, we need to fix the remaining includes by removing the src/ part of the path for each include statement. As a result, we should end up with an index.adoc file containing only the title, table of contents and the section includes, as shown in the following listing.

1= Example documentation
2 
3// toc-title definition MUST follow document title without blank line!
4:toc-title: Table of Contents
5 
6// numbering from here on
7:numbered:
8 
9<<<<
10// 1. Introduction and Goals
11include::01_introduction_and_goals.adoc[]
12 
13<<<<
14// 2. Architecture Constraints
15include::02_architecture_constraints.adoc[]
16 
17<<<<
18// 3. System Scope and Context
19include::03_system_scope_and_context.adoc[]
20 
21<<<<
22// 4. Solution Strategy
23include::04_solution_strategy.adoc[]
24 
25<<<<
26// 5. Building Block View
27include::05_building_block_view.adoc[]
28 
29<<<<
30// 6. Runtime View
31include::06_runtime_view.adoc[]
32 
33<<<<
34// 7. Deployment View
35include::07_deployment_view.adoc[]
36 
37<<<<
38// 8. Concepts
39include::08_concepts.adoc[]
40 
41<<<<
42// 9. Architecture Decisions
43include::09_architecture_decisions.adoc[]
44 
45<<<<
46// 10. Quality Requirements
47include::10_quality_requirements.adoc[]
48 
49<<<<
50// 11. Technical Risks
51include::11_technical_risks.adoc[]
52 
53<<<<
54// 12. Glossary
55include::12_glossary.adoc[]
56

Asciidoctor

Now that we have our AsciiDoc files in place, we can start to automate their conversion to HTML using Asciidoctor. Therefore, we add the Asciidoctor Gradle plugin to the plugins section of our build.gradle.kts.

1plugins {
2    ....
3    id("org.asciidoctor.jvm.convert") version "3.3.2"
4}
5

Afterwards, we configure the conversion using the AsciidoctorTask as shown in the following listing:

1tasks.withType(AsciidoctorTask::class) {
2    setSourceDir(file("./src/docs/resources"))
3    setBaseDir(file("./src/docs/resources"))
4    setOutputDir(file("build/docs"))
5    isLogDocuments = true
6    options(mapOf("doctype" to "book"))
7}
8

This is just a basic configuration. More information on how to configure the task can be found in the gradle plugin documentation. However, with this basic configuration, we should be able to convert our AsciiDoc files by running the Asciidoctor Gradle task:

1gradle asciidoctor
2

As a result, the HTML output can be found in the build/docs folder as shown in the following figure.

The resulting HTML files after executing the asciidoctor Gradle task.

Configuration

Our AsciiDoc document is split into multiple files, which are included in the root index.adoc file. When working with multiple, included AsciiDoc files, there are usually some settings which have to be configured globally for all of these files. For example, the directory containing the C4-PlantUML diagrams we will include in the next part. Such settings a referred to as attributes by Asciidoctor.

One approach is to define those attributes in a dedicated AsciiDoc file and include this file in our root index.adoc. The arc42 template followed this approach with the config.adoc file we deleted in the section "The arc42 template". When the AsciiDoc files are converted to HTML, those attributes would be applied to the index.adoc and all included files by Asciidoctor.

While this approach works well for conversion, it does not work for the IDE plugin and its preview feature. The reason is that the IDE plugin does not recognize the attributes included in the index.adoc when previewing included files. As a result, the included files would be rendered differently in the IDE preview and the converted HTML document.

To overcome this limitation, we will generate an .asciidoctorconfig containing our attributes using the Asciidoctor editorconfig Gradle plugin. The generated configuration file will be recognized, and the contained attributes will be applied automatically to all AsciiDoc files by the IDE editor plugin.

Therefore, we define our attributes as a map in our built.gradle.kts as shown in the following listing.

1val asciiAttributes = mapOf(
2    "plantUmlDir" to "./plantuml/",
3    "toc" to "left",
4    "toclevels" to 3,
5    "max-width" to "100%",
6    "projectName" to rootProject.name,
7    "buildTime" to SimpleDateFormat("dd-MM-yyyy HH:mm:ssZ").format(Date())
8)
9

The listing demonstrates two additional benefits of this approach. Because we define the attributes in our gradle.kotlin.kts, we can use Gradle related variables like rootProject.name. Furthermore, we can compute the configuration values dynamically at build time, as shown with the buildTime attribute.

To generate an .asciidoctorconfig containing these attributes, we add the editorconfig plugin to the plugins section of our build.gradle.kts.

1plugins{
2  ...
3  id("org.asciidoctor.editorconfig") version "3.3.2"
4}
5

Next, we configure the AsciidoctorEditorConfigGenerator task to use these attributes and set the destination for the .asciidoctorconfig file to the src/docs/resources folder.

1tasks.withType(AsciidoctorEditorConfigGenerator::class) {
2    setAttributes(asciiAttributes)
3    setDestinationDir("./src/docs/resources")
4    group = "documentation"
5}
6

To use these attributes also while converting the Asciidoctor files to HTML, we edit the Asciidoctor task and set the attributes.

1tasks.withType(AsciidoctorTask::class)
23  attributes(asciiAttributes)    
4}
5

Furthermore, to ensure that the .asciidoctorconfig is generated automatically, we let the processDocsResources task depend on the asciidoctorEditorConfig task.

1tasks.named("processDocsResources"){
2    dependsOn("asciidoctorEditorConfig")
3}
4

PlantUML conversion

We use Asciidoctor diagram to convert the C4-PlantUML diagrams, which we will generate via Structurizr, to images. Asciidoctor diagram is a set of extensions that are already shipped with the org.asciidoctor.jvm.convert plugin. Such a set of extensions is referred to as module in Asciidoctor.

Since we already added the convert plugin, we only need to activate the diagram module by adding the following lines to our build.gradle.kts and set the version to the latest available.

1asciidoctorj {
2    modules {
3        diagram.use()
4        diagram.setVersion("2.2.1")
5    }
6}
7

The diagram module uses Graphviz to convert PlantUML (and other) diagrams to SVG, PNG or JPEG images. Therefore, we have to download and install Graphviz. Graphviz provides a program called dot, which is used to generate images from the textual diagram definitions. Detailed information regarding the installation of Graphviz for PlantUML can be found in the PlantUML documentation.

Summary and outlook

We learned how to use AsciiDoc to write architecture documentation based on the arc42 template. We stored the documentation next to our code and used our IDE to edit it, following the documentation as code approach. Furthermore, we automated the generation of HTML documents out of the AsciiDoc files using Asciidoctor and Gradle.

In the next part, we use Structurizr to model our system, create C4-PlantUML diagrams from this model and integrate them in our documentation.

share post

Likes

3

//

More articles in this subject area\n

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen

Wir helfen Deinem Unternehmen

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.