Docs as Code - Dokumentation mit Asciidoctor

titelbild-docs-code
In diesem Artikel möchte ich am Beispiel von Asciidoctor beleuchten, wie eine Alternative zu klassischen Dokumentationswerkzeugen aussehen kann, wie diese funktioniert, und wie sich Dokumentation als Code in den Softwareentwicklungsprozess einbinden lässt. Gerade für agile Projekte kann die kontinuierliche Fortschreibung und Verbesserung der Dokumentation damit sehr effektiv und effizient gestaltet werden.

In nahezu jedem halbwegs professionellen Projekt im Bereich der Softwareentwicklung müssen verschiedene Arten von Dokumenten erstellt, fortgeschrieben, versioniert und publiziert werden. Viele Unternehmen setzen hier zunächst auf klassische Schreibprogramme, wie z. B. Microsoft Word. Für viele Anwendungszwecke kommt man damit gut zurecht. Wenn es um technische Dokumentationen geht, zeigen sich hier jedoch schnell Grenzen:

  • Wie wird Quellcode eingebettet und wie erfolgt ein Syntax-Highlighting?
  • Womit werden Grafiken und Diagramme erstellt, wie werden diese im Dokument eingebettet und wo verbleibt das Original?
  • Wie kann eine Dokumentation kollaborativ fortgeschrieben werden, ohne Konflikte bei der Bearbeitung zu erhalten?
  • Wie werden Versionen verwaltet, und welche Version der Doku passt zu welcher Version der Software?

Neben diesen Aspekten kommen in der Praxis noch viele Kleinigkeiten hinzu, die eine klassische Form der Dokumentation erschweren. Da Softwareentwickler ihre Programme in Form von Programmcode entwickeln, liegt es nahe, diesen Ansatz für die Erstellung der Dokumentation zu adaptieren. Dies führt uns zum Titel des Beitrags: Documentation as Code oder Docs-as-Code (DaC) - also Dokumentation als Quellcode. Der Ansatz ist nicht neu und wird beispielsweise in Universitäten mittels LaTex bereits seit Jahrzehnten praktiziert. Einen ähnlichen Ansatz bietet die Auszeichnungsprache AsciiDoc mit dem Textverarbeitungstool Asciidoctor. Eine alternative Implementierung von Asciidoctor für die JVM wird von AsciidoctorJ zur Verfügung gestellt. Damit lassen sich Dokumentationen ohne externe Binaries direkt im Rahmen des Build-Prozesses mit Maven oder Gradle erstellen. Und genau das wollen wir uns in diesem Artikel einmal anschauen.

AsciiDoc

AsciiDoc ist eine Auszeichnungssprache, mit der sich Texte in verschiedenen Dokumentenformaten publizieren lassen. Der Quellcode wird in normalen Textdateien mit der Dateiendung ".adoc" gespeichert und kann in verschiedene Ausgabeformate konvertiert werden, z. B. nach HTML5 oder ins DocBook-Format. Von DocBook können sie wiederum in viele andere Formate, z. B. nach PDF konvertiert werden.

Asciidoctor

Asciidoctor ist eine Neuimplementierung des ursprünglich in Python geschriebenen Textverarbeitungstoos und wurde in Ruby erstellt. Im vorliegenden Artikel kommt es allerdings nicht zum Einsatz, denn wir arbeiten hier mit AsciidoctorJ.

AsciidoctorJ

AsciidoctorJ ist eine Adaptierung für die Java Virtual Machine (JVM) und wird dort unter der Verwendung von JRuby ausgeführt. Neben dem eigentlichen Text-Processor stehen ein Maven- und ein Gradle-Plugin für die nahtlose Einbindung in den Buildprozess zur Verfügung.

AsciidoctorJ is the official library for running Asciidoctor on the JVM. Using AsciidoctorJ, you can convert AsciiDoc content or analyze the structure of a parsed AsciiDoc document from Java and other JVM languages.

https://github.com/asciidoctor/asciidoctorj

Eine Beispiel-dokumentation

Um uns mit den Prinzipien der Auszeichnungssprache und den Möglichkeiten der Konvertierung vertraut zu machen, schauen wir uns im Folgenden anhand eines DV-Konzepts eine Beispiel-Dokumentation eines Software-Projektes an. Zunächst gehen wir auf die Strukturierung des Projektes und der Dateien ein. Danach werfen wir einen Blick auf einige wichtige Syntax-Elementen von AsciiDoc. Im Anschluss folgt ein Überblick über die Erzeugung von Grafiken und Diagrammen. Zuletzt schauen wir uns die Konfiguration des Maven-Plugins und die Integration in den Build-Prozess an.

Strukturierung

Die nebenstehende Grafik zeigt ein klassisches Java-Maven-Projekt. Unter src/main/java befindet sich der Java-Quellcode - das sollte uns vertraut vorkommen. Etwas weniger bekannt dürfte allerdings das Verzeichnis src/docs/asciidoc sein. Dort finden wir die *.adoc-Dateien wieder, die den Quellcode für unsere Dokumentation enthalten. Wie diese inhaltlich aufgebaut sind, schauen wir uns im nächsten Kapitel an. Zunächst möchte ich die Strukturierung vorstellen.

Die Datei 1_main.adoc ist das Hauptdokument, in das alle anderen Dokumente eingebunden sind. 2_preface.adoc enthält Vorwort und Einleitung und 3_overviews.adoc enthält Projektglossar, Abkürzungsverzeichnis etc. Dazwischen kommen die eigentlichen Kapitel mit den spezifischen Inhalten. Diese sind zur besseren Übersicht im Unterverzeichnis chapter abgelegt. Es gibt hier also drei Kapitel (001_demo_ditaa.adoc, 002_demo_use_case.adoc und 003_demo_sequenz.adoc).

Darüber hinaus gibt es natürlich noch die pom.xml für Maven und eine README.md.

Syntax von AsciiDoc

Schauen wir uns zunächst ein paar Syntax-Elemente von AsciiDoc an, damit wir ein Gefühl dafür bekommen, wie die Sprache aufgebaut ist.

Überschriften

Eine Überschrift wird mit einen =-Zeichen eingeleitet

= erste Ebene / Dokumententitel

Eine Überschrift mit einem =-Zeichen ist die Hauptüberschrift für das gesamte Dokument. Die weiteren Gliederungspunkte werden mit weiteren =-Zeichen erstellt:

== zweite Ebene

=== dritte Ebene

==== vierte Ebene

===== fünfte Ebene


Aufzählungen

Unsortierte Aufzählungen (Unordered List) werden mit dem *-Zeichen eingeleitet:

* erster Punkt

** zweiter Punkt

*** dritter Punkt

Sortierte Aufzählungen (Ordered List) werden mit dem .-Zeichen eingeleitet:

. erster Punkt

.. zweiter Punkt

... dritter Punkt


Tabellen

Tabellen werden ebenfalls mit einfachen Ascii-Zeichen abgebildet. Eine einfache Tabelle könnte wie folgt aussehen:

.Die beteiligten Komponenten
|===
| Browser     | Der Browser läuft auf dem Client-PC oder einem mobilen Endgerät (Tablet, Smartphone etc.)
| Frontend    | Das Frontend wird auf dem Server in der DMZ deployed und ist für das Rendering der GUI zuständig.
| Middleware  | Die Middleware enthält die Business-Logik.
| Datenbank   | Die Daten werden in der zentralen Datenbank gespeichert.
|===


Die erste Zeile mit dem führenden Punkt ist die Legende der Tabelle. Legenden können auch an anderen Stellen verwendet werden (z. B. bei Grafiken).

Mit der zweiten Zeile |=== wird die Tabelle eingeleitet. In der dritten Zeile werden die einzelnen Zellen durch das |-Symbol getrennt. In der letzten Zeile wird die Tabelle mit ===| beendet.


Textformatierungen

Einfache Formatierungen von Text können wie folgt abgebildet werden:

Dies ist ein *fettes* Wort.

Dies ist ein _kursives_ Wort.

Dieses ist ein als `Code` dargestelltes Wort.


Code-Blöcke

Quellcode wird oft nicht nur im Text, sondern als kompletter Block dargestellt. Dazu wird dieser in zwei Blöcken von .... eingeschlossen:

[source,xml]
....
<element tag="value">text</element>
....

Über dem Block wird in eckigen Klammern definiert, welchen Inhalt der folgende Block hat. In diesem Fall liegt mit source also Quellcode in xml vor.


Eine ausführliche Erläuterung mit entsprechenden Beispielen zur Syntax ist auf der offiziellen Webseite von Asciidoctor (hier) zu finden.



Diagramme

Für eine umfassende und nützliche Software-Dokumentation oder ein sinnvolles DV-Konzept werden zur Erläuterung und Veranschaulichung üblicherweise Grafiken und Diagramme einsetzt. Hier spielt Asciidoctor seine Stärken aus, indem es nativ Ditaa- und PlantUML-Diagramme unterstützt.
Ditaa

Ditaa steht für "Diagrams Through Ascii Art" und erlaubt die Erstellung von einfachen Grafiken und Diagrammen mittels Ascii-Art. Im konkreten Demo-Projekt wird dies im Kapitel 001_demo_ditaa.adoc demonstriert:

.Legende der Grafik
[ditaa]
....
+---------+          +------------+          +------------+          +-----------+
|         |          |            |          |            |          |           |
| Browser |          | Frontend   |          | Middleware |          | Datenbank |
|         |   HTTP   |            |   HTTP   |            |   JDBC   |           |
|         +--------->+            +--------->+            +--------->+           |
|         |          |            |          |            |          |           |
|     cYEL|  +------>|        c06F|          |        c0F0|          |       cPNK|
+---------+  |       +-----+------+          +-----+------+          +-----+-----+
             |             |                       |                              
             :             |                       |                              
     /-------+--\          v                       v                              
     |          |     +----------+            +----------+                        
     | DMZ      |     | Logging  |            | Logging  |                        
     |          |     |          |            |          |                        
     \----------/     |       {d}|            |       {d}|                        
                      +----------+            +----------+                        
.... 

Das Resultat sieht dann wie folgt aus:


Abb. 1: Legende der Grafik
PlantUML

Einen anderen Ansatz verfolgt PlantUML. Mit einer speziellen DSL lassen sich durch einen Beschreibungstext komplexe und anschauliche Diagramme generieren. Je nach Diagramm-Typ funktioniert das besser oder schlechter. Sequenzdiagramme sind beispielsweise sehr gut geeignet, da ein solches Diagramm einen relativ einfachen Aufbau hat, und sich daher sehr gut textuell beschreiben lässt. Ein einfaches Beispiel liefert das Kapitel 003_demo_sequenz.adoc unses Beispiel-Projekts:

.Request über Schnittstellen vom Frontend über Middleware zur Datenbank
[plantuml]
....
participant Frontend as FE
participant Middleware as MW
participant Datenbank as DB

FE  ->  MW:       GET /kunden/4711
activate   FE
MW  ->  DB:       "SELECT * FROM KUNDEN where KD_NR = '4711'"
activate   MW
MW  <-- DB:       KD oder leeres ResultSet
deactivate MW
FE  <-- MW:       HTTP 200/400/500
deactivate FE

note left
 Kunde als HTTP-Body
 oder Error im HTTP-Header
end note
.... 
Das daraus generierte Diagramm sieht dann wie folgt aus:

Ein weiteres Beispiel mit PlantUML zeigt uns das Kapitel 002_demo_use_case.adoc. Dort wird ein Use-Case-Diagramm verwendet:

.Verwendung der Demo-Applikation durch den Mitarbeiter im Fachbereich
[plantuml]
....
actor "MA Fachbereich" as MA_FB
node Browser
node Frontend {
  file "NFS Mount" as NFS
}
usecase CMS

MA_FB         -do->  Browser:       ""nutzt Demo-Applikation""
Browser       -ri->  Frontend:      ""ruft Frontend auf""

CMS           -up->  Frontend:      ""CMS-Daten + Konfiguration""
.... 

Werfen wir auch hier einen Blick auf das Ergebnis:

Maven-Plugin
 

Nachdem wir jetzt die grundlegenden Eigenschaften und die Struktur der Beispiel-Dokumentation angeschaut haben, werfen wir nun einen Blick auf das Resultat bzw. das Maven-Plugin von AsciidoctorJ, mit welchem der Output erzeugt wird.

Zunächst wird das Plugin in den build-Block der pom.xml eingebunden:

<build>
    <plugins>
        <plugin>
            <groupId>org.asciidoctor</groupId>
            <artifactId>asciidoctor-maven-plugin</artifactId>
            <version>${asciidoctor-maven-plugin.version}</version>
            <dependencies>
                <dependency>
                    <groupId>org.asciidoctor</groupId>
                    <artifactId>asciidoctorj</artifactId>
                    <version>${asciidoctorj.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.asciidoctor</groupId>
                    <artifactId>asciidoctorj-pdf</artifactId>
                    <version>${asciidoctorj-pdf.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.asciidoctor</groupId>
                    <artifactId>asciidoctorj-diagram</artifactId>
                    <version>${asciidoctorj-diagram.version}</version>
                </dependency>
            </dependencies>
            <configuration>
                <sourceDirectory>src/docs/asciidoc</sourceDirectory>
                <preserveDirectories>true</preserveDirectories>
                <attributes>
                    <sourcedir>${project.build.sourceDirectory}</sourcedir>
                </attributes>
                <requires>
                    <require>asciidoctor-diagram</require>
                </requires>
            </configuration>
            <executions>
                <execution>
                    <id>output-pdf</id>
                    <phase>generate-resources</phase>
                    <goals>
                        <goal>process-asciidoc</goal>
                    </goals>
                    <configuration>
                        <backend>pdf</backend>
                        <attributes>
                            <source-highlighter>coderay</source-highlighter>
                            <icons>font</icons>
                            <pagenums />
                            <toc />
                            <idprefix />
                            <idseparator>-</idseparator>
                        </attributes>
                    </configuration>
                </execution>
                <execution>
                    <id>output-html</id>
                    <phase>generate-resources</phase>
                    <goals>
                        <goal>process-asciidoc</goal>
                    </goals>
                    <configuration>
                        <backend>html5</backend>
                        <attributes>
                            <source-highlighter>coderay</source-highlighter>
                            <icons>font</icons>
                            <pagenums />
                            <toc />
                            <idprefix />
                            <idseparator>-</idseparator>
                        </attributes>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    <plugins>
<build> 

In den Zeilen 4-6 erfolgt die Angabe des Plugins org.asciidoctor:asciidoctor-maven-plugin:2.1.0. Anschließend folgen die für das Plugin notwendigen Abhängigkeiten:

  • asciidoctorj
  • asciidoctorj-pdf
  • asciidoctorj-diagram


In  den Blöcken configuration und exectutions folgen dann konkrete Anweisungen an das Plugin. Dort sehen wir auch, dass es zwei Output-Formate gibt: PDF und HTML5. Im Rahmen des Build-Prozesses werden also Dokumente im PDF-Format und HTML5-Seiten erzeugt.

Um ein Gefühl dafür zu bekommen, wie die erzeugte Dokumentation aussieht, hier ein Auszug aus dem Kapitel 2 des PDF-Dokuments:

PDF eignet sich natürlich gut dafür, eine Gesamtdokumentation zusammen mit dem Software-Paket an einen Kunden auszuliefern, oder an Stakeholder zu verschicken.

Die HTML-Variante kann sehr einfach auf einem Webserver publiziert werden. Wenn dies im Rahmen eines automatischen Build-Prozesses, z. B. im Zuge des CI/CD-Prozesses erfolgt, dann ist sichergestellt, dass immer eine aktuelle Dokumentation zur Verfügung steht. Je nach Art der Dokumentation und Applikation entweder im Intra- oder sogar im Internet.

Hier ebenfalls ein Auszug aus der erzeugten HTML-Dokumentation von Kapitel 2:

Fazit

AsciiDoc in Kombination mit Asciidoctor oder AsciidoctorJ ist eine gute Variante für die Erstellung von technischen Dokumentationen. Die einfache Erzeugung und native Einbindung von Diagrammen erlaubt eine einfache Anpassung und Weiterentwicklung der Dokumentation. Durch die Integration in ein beliebiges Java-Projekt und den entsprechenden Build-Prozess kann die Dokumentation auf Knopfdruck oder sogar vollkommen automatisch generiert werden. Durch ein Quellcodeverwaltungssystem wird die Dokumentation automatisch versioniert und kann zusammen mit dem Programmcode weiterentwickelt werden. Verschiedene Ausgabeformate erlauben ein konsumentengerechtes Format, ohne zu viel Aufwand in Layout und Formatierung investieren zu müssen.


Der Quellcode des Beispiel-Projekts kann auf GitHub heruntergeladen werden: https://github.com/trojava/asciidoctorj-ordix-blog-demo

By accepting you will be accessing a service provided by a third-party external to https://blog.ordix.de/