Technical Documentation as Code
TL;DR use Git, Asciidoc and pipeline extensions to store technical documentation next to the source code and keep it up to date
There is hardly a project, a task, that is possible without appropriate documentation. In source code, formats such as Markdown for ReadMe files have become established — but otherwise binary formats (PDF, MS Office, ect.) dominate.
The more stakeholders work on a document, the more difficult it becomes to choose the right format. We do not want to compete with Confluence or Sharepoint here, instead focus on technical documentation that belongs to a “piece of code” — typically to an application.
System specification, release notes, user manual, migration instructions — there are so many documents whose content overlaps and can only be kept up to date with difficulty.
Wouldn’t it be cool if the documentation updated itself or at least you only had to change it in one place?
There are a lot of helpful links, today I want to present an pragmatic approach we created in our team — a combination of different technologies whose limitation I haven’t found yet myself.
Where to store?
This is the simplest question — of course, close to the code — Git.
Which format do we choose?
One of the most important functionalities will be the import of documents into other documents, the reuse. Accordingly, it is essential that this also works across different folder structures.
Asciidoc (https://asciidoc.org/) offers exactly this feature and many other design options. In addition, it is machine-writable and readable code.
Here’s a straightforward example to include content from another file:
include::myfolder/mydocument.adoc[]
In addition, source management software like Gitlab will automatically do the imports, when showing the document in their GUI. Perfect for non-technical people ;-)
How do we create higher-level documentation?
Of course, there are documentations that do not belong to one component, but are comprehensive. Fortunately, Git offers a suitable feature to connect the different storage locations (for example, repositories of the applications): Git submodules.
The repository structure will look like this:
You will end up having all files available on one disc after checking out the my-overall-release-notes repository including its git-submodules.
Sadly Gitlab is not able to include at runtime from git submodules, direct display of the document will not contain the data from these modules (after checkout it works locally).
How do we structure the documents?
This is the point where I can’t give any meaningful guidelines, basically the more modular the documents are the better. Using Include we can merge arbitrarily over many hierarchy levels.
Typical modules could be:
- architecture drawings (we will come to that one later)
- roles and permissions
- database
- error codes
- properties
- release notes
- system specification (using the other modules)
How do we create deliverable documents?
Ascii-Doctor provides a Docker image to generate PDF from asciidoc files. You should extend the image to be able to generate with extended features.
Dockerfile
FROM docker.io/asciidoctor/docker-asciidoctor
RUN apk add graphicsmagick-dev ruby-dev musl-dev gcc
RUN gem install prawn-gmagick
Usage — generated content stored in folder _gen:
mkdir _gen
docker build — tag asciidoc .
d=$(date +%Y%m%d)
docker run — rm -v $(pwd):/documents/ asciidoc asciidoctor-pdf doc/systemspec/systemspec1.0.adoc -o _gen/systemspec1.0-$d.pdf
How do we create diagrams?
Diagrams.net can be comfortably edited via IDE plugins and stored as editable SVG or PNG in the code. Gitlab or other source management systems will have plugins or native functionalities to directly display them in GUI — so you can simply import them in Markdown or Asciidoc.
How do we generate content?
There are scenarios in which it is worthwhile to generate documentation from code: properties, error codes, database columns, …
Since I don’t want to take the fun of programming away from you, here is my approach and not the finished solution. In my case I’m using Maven plugins to do the task automatically in pipeline…
Extend every property with a comment describing it and the allowed values. Mandatory properties could use variables like ${mandatory1}.
Using this information you can create a table like this and include it in any document you like to:
[cols=”1,1,1"]
|===
| property | description | default
| application.name | name of the application | my supercool application
| application.loglevel | loglevel of the application (INFO,WARN,ERROR) | WARN
| application.mandatory1 | mandatory property of the application | ${mandatory1}
|===
Same applies for any kind of constants in code, add comments and simply use the code to create Asciidoc tables.
Want to have a challenge?
Try out using SQL scripts as basis to create Asciidoc tables for every database table containing the columns and their data types and default values.
Putting it all together…
We are able to:
- store the documents next to our code (including version management)
- generate content from code
- include content into each other
- use diagrams and edit in IDE
- read documents directly within GUI of our source code management platform (for example Gitlab)
- work with multiple repositories
- generate deliverables like PDF files
We hope that this article will simplify your work with technical documentation as much as possible, because let’s be honest — who likes to write documentation?
Robert