Générer vos clients d'API à partir de leur spécification OpenAPI

Publié le 23/04/2025 Source : sfeir.dev

Il y a de cela fort longtemps, j’avais écrit un article sur comment réussir sa migration de swagger 2 vers OpenAPI 3 :

Réussir sa migration de Swagger 2 à OpenApi 3

Et à la fin de ce dernier, je vous promettais un futur article sur la génération de clients d’API depuis leur spécification OpenAPI, et bien nous y voilà.


Dans le développement d’applications modernes, les API RESTjouent un rôle central pour permettre la communication entre différents services. Cependant, écrire manuellement des clients pour interagir avec ces API peut être fastidieux et source d’erreurs.
C’est là qu’interviennent des outils comme le plugin Maven openapi-generator-maven-plugin, basé sur la spécification OpenAPI. Cet article explore pourquoi et comment utiliser ce plugin, ses avantages et inconvénients, ainsi qu’un cas pratique pour illustrer son application.

Pourquoi générer ses clients d’API ?

La génération automatique de clients d’API repose sur une idée simple : transformer une spécification formelle (comme un fichier OpenAPI au format JSON ou YAML) en code prêt à l’emploi. Plutôt que de coder à la main des appels HTTP avec des bibliothèques comme RestTemplate ou HttpClient, ou de définir manuellement des modèles de données, les développeurs peuvent s’appuyer sur des outils qui produisent ce code de manière cohérente et rapide.
Cela répond à plusieurs besoins :

⚖️ Avantages et inconvénients

➕Les avantages

L’utilisation d’un générateur comme openapi-generator-maven-plugin offre plusieurs atouts :

➖ Les inconvénients

Malgré ses nombreux avantages, le plugin présente aussi quelques limites :

Le plugin Maven openapi-generator-maven-plugin

Le plugin Maven openapi-generator-maven-plugin, développé par OpenAPITools, est un outil puissant pour générer des clients, des serveurs ou des modèles à partir de spécifications OpenAPI.
Intégré au cycle de vie de Maven, il s’exécute typiquement durant la phase generate-sources, produisant du code dans un dossier comme target/generated-sources.

](https://github.com/OpenAPITools/openapi-generator/tree/master?ref=sfeir.dev)

Projet GitHub du plugin maven

Configuration de base

Voici un exemple minimal dans un pom.xml :

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>7.4.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>
                <generatorName>spring</generatorName>
                <apiPackage>com.example.api</apiPackage>
                <modelPackage>com.example.model</modelPackage>
            </configuration>
        </execution>
    </executions>
</plugin>

Exemple de configuration

Le plugin supporte de nombreuses options, comme <library> pour choisir une implémentation spécifique (ex. spring-cloud pour OpenFeign) ou <configOptions> pour des réglages fins.

Templating avec Mustache

Un atout majeur du plugin est sa capacité à personnaliser le code généré grâce à des fichiers de template Mustache.
Par défaut, il utilise des templates prédéfinis pour chaque générateur, mais vous pouvez les surcharger en spécifiant un répertoire via <templateDirectory>.
Ces fichiers Mustache (avec l’extension .mustache) permettent de modifier la structure du code généré, comme ajouter des annotations personnalisées ou ajuster les conventions de nommage.
Par exemple, pour modifier le template d’une interface Spring, vous pouvez copier le template par défaut depuis le dépôt GitHub d’OpenAPI Generator, le personnaliser, et l’indiquer dans la configuration. Cela offre une flexibilité immense, bien que cela demande une bonne compréhension des variables disponibles dans le contexte Mustache.

Cas pratique

Imaginons un projet Spring Boot qui doit consommer une API de gestion de livres. La spécification OpenAPI (books.json) définit des endpoints comme GET /books/{authorId} et POST /books. Voici comment utiliser le plugin :

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>7.12.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/swagger/books.json</inputSpec>
                <generatorName>spring</generatorName>
                <configOptions>
                    <sourceFolder>src/gen/main/java</sourceFolder>
                    <configPackage>fr.eletutour.clientgenerationtutorial.author.config</configPackage>
                    <dateLibrary>java8</dateLibrary>
                    <unhandledException>true</unhandledException>
                    <developerOrganization>books</developerOrganization>
                    <useTags>true</useTags>
                    <interfaceOnly>false</interfaceOnly>
                    <useResponseEntity>false</useResponseEntity>
                    <useJakartaEe>true</useJakartaEe>
                </configOptions>
                <library>spring-cloud</library>
                <apiPackage>fr.eletutour.book.consumer.api</apiPackage>
                <modelPackage>fr.eletutour.books.consumer.model</modelPackage>
            </configuration>
        </execution>
    </executions>
</plugin>

Analyse de la configuration

Cette configuration utilise la version 7.12.0 (la dernière à l’heure actuelle) du plugin pour générer un client Spring compatible avec OpenFeign (<library>spring-cloud>).
Le fichier books.json dans src/main/resources/swagger sert de spécification. Les options <configOptions> permettent des réglages précis :

Utilisation dans le code

@Service
public class AuthorService {
    private static final Logger logger = LoggerFactory.getLogger(AuthorService.class);
    private final AuthorRepository authorRepository;
    private final BookManagementApi bookManagementApiClient;
    private final AuthorMapper authorMapper = AuthorMapper.INSTANCE;

    public AuthorService(AuthorRepository authorRepository, BookManagementApi bookManagementApiClient) {
        this.authorRepository = authorRepository;
        this.bookManagementApiClient = bookManagementApiClient;
    }

    /**
     * Récupère un auteur par son ID.
     * @param id ID de l'auteur
     * @return Auteur correspondant
     * @throws AuthorException si l'auteur n'est pas trouvé
     */
    public Author getAuthorById(Long id) throws AuthorException {
        logger.info("Récupération de l'auteur avec l'ID {}", id);
        AuthorEntity authorEntity = authorRepository.findById(id)
                .orElseThrow(() -> {
                    logger.warn("Auteur non trouvé pour l'ID {}", id);
                    return new AuthorException(AuthorException.AuthorError.AUTHOR_NOT_FOUND, "Auteur non trouvé pour l'ID : " + id);
                });
        Author author = authorMapper.toAuthor(authorEntity);
        return enrichAuthorWithBooks(author);
    }


    /**
     * Enrichit un auteur avec ses livres en appelant l'API externe.
     * @param author Auteur à enrichir
     * @return Auteur avec la liste des livres
     */
    private Author enrichAuthorWithBooks(Author author) {
        try {
            logger.debug("Récupération des livres pour l'auteur avec l'ID {}", author.getId());
            List<Book> books = bookManagementApiClient.getBooksByAuthorId(author.getId());
            author.setBooks(books);
            logger.debug("Ajout de {} livres à l'auteur avec l'ID {}", books.size(), author.getId());
        } catch (Exception e) {
            logger.error("Erreur lors de la récupération des livres pour l'auteur avec l'ID {}", author.getId(), e);
            throw new RuntimeException("Erreur lors de la récupération des livres pour l'auteur " + author.getId(), e);
        }
        return author;
    }
}
@SpringBootApplication
@EnableFeignClients
public class AuthorDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthorDemoApplication.class, args);
    }
}

Résultat

Le client généré appelle l’API via OpenFeign, et les modèles (comme Book) sont automatiquement mappés depuis le JSON. Si la spécification change, un simple mvn clean generate-sources met à jour le client.

Conclusion

L’utilisation d’un générateur de clients REST basé sur OpenAPI offre un gain de temps considérable en automatisant la création du code, tout en réduisant les risques d’erreurs humaines. Grâce à cet outil, il devient plus simple de maintenir la cohérence entre le client et le serveur, même lorsque l’API évolue.

Dans un projet Spring Boot, OpenAPI Generator s’intègre parfaitement avec des solutions modernes comme Feign, facilitant la communication avec des services distants tout en adoptant les bonnes pratiques actuelles.

Bien entendu, cette approche ne convient pas à tous les cas d’usage. Pour des API très simples ou peu sujettes à des évolutions, un client manuel basé sur RestTemplateWebClient ou Feign peut être une alternative viable. Cependant, pour des systèmes complexes où la synchronisation avec un contrat OpenAPI est essentielle, l’utilisation d’un générateur devient un véritable atout.