Blog Arolla

Recette de documentation vivante avec Spring Boot (adaptable et gluten free)

Entrée.

Chères lectrices, chers lecteurs, je vais aujourd'hui partager avec vous une recette de ma grand-mère, qui m'a appris à sublimer les rouleaux de printemps au concombre. Pour cela, nous allons avoir besoin des ingrédients suivants :

  • Asciidoctor
  • Cucumber
  • SpringBoot (Spring MVC en fait, mais comme est on en pleine hype microservices, faut être SWAG, sinon j'aurais dit JHipster)

Mais avant de commencer à trancher dans le lard, intéressons-nous à la base-même du plat du jour, AsciiDoc : AsciiDoc est un langage balisé léger du même gabarit que le plus connu MarkDown. N'étant un expert ni de l'un ni de l'autre, je ne vais pas créer un débat de goûts et sensations culinaires de chacun d'eux. Sachez juste que selon les auteurs, AsciiDoc a été pensé pour créer toute sorte de document, de la plus simple des notes à (et surtout) une publication entière et professionnelle. Il est complet (c'est à dire sans matière grasse à ajouter) vis à vis de MarkDown et de ce fait, plus complexe. Mais ce qu'il faut retenir avant tout, c'est que l'un ou l'autre vous permettra d'avoir la possibilité de traiter le document que vous écrivez comme du code qui pourrait être versionné : vous pouvez donc utiliser n'importe quel outil de comparaison pour regarder les différences entre 2 versions. Par contre, oubliez les éditeurs WYSIWYG car les langages balisés légers sont faits pour être facilement utilisables et pour se passer des éditeurs de texte comme Word.

Notre ingrédient principal est donc AsciiDoctor, un outil open-source permettant de transformer des documents AsciiDoc en documents HTML5, PDF, EPUB3 et autres. Il se différencie grâce à son intégration simplifiée à des projets à base de Ruby, Javascript, Java et autres langages basés sur la JVM. Il permet aussi d'étendre le langage AsciiDoc afin de pouvoir récupérer une partie ou l'ensemble d'un fichier tierce, se révélant alors très pratique pour extraire des parties de votre code source. Il peut aussi être étendu afin d'intégrer des graphes ou encore de customiser la sortie afin de créer une présentation slideshow.
Son créateur, Dan Allen, est très actif pour améliorer sans cesse l'outil. Il fait aussi beaucoup de présentations sur ce sujet, notamment aux derniers Devoxx France, Belgique et Maroc.

Lorsque vous faites du BDD, il y aura forcément un moment où vous avez besoin de décrire vos scénarios et allez en profiter pour baser vos tests dessus. Pour son côté rafraichissant et peu calorique, Cucumber est une solution de choix, avec l'écriture des fameux Given-When-Then en syntaxe Gherkin. Ici, nous allons transformer les scénarios et leurs résultats en documentation grâce à CukeDoctor. CukeDoctor s'appuie sur AsciiDoctor pour transformer les résultats JSON de Cucumber en document HTML5 qui indiqueront, à la manière d'un report, les différentes étapes et le résultat de chaque scénario.

Enfin, pour le dressage et la cuisson, nous allons utiliser Spring Boot et pour documenter son API REST basée sur Spring MVC, nous ajouterons Spring REST Docs. Ce projet et plugin maven va vous permettre de faire 2 choses à la fois : tester votre API REST (comme Spring MVC Tests) et en sortir une documentation AsciiDoctor. Chacun de vos tests sur les controllers va générer des mini-documents AsciiDoctor que vous importerez afin de donner des exemples d'appels et d'indiquer les différents paramètres, leur signification et les valeurs qu'elles peuvent prendre. Fini donc le plat préparé Swagger du supermarché, place à l'authentique documentation à la manière de GitHub !

Plat principal

Sortez vos outils, vos tablettes à découper et de quoi noter. Commençons donc par télécharger un moule tout fait d'application maven avec spring boot web (pensez donc à sélectionner le starter Web) sur https://start.spring.io/.

AsciiDoctor

Nous allons maintenant nous intéresser à écrire une documentation remplaçant les spécifications fonctionnelles et techniques en asciidoctor.
Pour cela, le plus simple est de rajouter un plugin maven au pom.xml (dans la partie build > plugins donc):

<plugin>
    <groupId>org.asciidoctor</groupId>
    <artifactId>asciidoctor-maven-plugin</artifactId>
    <version>1.5.3</version>
    <executions>
        <execution>
            <id>ascidoctor-html5-documentation</id>
            <phase>generate-resources</phase>
            <goals>
                <goal>process-asciidoc</goal>
            </goals>
            <configuration>
                <sourceDirectory>src/main/asciidoc/specifications</sourceDirectory>
                <backend>html5</backend>
                <sourceHighlighter>highlightjs</sourceHighlighter>
                <preserveDirectories>true</preserveDirectories>
                <attributes>
                    <sourceDir>${project.basedir}/src/main/java</sourceDir>
                    <data-uri/>
                    <toc2/>
                    <toclevels>4</toclevels>
                </attributes>
            </configuration>
        </execution>
    </executions>
</plugin>

L'exécution ascidoctor-html5-documentation permet de générer un document HTML5 (le paramètre backend) à partir du dossier source situé dans src/main/asciidoc/specifications, de préserver la hiérarchie des répertoires sources d'AsciiDoctor et d'avoir une table des matières à 4 niveaux, affichée sur le côté (toc2).1.

Voilà ! Maintenant vous pouvez commencer à écrire de l'asciidoctor dans un fichier .adoc:

= Projet DEMO - Spécifications Fonctionnelles
================
:author: Yvan VU
:data-uri:
:icons: font

== Introduction

=== Objet du document

Le présent document constitue les spécifications fonctionnelles de l'application DEMO.

=== Documents de référence

D'autres documents sont disponibles pour l'application DEMO :

[cols="1,2", options="header"]
|===
|Titre
|Description
|<<../techniques/index.adoc#,Spécifications_techniques>>
|Spécification techniques du projet DEMO
|===

////
TODO : mettre le lien vers les documents en question
|<<../scenarios.adoc#, Rapport cucumber >>
|Rapport de scénarios du projet DEMO
|<<../index.adoc#,api_rest_docs >>
|Documentation de l'API REST du projet DEMO
////

== Description générale

=== Enjeux

...

=== Usage

TIP: ceci est un pro-tip

==== API REST

l'API REST permettra à chaque client d'utiliser l'application via des appels #http#.

==== FizzBuzz

Lorsque l'on appelle le service FizzBuzz, on lui donne en paramètre un entier. Le service retournera alors :

* l'entier passé en paramètre *sauf*
* si le paramètre est un multiple de *3*, le service retournera *Fizz*
* si le paramètre est un multiple de *5*, le service retournera *Buzz*
* si le paramètre est un multiple de *3 et 5*, le service retournera *FizzBuzz*

Comme vous pouvez le voir, la syntaxe d'AsciiDoctor a été faire pour être la plus simple possible afin de ne pas polluer l'information. Les niveaux des titres sont définis par les = et nous avons entre autres créé un tableau, ( les |=== et | ), des commentaires et une liste via *.

On peut aussi extraire une partie du code source :
(dans le fichier .adoc)

[source,java]
----
include::{sourceDir}/com/example/domain/FizzBuzz.java[tags=implementation]
----

include permet donc d'inclure le contenu d'un fichier dans la documentation, grâce aux tags, on ira chercher le code java situé entre les balises // tag::implementation[] et // end::implementation[] dans la classe FizzBuzz.

Comme on a défini l’exécution du plugin durant la phase generate-resources, la transformation des documents en html se fera avant la compilation.

Via le build maven, après un simple maven compile, on pourra voir un joli document html se générer dans target/generated-docs/fonctionnelles/index.html :
SortieHtmlAsciiDoctor

et pour l'extrait de code :
FizzBuzz-java

Voilà, avec un peu de pratique vous maîtriserez AsciiDoctor et écrirez vos propres recettes de cuisine ! Maintenant vous n'avez plus qu'à convaincre votre équipe ou toute personne non technique d'écrire la documentation en asciidoctor et non plus sous word !

CukeDoctor

Vous avez donc vos plats à base de Cucumber et des scénarios Gherkin qui n'attendent qu'à être sortis dans une jolie page web montrant que tout est beau :
scenariosGherkins

Là aussi, on va ajouter le plugin maven suivant 2:

&lt;plugin&gt;
    &lt;groupId&gt;com.github.cukedoctor&lt;/groupId&gt;
    &lt;artifactId&gt;cukedoctor-maven-plugin&lt;/artifactId&gt;
    &lt;version&gt;0.6.1&lt;/version&gt;
    &lt;configuration&gt;
        &lt;outputFileName&gt;scenarios&lt;/outputFileName&gt;
        &lt;outputDir&gt;generated-docs&lt;/outputDir&gt;
        &lt;toc&gt;left&lt;/toc&gt;
        &lt;numbered&gt;true&lt;/numbered&gt;
    &lt;/configuration&gt;
    &lt;executions&gt;
        &lt;execution&gt;
            &lt;goals&gt;
                &lt;goal&gt;execute&lt;/goal&gt;
            &lt;/goals&gt;
            &lt;phase&gt;install&lt;/phase&gt;
        &lt;/execution&gt;
    &lt;/executions&gt;
&lt;/plugin&gt;

La configuration ici présente indique que l'on va générer deux fichiers scenarios.adoc et scenarios.html où tout le report des tests Cucumber sera présent.

Le travail supplémentaire s'arrête là : vous avez déjà écrit tout ce qu'il fallait. Vous avez juste à lancer le build maven install et vous obtiendrez alors la sortie suivante (toujours dans target/generated-docs/scenarios.html :
SortieCukeDoctor

Bon, je vous l'accorde, ceci n'était que pour vous montrer qu'on peut intégrer dans son build l'automatisation de tâches AsciiDoctor, car cela reste un report assez basique au final.
Mais comme il y aussi la génération d'un .adoc à côté du .html, vous pourrez toujours l'importer dans un document asciidoctor, moyennant quelques configurations à faire.
MarquePouce

Il existe bien sûr d'autres alternatives pour documenter ses tests et rapports Cucumber, du plugin Jenkins au projet github d'un collègue.

Retrouvons-nous après une courte page de publicité : Vous souhaitez en faire plus avec vos concombres ? Vous en avez marre de juste les découper en rondelle et les présenter en apéro à ses amis? Découvrez Tzatziki, une recette onctueuse préparé par Arnauld Loyer lui-même !

Spring REST Docs

Attention, c'est à cet endroit qu'on utilise notre ingrédient principal, l'ingrédient que l'on aime tous : Spring !

Les rouleaux de printemps, c'est facile à faire. Idem ici aussi, on commence par rajouter la levure chimique à notre maven puis 2 nouvelles tâches à exécuter dans la partie build (qui commence à grossir maintenant...) :
l'une pour définir et filtrer les classes de tests pour Spring REST Docs, l'autre, qui est une nouvelle exécution du plugin AsciiDoctor, pour transformer le document de l'API REST en html.

&lt;!-- dans dependencies --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.restdocs&lt;/groupId&gt;
    &lt;artifactId&gt;spring-restdocs-mockmvc&lt;/artifactId&gt;
    &lt;version&gt;1.0.0.RELEASE&lt;/version&gt;
    &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;
&lt;!-- à ajouter dans executions de asciidoctor-maven-plugin, après l&#039;execution du ascidoctor-html5-documentation --&gt;
&lt;execution&gt;
    &lt;id&gt;rest-api&lt;/id&gt;
    &lt;phase&gt;package&lt;/phase&gt;
    &lt;goals&gt;
        &lt;goal&gt;process-asciidoc&lt;/goal&gt;
    &lt;/goals&gt;
    &lt;configuration&gt;
        &lt;sourceDirectory&gt;src/main/asciidoc/api&lt;/sourceDirectory&gt;
        &lt;backend&gt;html5&lt;/backend&gt;
        &lt;sourceHighlighter&gt;highlightjs&lt;/sourceHighlighter&gt;
        &lt;attributes&gt;
            &lt;sourceDir&gt;${project.basedir}/src/main/java&lt;/sourceDir&gt;
            &lt;generatedDir&gt;${project.build.directory}/generated-snippets&lt;/generatedDir&gt;
            &lt;data-uri/&gt;
            &lt;toc2/&gt;
            &lt;toclevels&gt;4&lt;/toclevels&gt;
        &lt;/attributes&gt;
    &lt;/configuration&gt;
&lt;/execution&gt;
&lt;!-- à ajouter build &gt; plugins --&gt;
&lt;plugin&gt;
    &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
    &lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;
    &lt;version&gt;2.18.1&lt;/version&gt;
    &lt;executions&gt;
        &lt;!--tests for api rest documentation--&gt;
        &lt;execution&gt;
            &lt;id&gt;api-rest-documentation-tests&lt;/id&gt;
            &lt;goals&gt;
                &lt;goal&gt;test&lt;/goal&gt;
            &lt;/goals&gt;
            &lt;configuration&gt;
                &lt;includes&gt;
                    &lt;include&gt;**/*RESTDocumentation.java&lt;/include&gt;
                &lt;/includes&gt;
                &lt;systemPropertyVariables&gt;
                    &lt;org.springframework.restdocs.outputDir&gt;
                        ${project.build.directory}/generated-snippets
                    &lt;/org.springframework.restdocs.outputDir&gt;
                &lt;/systemPropertyVariables&gt;
            &lt;/configuration&gt;
        &lt;/execution&gt;
    &lt;/executions&gt;
&lt;/plugin&gt;

L’exécution supplémentaire de AsciiDoctor pourrait être fusionnée avec celle de la 1ère étape : si vous souhaitez le faire, vous devrez déplacer la génération asciidoctor après la phase de tests. En effet, la phase de tests génère les rapports Cucumber et Spring Rest Docs en asciidoctor, la transformation asciidoctor pourra alors l'inclure et s'exécuter dans une phase tardive comme la package.

Nos tests de webservices REST réalisés avec mockmvc sont donc écrits comme ceci :

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {DemoApplication.class})
@WebAppConfiguration
public class FizzBuzzRestTests {

    @Rule
    public final RestDocumentation restDocumentation = new RestDocumentation(&quot;target/generated-snippets&quot;);
    @Autowired
    private WebApplicationContext context;
    @Autowired
    private ObjectMapper objectMapper;
    private MockMvc mockMvc;

    @Before
    public void setUp() {
        objectMapper.findAndRegisterModules();
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
                .apply(documentationConfiguration(this.restDocumentation))
                .build();
    }

    @Test
    public void checkCodeValiditySimple() throws Exception {
        this.mockMvc.perform(
                get(&quot;/fizzbuzz/{input}&quot;, 30)
                        .accept(MediaType.APPLICATION_JSON)
                        .contentType(MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(content().string(&quot;{&quot;result&quot;:&quot;FizzBuzz&quot;}&quot;))
                .andDo(document(&quot;fizz-buzz&quot;,
                                pathParameters(
                                        parameterWithName(&quot;input&quot;).description(&quot;Un paramètre entier.&quot;)),
                                responseFields(
                                        fieldWithPath(&quot;result&quot;).description(&quot;Le résultat de l&#039;appel : L&#039;entier passé en paramètre, Fizz, Buzz ou FizzBuzz&quot;))
                        )
                );
    }

}

Ici, dans le test reposant sur mockmvc du controller correspondant à /fizzbuzz/{entier}, on a ajouté dans le @Before la configuration pour utiliser REST Docs. Puis dans le test, on documente dans le andDo(document(...)) les paramètres en entrée et sortie. Cela veut dire que si vous avez déjà des tests sur vos controllers Spring MVC, vous n'avez qu'à rajouter cette partie.
Pour l'exemple, on retourne un object JSON contenant le résultat du fizzbuzz.

Lorsque ce test sera exécuté via maven, plusieurs documents seront générés dans target/generated-snippets/fizz-buzz:

  • curl-request.adoc
  • http-request.adoc
  • http-response.adoc
  • path-parameters.adoc
  • response-fields.adoc

Vous pouvez alors utiliser ces documents en les incluant à votre documentation pour l'API REST :

=== GET /fizzbuzz/{entier}

Requête GET pour récupérer le résultat d&#039;un fizzbuzz particulier d&#039;un {entier} passé en paramètre de l&#039;URL.

Requête curl :
include::{generatedDir}/fizz-buzz/curl-request.adoc[]

Requête http :
include::{generatedDir}/fizz-buzz/http-request.adoc[]

Paramètres :
include::{generatedDir}/fizz-buzz/path-parameters.adoc[]

&#039;&#039;&#039;

Réponse http :
include::{generatedDir}/fizz-buzz/http-response.adoc[]

Champs de la réponse :
include::{generatedDir}/fizz-buzz/response-fields.adoc[]

=== POST ...

...

Le plugin maven va donc transformer ces tests en une documentation asciidoctor (qui sera à son tour transformée en html via l'étape du plugin maven asciidoctor) consultable dans target/generated-docs/index.html :
SortieRestDocs

Spring Rest Docs est capable de documenter les paramètres dans le path aussi bien que dans la request, de gérer l'hypermedia, les headers et les payload en JSON ou XML.
Votre exemple d'appel pouvant très bien comporter des champs optionnels, vous pouvez spécifier le type d'un champ afin que Spring Rest Docs ne le mette pas à Null.
Du coup, vous pouvez faire plusieurs exemples d'appels et puisque vous avez juste à extraire la partie document() dans une méthode, vous ne documentez qu'à un seul endroit du code !

Avec ceci, vous avez une documentation de votre API REST qui est claire, concise, et qui, en se reposant sur vos tests, devient vivante car tout changement d'api vous obligera à revoir la documentation de vos exemples.

Dessert

Grâce à ces 3 solutions, vous avez maintenant la possibilité d'avoir une documentation vivante de votre code qui servira :

  • au métier pour la partie spécifications (et évitera d'aller rechercher dans vos mails le fameux "[FINAL]LES_VERITABLES_ET_UNIQUE_COMPLETE_specifications_v13.87(copier)(6).docx")
  • aux autres développeurs qui interviendront tôt ou tard sur le code et pourront s'appuyer sur les différents documents
  • aux personnes qui utiliseront votre API et auront des exemples d'appels

Vous venez donc de sublimer votre délicieux projet Spring. Vous ne l'utilisez pas à votre travail ? Pas de problème, la 1ère partie est la plus importante et la plus générique.

On peut aussi noter que l'ensemble de ces solutions n'impactent aucunement l'environnement de travail puisqu'elles sont directement intégrées via des plugins maven.

Reste ensuite que votre documentation doit être déposée sur un serveur web accessible par les personnes visées. Personnellement (et en tant que réinventeur d'omelette), j'ai intégré un script à la fin de mon build Jenkins pour copier les pages html sur un répertoire de serveur apache disponible en intranet. Sinon, vous pouvez toujours l'intégrer à un maven site.

Vous pouvez retrouver le code utilisé pour cet article sur ce GitHub.


  1. Vous trouverez plus d'exemples de configuration à l'adresse github du projet : https://github.com/asciidoctor/asciidoctor-maven-plugin
  2. Lien github du plugin Maven CukeDoctor: https://github.com/rmpestano/cukedoctor/tree/master/cukedoctor-maven-plugin
Plus de publications

5 comments for “Recette de documentation vivante avec Spring Boot (adaptable et gluten free)

  1. Clément HELIOU
    17 février 2016 at 23 h 24 min

    Hello Yvan.

    Merci pour cet article.
    La documentation vivante est en effet quelque chose de primordial dont on entend que trop peu parler.
    J’aurais néanmoins une petite remarque.

    Si l’objectif de cet article est de parler de documentation vivante, je trouve surprenant de lire:

    “Nous allons maintenant nous intéresser à écrire une documentation remplaçant les spécifications fonctionnelles et techniques en asciidoctor”.

    Ce genre de règles de gestion écrites en dur, qu’elles le soient en .doc ou .adoc, c’est une documentation morte par définition !
    Elles n’ont donc, selon moi, pas leur place dans une documentation dite vivante et constituent même un obstacle dans le chemin de migration vers des spécifications par l’exemple, c’est à dire via les scénarios BDD. Pourquoi les gens du métier prendraient-ils la peine de collaborer pour éliciter les besoins et écrire des scénarios si l’on continue d’accepter leurs bonnes vielles specs ?!

  2. Yvan Vu
    19 février 2016 at 10 h 47 min

    Bonjour Clément, merci beaucoup de ton retour.
    Ta remarque est pertinente : j’ai tendance à vouloir élargir ma vision de la documentation vivante pour aller au delà du principe “la documentation est le code”.
    D’un point de vue développeur, oui la meilleure documentation sera la documentation vivante, générée à partir du code et des tests afin d’entrer rapidement dans le projet.
    Mais si je me place dans la peau d’un non développeur (ou pas), desfois j’aurais envie d’avoir un truc succinct, comme lorsque j’ouvre le manuel d’un produit : on me présente brièvement de que je peux faire, sans pour autant rentrer dans les détails.
    Du coup le contenu du document remplaçant les spécifications fonctionnelles et techniques a toute sa raison d’exister : décrire ce que le code ne peut pas expliquer. C’est à dire des idées globales, le contexte du projet, l’historique, l’architecture autour, le choix de design, etc.
    Alors oui ce n’est plus à proprement parler du domaine de la documentation vivante, mais pourquoi mettre cela ailleurs si on peut le mettre avec ? Surtout qu’on peut faire en asciidoctor tout ce qu’on peut faire avec un éditeur de texte.

  3. Fabien
    19 février 2016 at 17 h 57 min

    Merci Yvan pour ce tutos.
    Je me permet de compléter ta réponse à Clément:
    “Ce genre de règles de gestion écrites en dur, qu’elles le soient en .doc ou .adoc, c’est une documentation morte par définition !”
    Les grandes plus-value d’un .adoc sont les suivantes:
    – fichier non binaire: il est donc plus facile d’effectuer des modifications concurrentes que sur des .doc, ce qui induit le point suivant
    – fichier versionnables sur git: tu peux donc extraire, à un instant t, une documentation datant de la version de code en question.
    Il m’est déjà arrivé de devoir corriger un bug sur une version antérieure (t-1) dont la documentation, pour une fois à jours, ne correspondait plus du tout à l’état de l’époque (t-1)…mais à l’état de la version en cours (t).
    Avoir une documentation versionnée avec la même politique de branches et de tags m’aurait à l’époque bien aidé.
    C’est donc un pas en avant non négligeable, même si en effet ce type de spécifications est voué à être peu à peu remplacé.
    A nous bien sur de choisir le support adapté à chaque information

  4. christophe michel
    25 mars 2016 at 17 h 07 min

    A noter que Spring REST Docs nécessite Spring Framework 4.1 minimum.

  5. Clément
    16 novembre 2021 at 16 h 53 min

    Existe-t-il un plugin markdown remplacant ascii-doc pour les spécifications d’une api Rest ? Merci pour le partage