AsyncAPI : Comment standardiser les API asynchrones ?

Je vous parle d’un temps que les moins de 20 ans ne peuvent pas connaître : Swagger n’existait pas encore, les entreprises avaient chacune leur standard de description d’API REST et la compréhension, la consommation et la documentation de ces APIs étaient autant de challenges.

Dans un monde où APIsation rime avec Obsession, les utilisateurs - développeurs mais aussi experts métiers - ont exigé des APIs normalisées et simples à comprendre. Les APIs synchrones, REST en étant l’un des exemples le plus mature, se sont ainsi standardisées et outillées pour présenter l’écosystème complet que nous connaissons aujourd’hui. Elles ont ainsi atteint un fort niveau de maturité, là où les APIs asynchrones semblent quelque peu laissées pour compte.

Cependant, les problématiques du passé ressurgissent avec force à l’heure de l’avènement des architectures évènementielles et de l’IoT : il devient primordial de pouvoir normaliser la définition des APIs asynchrones afin d’aboutir à un écosystème standardisé facilitant la compréhension de ces interfaces et favorisant l’outillage et l’automatisation des traitements.

Là où les APIs synchrones sont toutes basées sur le protocole HTTP, les APIs asynchrones sont quant à elles basées sur une grande variété de technologies et de protocoles différents (MQTT, Kafka, Streaming, etc) qui complexifie la possibilité d’une standardisation globale. Il est d’ailleurs commun de voir différentes implémentations à l'œuvre au sein d’une même architecture, et il est donc crucial de pouvoir établir un standard agnostique des technologies et protocoles utilisés pour décrire uniformément ces interfaces asynchrones.

L’initiative AsyncAPI

Ce challenge est celui que l'initiative AsyncAPI cherche à relever au travers de l’AsyncAPI Specification (A2S).

AsyncAPI définit un format standard et Open source, accessible et compréhensible par tout un chacun, pour la représentation des APIs asynchrones qui décrit les notions fondamentales de ces types d’interfaces :

  • But de l’API, ses caractéristiques
  • Message : l’information structurée qui va transiter entre systèmes
  • Protocole : le type de communication utilisé pour transférer les messages
  • Opération : Publish ou Subscribe pour décrire les actions réalisables dans un contexte asynchrone
  • Channel : Composant adressable pour publier ou consommer un message, pouvant par exemple correspondre à un topic Kafka

La force de ce contrat d’API, outre le fait qu’il soit indépendant de tout langage de développement comme peut l’être l’OpenAPI Specification (OAS), est donc d’être utilisable quel que soit le protocole de messagerie. Attention, la notion de message dans le cadre d’AsyncAPI est à prendre dans le sens large du terme : la spécification couvre bien entendu les interfaces mettant en jeu des Middlewares Orientés Messages (Kafka, RabbitMQ, etc) mais veut également couvrir les cas d’usages plus larges comme les interfaces de Streaming, via le protocole WebSocket ou en utilisant les server-sent events par exemple.

A2S n’est pas seulement une nouvelle spécification, elle vise réellement à fédérer le monde des interfaces asynchrones autour de standards qui soient accessibles et inclusifs.

Accessibilité : The “OAS Sister Spec”

AsyncAPI Specification n’est pas issue d’un Big Bang, elle a été créée à partir de la success story OpenAPI Specification : le but n’est pas de réinventer la roue, mais bien de capitaliser sur ce qui a fonctionné pour les architectures REST, et de l’adapter à un monde pluri-protocolaire aux paradigmes sensiblement différents.

A2S dérive donc d’OAS et cherche à conserver le maximum de compatibilité avec elle afin de favoriser la réutilisabilité : cela permet par exemple de mutualiser les schémas de données, l’information véhiculée par un message ou échangée dans des opérations REST étant souvent similaire.

alt_text

Comparaison OpenAPI 3.0 - AsyncAPI 2.0 (tirée de la documentation)

En comparant les structures de chacune des spécifications, on note de nombreuses similitudes qui vont favoriser l’adoption d’A2S en proposant une courbe d’apprentissage rapide pour les personnes familières à OpenAPI et en facilitant le développement d’outils sur base de ce qui peut déjà exister pour les API REST.

Inclusion : une interface normée, des messages libres

Le but de l’AsyncAPI Specification est de définir un standard de description des APIs asynchrones, sans aller jusqu’à imposer un format spécifique de description d’un message (payload).

De nombreux acteurs sont déjà établis autour de la standardisation des schémas de messages. Essayer d’imposer un format unique serait contre-productif à la fois pour AsyncAPI et pour la communauté en démarrant une compétition qui n’a pas lieu d’être et qui serait dommageable à l’essor de l’outillage des API asynchrones.

A2S décrit donc une section libre “Payload” dans sa spécification, dédiée à la description du schéma de données et qui peut accepter n’importe quel format de message : custom, A2S mais également des schémas CloudEvents ou Apache Avro par exemple. Les technologies sont ainsi compatibles et complémentaires et ne représentent pas de freins l’une pour l’autre dans leur implémentation.

Outillage

La mise en place de l’AsyncAPI Specification est un premier pas fondateur dans la normalisation des contrats d’interfaces asynchrones. Cependant, afin de s’imposer dans un marché concurrentiel et sans cesse à la recherche d’optimisation et d’automatisation, son adoption ne saurait être complète sans l’outillage idoine.

Celui-ci est en constante évolution, mais une base de travail solide est d’ores et déjà disponible :

  • Async API Generator : Sur base d’un fichier de spécification, l’outil permet de générer une documentation au format HTML ou Markdown, mais également d'accélérer les développements en générant du code d’implémentation dans plusieurs langages. Pour les cas d’usages moins standards, la création de template custom permet aux utilisateurs de générer ce qu’ils souhaitent.
  • AsyncAPI Playground : Outil en ligne de manipulation de fichier de spécification et de visualisation de la documentation générée via une console interactive
  • Code-first tools: Un ensemble d’outils permettant la génération des documents AsyncAPI à partir du code source dans plusieurs langages de développements (Java, C#, Kotlin, etc)
  • Mocking: Mickrocks propose la création de mocks à partir de contrats d’API et intègre désormais la Spécification AsyncAPI
  • Autres : Parsers, Convertisseurs, GitHub Actions, etc

Le prochain outil très attendu par la communauté, l’AsyncAPI Studio, est actuellement en cours de développement et devrait faciliter l’approche “Design-First” en proposant un outil de modélisation des contrats d’interfaces et des architectures orientées événements.

La multiplication et la démocratisation de ces outils vont permettre à la spécification de se développer encore plus vite et d’atteindre de nouveaux acteurs afin de pouvoir éventuellement s’imposer comme un standard.

Let’s get Dirty

Maintenant que nous avons introduit la spécification, décortiquons un peu sa structure ! Nous allons pour cela nous baser sur un exemple simple : une interface de notation de films qui va nous permettre :

  1. d’être notifié à l’ajout d’un film dans le référentiel
  2. de donner une note à un film donné

Asyncapi & Info

Un ensemble d’informations génériques sur l’interface, sur le même modèle qu’OpenAPI.

Channels

La section channels va nous permettre de décrire les interactions possibles avec notre API.

Pour chaque channel (correspondant à un topic, un exchange, etc selon le contexte) vont être décrites la ou les opérations - Publish ou Subscribe - qui lui sont associées.

channels:
  movie/event/added:
    subscribe:
      summary: New movie added to the referential
      message:
        name: movieAdded
        
  movie/action/{movieId}/evaluate:
    publish:
      summary: Evaluate a particular movie
      message:
        name: evaluate

Dans notre exemple, la spécification va ainsi décrire 2 opérations possibles :

  1. la possibilité de souscrire au message movieAdded sur le channel movie/event/added (lorsqu'un film aura été ajouté à notre référentiel)
  2. la possibilité de publier le message evaluate sur le channel movie/action/{movieId}/evaluate (qui sera alors consommé par notre application référentielle)

Outre ces opérations, il est possible d’ajouter des informations complémentaires telles que des références externes, une description, ou encore des paramètres permettant de variabiliser le nom du channel (comme sur notre seconde opération).

Pour chaque opération, l’élément principal reste la description du message qui comprendra notamment une section header facultative, mais surtout un payload définissant la structure de notre message :

channels:
  movie/event/added:
    subscribe:
      summary: Inform about new movie added to the referential
      message:
        name: movieAdded
        payload:
          type: object
          properties:
            id:
              type: integer
              minimum: 0
              description: Id of the movie.
            name:
              type: string

Le payload de cet exemple est décrit selon le schéma A2S (conforme aux normes JSON), mais pourrait être défini dans n’importe quel format (OAS 3.0, Avro, Cloudevents, etc). Voici ci-dessous un exemple de payload référençant un schéma Avro :

channels:
  movie/event/added:
    subscribe:
      summary: Inform about new movie added to the referential
      message:
        schemaFormat: 'application/vnd.apache.avro+yaml;version=1.9.0'
        payload:
          $ref: 'path/to/user-create.avsc/#UserCreate'

Servers

La section server n’est pas obligatoire, mais présente tout de même un certain intérêt : elle va nous permettre de spécifier à quelle adresse et avec quel protocole se connecter et ainsi faciliter la connexion du client pour émettre/recevoir des messages.

servers:
  production:
    url: broker.moviescoring.com
    protocol: mqtt

Les éléments url et protocol sont obligatoires afin de spécifier les informations de connexion, et des informations complémentaires peuvent être apportées notamment concernant les mécanismes de sécurité utilisés par le serveur (API key, OAuth2, etc).

Components

Cette section permet de définir des lots d’objets réutilisables au travers du contrat d’API, et n’ont de valeur que si référencés explicitement par les propriétés du document. Parmi les principaux, nous noterons notamment la possibilité de définir des messages, leur schémas, les security schemes, ainsi qu’un ensemble de caractéristiques et de liens sur les différents objets décrits.

components:
  messages:
    movieAdded:
      name: movieAdded
      contentType: application/json
      payload:
        $ref: "#/components/schemas/movieAddedPayload"

Spécification AsyncAPI

Une fois les différentes sections agrégées et organisées, on obtient donc un fichier AsyncAPI valide et complet comme suit :

asyncapi: '2.0.0'
info:
  title: Movie scoring API
  version: '1.0.0'
  description: |
    Movie list API allows you to remotely evaluate registered movies.

servers:
  production:
    url: broker.moviescoring.com
    protocol: mqtt

channels:
  movie/event/added:
    subscribe:
      summary: Inform about new movie added to the referential
      message:
        $ref: '#/components/messages/movieAdded'
  movie/action/{movieId}/evaluate:
    parameters:
      movieId:
        $ref: '#/components/parameters/movieId'
    publish:
      summary: Evaluate a particular movie
      message:
        $ref: '#/components/messages/evaluate'

components:
  messages:
    movieAdded:
      name: movieAdded
      contentType: application/json
      payload:
        $ref: "#/components/schemas/movieAddedPayload"
    evaluate:
      name: evaluate
      contentType: application/json
      payload:
        $ref: "#/components/schemas/evaluatePayload"
  parameters:
    movieId:
      description: Id of the movie.
      schema:
        type: integer
        minimum: 0
        description: Id of the movie.
  schemas:
    movieAddedPayload:
      type: object
      properties:
        id:
          type: integer
          minimum: 0
          description: Id of the movie.
        name:
          type: string
    evaluatePayload:
      type: object
      properties:
        score:
          type: integer
          minimum: 0
          maximum: 5
          description: Score of the movie.

La spécification pourra désormais être intégrée aux sources, et pourra être exploitée afin de générer une documentation au format HTML par exemple pour être mise à disposition des utilisateurs :

npm install -g @asyncapi/generator
ag blog.yaml @asyncapi/html-template -o html

De la même façon, ce fichier de spécification pourra également être exploité afin de générer un squelette d’application dans le langage de son choix. Les langages et technologies aujourd'hui supportés sont disponibles ici (Spring, NodeJS, etc)

Conclusion

AsyncAPI est la première initiative de description des APIs asynchrones à réellement sortir du lot, et à proposer un standard accessible, ouvert, proche de ce qui peut exister par ailleurs sur le marché et avec un outillage déjà fonctionnel.

Le pari n’est clairement pas gagné, car la spécification est toujours relativement jeune et l’adoption par le marché pas encore acquise. En revanche, outre la communauté grandissante, de plus en plus de grands noms du secteur (Solace, Mulesoft, Salesforce, etc) s'intéressent à cette initiative, la sponsorisent et y participent par le développement.

2020 a ainsi marqué un virage décisif pour AsyncAPI qui est apparu dans les tendances de l’année d’InfoQ, mais surtout grâce à l’annonce en fin d’année du partenariat avec Postman qui devrait permettre à l’initiative de se développer et de fortement gagner en exposition au sein de la communauté API.

Les ingrédients nécessaires au succès d’A2S sont réunis, seuls le temps et l’expérience nous permettront de construire le futur des APIs asynchrones autour de cette spécification.

Quel que soit ce futur et même si A2S n’est pas le standard que peut aujourd’hui être OAS, une documentation efficace et un outillage étoffé pour nos APIs asynchrones restent des avantages majeurs qui valent le coup d’être mis en place dans nos projets.

Comme aurait pu dire Pascal en son temps :

« Si vous gagnez, vous gagnez tout ; si vous perdez, vous ne perdez rien. Gagez donc sur AsyncAPI, sans hésiter. »