Asyncapi: definición de apis así­ncronas en una arquitectura dirigida por eventos

0
8062
openapi vs asyncapi components

Asyncapi es a una arquitectura orientada por eventos lo que OpenAPI es a la definición de APIs REST y en este tutorial veremos una introducción a su especificación.

0. Índice de contenidos.

1. Introducción.

AsyncAPI es una iniciativa cuyo objetivo es proporcionar la especificación de un lenguaje de definición de apis asíncronas, que permita un diseño más efectivo de arquitecturas dirigidas por eventos, al igual que OpenAPI lo es para la definición de APIs REST. Ambos son agnósticos de la implementación y en el caso de AsyncAPI lo es también del protocolo por el cual se transmiten los mensajes (AMQP, MQTT, WebSockets, Kafka, STOMP, HTTP…).

La iniciativa está liderada por Fran Méndez, quien es ahora Director de Ingeniería del equipo de Postman, quién ha anunciado recientemente un partnership con AsyncAPISpec,
la especificación que él mismo lidera, que impulsará el desarrollo y evolución de la misma.

A dí­a de hoy, podemos decir que OpenAPI 3 es el estándar de facto para la definición de APIs REST, proporcionando no solo un lenguaje de definición de APIs, sino todo un set de herramientas que nos permiten generar documentación entendible por humanos, aunque el propio lenguaje de definición ya lo sea (bueno menos…) y disponer de generadores de código que permiten ahorrar tiempo en la construcción de ambas capas (productor/consumidor) en infinidad de lenguajes. OpenAPI 3 es el sucesor de Swagger 2, de hecho, la especificación de OpenAPI se renombró, mientras que el set de herramientas se sigue denominando swagger.
Hasta llegar a este punto, existían básicamente dos alternativas adicionales: JSON Schema, mucho más básico, y RAML, con características similares.

AsyncAPI se basa en OpenAPI 3, hasta tal punto que la definición de los componentes es la misma, y su objetivo es proporcionar las mismas utilidades que OpenAPI, para la definición de eventos en el ámbito de la asincronicidad.

Ambas especificaciones se basan en la premisa contract-first de modo que, antes de comenzar el desarrollo de las APIS, consumidor/productor o cliente/servidor deben tener disponible la definición del API como un ejercicio previo de estudio del mismo, por dos razones:

  • sin un posible cliente que consuma nuestra API REST o nuestros eventos, no tiene ningún sentido desarrollarlo solo desde un único punto de vista,
  • una vez definida la interfaz del servicio, deberíamos «elevarlo a público», tratando de hacer una analogía con la firma de un contrato legal de derecho de uso, una cosa es llegar a un acuerdo sobre el precio y las condiciones del servicio y otra muy diferente, es firmarlo ante notario para hacerlo público; OpenApi y AsyncAPI nos proporcionan una especificación para redactar los términos del acuerdo o ser capaces de leer las condiciones del mismo de una manera sencilla, si bien después nos deberíamos asegurar que ninguna de las partes incumplen los términos del acuerdo, elevándolo a la firma de un contrato.

En una arquitectura de cierta envergadura ya deberí­amos estar usando alguna especificación, preferiblemente OpenAPI, no solo desde el punto de vista de publicar nuestras APIs hacia terceros, sino también para la definición del consumo interno, tener controladas las dependencias para realizar un posible análisis de impacto de cualquier modificación o versionado de las APIs.
Si nuestra arquitectura tiene también comunicación asíncrona, deberí­amos plantearnos el uso de AsyncAPI para la definición de la estructura de los eventos y tener una herramienta también para analizar el impacto de las dependencias.

De la misma manera, en el ámbito de APIs REST deberíamos plantearnos el uso de alguna herramienta que nos asegure el cumplimiento de los contratos de consumo de APIs. Tenemos básicamente dos alternativas: Pact y Spring Cloud Contracts. En una arquitectura orientada a eventos deberíamos analizar también, la mejor manera de implementar un sistema de cumplimiento de contratos en la publicación y consumo de eventos,
con el enfoque de consumer driven contracts. Dejaremos este último punto para futuros tutoriales. 😉

Después de esta no tan breve introducción, vamos a ver qué particularidades tiene la especificación de AsyncAPI para la definición de la producción y consumo de eventos dentro
de nuestra arquitectura asíncrona.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.3 GHz Intel Core i9, 32GB DDR4).
  • Sistema Operativo: Mac OS Mojave 10.14.6
  • Asyncapi: 2.0.0

3. Componentes de la especificación

Creo que lo más sencillo para explicar los componentes de la especificación es compararla con los de la especificación de OpenAPI, entendiendo que de una manera u otra, hemos llegado a una arquitectura dirigida por eventos tras haber pasado por el exceso de dependencias síncronas en nuestra arquitectura. Podemos encontrar el siguiente esquema en la propia documentación de la especificación de AsyncAPI donde nos proporciona una comparativa entre OpenAPI y AsyncAPI.

openapi vs asyncapi components

Lo primero que debemos notar es que donde en OpenAPI definimos endpoints en la sección de paths, en AsyncAPI definiremos canales de comunicación que podrí­an equipararse a colas de mensajería o típicos de mensajes, en los que podremos especificar tanto operaciones de publicación como de consumo o subscripción a dichos canales.

Dentro de un mismo canal podemos especificar varias operaciones de publicación o consumo, como efectivamente, podemos publicar eventos de varios tipos dentro de un mismo canal. Junto con la definición del canal, podemos especificar también el protocolo de comunicaciones y especificar cómo se realizará el intercambio de mensajes: AMQP, HTTP, JMS, Kafka, MQTT, STOMP, WebSocket…

La segunda diferencia relevante es que donde en OpenAPI definimos esquemas de petición respuesta, en AsyncAPI definimos el esquema de publicación o consumo de los mensajes, dentro de los cuales podemos especificar cabecera y payload. Desde el punto de vista del consumidor es interesante especificar únicamente aquellas propiedades del mensaje que nos interesa consumir desde el punto de vista de nuestro dominio. Deberí­amos entender que cuando consumimos un evento, el objetivo es trasladar a nuestro dominio un hecho de negocio acaecido en nuestro sistema de forma así­ncrona y nos puede interesar trasladar una proyección del mismo, no necesariamente toda la información completa del mensaje, no tenemos porqué consumir toda la información del mensaje.

En cuanto a la definición del contenido del mensaje, la especificación de las propiedades del mensaje es totalmente compatible en cuanto a tipos, con la definición de OpenAPI.

Cabe reseñar la inclusión, dentro de la especificación, de componentes básicos dentro de la definición de una arquitectura dirigida por eventos como es la posibilidad de incluir un atributo en la cabecera de los mensajes para propagar un id de correlación en los mensajes (correlationId) que permita gestionar la trazabilidad distribuida, no solo en las llamadas síncronas entre microservicios, sino también en la producción de los mensajes. Es básico disponer de un identificador único de petición que acompañe al sistema de trazabilidad centralizado que permita buscar trazas y correlacionarlas, independientemente del microservicio en el que se produzcan y ya sea porque se ha producido una petición síncrona o asíncrona en la entrada hacia el mismo.
Con ese atributo la especificación de AsyncAPI reserva una definición nativa del mismo para su inclusión en la cabecera de los mensajes.

4. Un ejemplo práctico

Podemos encontrar un editor online de AsyncAPI con el que jugar con la definición de nuestros mensajes. A continuación vemos un ejemplo simple de definición:

  asyncapi: 2.0.0
  info:
    title: Customers Async API
    version: '1.0.0'
    description: |
      Customer events propagation
    license:
      name: Apache 2.0
      url: https://www.apache.org/licenses/LICENSE-2.0

  servers:
    production:
      url: localhost:{port}
      protocol: kafka
      description: dockerized broker
      variables:
        port:
          description: Secure connection (TLS) is available through port 9092.
          default: '9092'

  defaultContentType: application/json

  channels:
    customers:
      description: The topic on which customer events may be produced and consumed.
      publish:
        operationId: customerLoggedIn
        message:
          $ref: '#/components/messages/customerLoggedInEvent'

  components:
    messages:
      customerLoggedInEvent:
        name: customerLoggedIn
        title: customer logged in the system
        payload:
          $ref: "#/components/schemas/customerLoggedInEventPayload"

    schemas:
      customerLoggedInEventPayload:
        type: object
        properties:
          customerId:
            description: customer identifier
            type: string
            format: uuid
            pattern: "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
            loggedAt:
            $ref: "#/components/schemas/loggedAt"
      loggedAt:
        type: string
        format: date-time
        description: Date and time when the message was sent.  

Tenemos un canal de comunicación «customers» que se correspondería con un tópico de kafka en el que publicamos un evento de tipo «client logged in» con la intención de propagar al resto del sistema un evento que informe del login de un cliente.

La información que se propagaría en el evento contiene únicamente el UUID del cliente, con el formato de UUID y la fecha en la que se produjo el loggin.

5. Tools.

Podríamos comenzar por integrar el generador de documentación dentro de nuestro pipeline de construcción de modo que se genere la documentación asociada a la definición en un formato HTML legible y desplegarlo dentro de nuestro repositorio de documentación de eventos. Con el ejemplo anterior, tendríamos una página de documentación bastante aparente y, sobre todo, legible muy fácilmente:

Lo siguiente sería evaluar las herramientas que añadirían productividad a nuestra arquitectura como la auto generación del código de productores y consumidores en base a la definición dada, así podemos acudir a la documentación de los generadores como podemos encontrar un generador de código para Spring Cloud Streams.

6. Conclusiones.

Basado en nuestra experiencia, AsycAPI es una evolución natural y muy sencilla cuando ya venimos usando OpenAPI para la definición de APIs REST a la hora de evolucionar nuestra
arquitectura en una orientación hacia eventos.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad