On the occasion of the release of the new major version of the AsyncAPI I wrote a rather theoretical post encouraging the use of AsyncAPI as a platform-independent standard.
But a standard is only useful if it can help you in real-life development scenarios. Just a simple Google search reveals that the older sibling, OpenAPI, has a much larger and more mature community. In the case of a machine-readable API specification language the tooling support is crucial. I will describe my experience with the tools so far.
AsyncAPI Studio
studio.asyncapi.com is a nice online visualisation tool to check if your AsyncAPI specification is valid. The page says it is "BETA", but it looked quite reliable when I tried it.
Code generators: the devil hidden in the schema details
In asynchronous APIs, unlike the REST (HTTP) APIs, we usually do not rely so much on common features of the protocol used. Instead of things like HTTP verbs, status codes and standard headers we concentrate on the payload of the messages. So it is nice that there are code generators that can produce a ready-to-run Spring Boot or Spring Cloud Stream application, but the most important part is the code that serializes/deserializes the payloads. Topic names and broker specifics are usually placed in separate configuration files anyway.
Different JSON schema languages
At first glance the schemas part of AsyncAPI looks the same as in OpenAPI. But the specifications are not exactly the same. AsyncAPI even in its latest version 3.0 only requires its users (including tools) to support a schema based on JSON schema draft 7 (from 2018) plus features specific to Async API, see the "Schema formats table" section of the specification. There are two more recent versions of the JSON schema, the latest is called 2020-12. And OpenAPI has its own set of extensions over the JSON schema.
No luck with polymorphism
I will illustrate the limitations of the generators on an example schema using polymorphic types with a discriminator field.
asyncapi: 2.6.0
info:
title: Example of schema using polymorphism
version: 2.6.0
channels:
pet:
subscribe:
message:
payload:
type: object
properties:
pet:
$ref: "#/components/schemas/Pet"
components:
schemas:
Pet:
type: object
discriminator: petType
properties:
name:
type: string
petType:
type: string
required:
- name
- petType
## applies to instances with `petType: "Cat"`
## because that is the schema name
Cat:
description: A representation of a cat
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
properties:
huntingSkill:
type: string
description: The measured skill for hunting
enum:
- clueless
- lazy
- adventurous
- aggressive
required:
- huntingSkill
## applies to instances with `petType: "Dog"`
## because that is the schema name
Dog:
description: A representation of a dog
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
properties:
packSize:
type: integer
format: int32
description: the size of the pack the dog is from
minimum: 0
required:
- packSize
## applies to instances with `petType: "StickBug"`
## because that is the required value of the discriminator field,
## overriding the schema name
StickInsect:
description: A representation of an Australian walking stick
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
properties:
petType:
const: StickBug
color:
type: string
required:
- color
The above is a valid AsyncAPI specification (you can check it in the AsyncAPI Studio), however none of the currently available AsyncAPI code generators can generate a usable (Java) code from it. By usable I mean a code that uses the discriminator field value to choose what Java class to instantiate and then deserializes the JSON object in the instance correctly.
The "most official" AsyncAPI generator linked from the AsyncAPI site will follow the references in the "allOf" structure to generate the subclasses of the Pet class. But it will ignore the "discriminator" logic and actually fail because it does know what to do with the "petType" attribute repeated in the derived types (e.g. StickInsect).
A less important thing I do not like about the "official" generator is that it is implemented in JavaScript (it uses the schema generator library Modelina written in TypeScript). For a Node.js backend this may be nice, but for Java (JVM) based backends it is not ideal to have to invoke npm as part of the build process. Especially knowing that the OpenAPI generator that is much more mature and probably used for the synchronous (REST) APIs of the same backend service, is implemented in Java and runs in Maven/Gradle nicely.
If you convert the above schema to the OpenAPI format and run the OpenAPI generator on it, you get the polymorphic code that you expect.
In the Tools dashboard of the OpenAPI site you can find two more generators: MultiAPI generator and ZenWave SDK, both written in Java. Unfortunately these are even less mature than the JavaScript one: They don't even detect there are types derived from the base Pet type for which classes should be generated.
Conclusion
I still think it is a good idea to start using AsycAPI for asynchronous APIs. But beware of possible problems if you want to use complex schemas. The safest is to use just simple schemas and wait for the tooling to catch up. You always have to verify your schemas with the actual generator you want to use. Even JSON schema draft 7 contains concepts similar to the discriminator approach above (if-then and oneOf), but neither of them made the generators give me what I needed.
Actually, the AsyncAPI 3.0 permits to use the OpenAPI schemas in AsyncAPI using what they call a "Multi Format Schema Object". Although the specification does not require, only recommends to support the OpenAPI schema, I would really love to see tools supporting it so that we can share the same schema format for AsyncAPI and OpenAPI.
Top comments (0)