API-First with AsyncAPI
- API-First with AsyncAPI
If you are familiar with OpenAPI and OpenAPI Generator API-First workflow:
- First, write the OpenAPI definition, collaborating between API providers and API consumers.
- Then, use OpenAPI Generator, either the maven plugin or a CLI, to generate some DTOs and interfaces from your OpenAPI definition.
- Implementing the generated interfaces, you can create a service for the API.
- As a client, you can use generated interfaces to consume the API with some HTTP client generated behind the scenes.
When doing API-First with AsyncAPI, the process is similar. After you generate some interfaces and DTOs from your API definition, you use the generated interfaces to produce messages, send them to the broker, and implement them to consume messages from the broker.
There is still a fundamental difference between OpenAPI and AsyncAPI: OpenAPI is used to document Request-Response / Client-Server APIs, while AsyncAPI is used to document Event-Driven APIs which, except for WebSockets, are Broker-based.
And broker-based APIs, unlike Client-Server, are inherently symmetric.
Broker-based APIs are Symmetric
Because APIs mediated by a broker are inherent symmetric, it's difficult to establish the roles of the client/server: what represents a publish
operation from one side will be a subscribe
operation seen from the other side. Also, a given service can act as a publisher and subscriber on the same API.
For these reasons, to avoid defining the same API operations multiple times from each perspective, we propose to define the API only once from the perspective of the provider of the functionality, which may be a producer, a consumer, or both.
Some definitions:
- SERVICE: An independent piece of software, typically a microservice, that provides a set of capabilities to other services.
- PROVIDER: The service that implements the functionality of the API. It may be accepting asynchronous command requests or publishing business domain events.
- CLIENT/s: The service/s that uses the API's functionality. It may be requesting asynchronous commands or subscribing to business domain events.
- PRODUCER: A service that writes a given message.
- CONSUMER: A service that reads a given message.
Define your AsyncAPI from the perspective of the PROVIDER of the functionality, which may be a producer, a consumer, or both. Share this definition with your CLIENTS.
Use the table to understand which section of AsyncAPI (publish or subscribe) to use for each topic and which role (provider or client) to use on the plugin configuration.
Events | Commands | |
---|---|---|
Provider | Produces (publish) | Consumes (subscribe) |
Client | Consumes (subscribe) | Produces (publish) |
OperationId Suggested Prefix | on<Event Name> | do<Command Name> |
If you still find it confusing which one is a provider and a client, just use this rule: it can be only one provider of a given message, while clients of a given message there can be many:
- If the provider is the producer, use publish section
- If it is the consumer, use subscribe section.
Events, Commands, and Messages
There are two types of messages in a messaging system: events and commands. An event message describes a change that has already happened, while a command message describes an operation that needs to be carried out. In other words, events are used to notify subscribers about something that has already occurred, while commands are used to initiate an action or process.
- Event: A message describing a change that has already happened.
- Command: A message describing an operation that has to be carried out.
Also, while there can be only one provider that produces a given event, commands can be issued for one or many client producers.
Understanding AsyncAPI Definition
While OpenAPI and AsyncAPI come to document completely different architectural styles, they are similar in many aspects; in fact, AsyncAPI YAML format was initially based on OpenAPI format and structure.
If you are familiar with OpenAPI, you may find useful the following image borrowed from AsyncAPI documentation (click image to follow):
Info
Document your API: name, purpose, contact details, and license...
Servers
Document where your API will be deployed and required security...
You can also document some server protocol-specific configurations using free-form bindings property
Channels: Publish / Subscribe
Each channel represents one single broker topic, channel, or queue... where you are about to publish or subscribe.
Use the table above to understand which section, publish or subscribe, you may want to use.
In a nutshell:
Providers publish events and subscribe to commands/queries/requests.
If you still find it confusing which is a provider and a client, use this rule: In a given messaging scenario, there can be only one provider of a message, while there can be multiple clients. If the provider is producing messages, use the publish
section. If the provider is consuming messages, use the subscribe
section.
Messages
Use Messages to describe Headers, Payload Schema, and Content-Type. You can also include examples, descriptions, and protocol-specific binding documentation...
1components:
2 messages:
3 turnOnOff:
4 name: turnOnOff
5 title: Turn on/off
6 summary: Command a particular streetlight to turn the lights on or off.
7 headers:
8 type: object
9 properties:
10 my-app-header:
11 type: string
12 payload:
13 $ref: "#/components/schemas/turnOnOffPayload"
14
Message Payloads / Schemas
You can define message payloads as:
- Inline components/schemas in the same familiar way you do in OpenAPI
- External files: both
json-schema
andavro schemas
(.avsc) are supported
1components:
2 messages:
3 MessageWithAsyncAPISchema:
4 payload:
5 $ref: "#/components/schemas/turnOnOffPayload" ## asyncapi/inline schema
6 MessageWithExternalJsonSchema:
7 schemaFormat: 'application/schema+json;version=draft-07'
8 payload:
9 $ref: "some/external/file.schema" ## a json-schema file
10 MessageWithAvroSchema:
11 schemaFormat: application/vnd.apache.avro+json;version=1.9.0
12 payload:
13 $ref: "v1/imports/file.avsc" ## and avro schema file
Reusing Configurations: Operation Traits, Message Traits...
Operation Traits, Message Traits are an excellent way to reuse chunks of configuration between different operations or messages.
For instance, if various messages share some common headers, you can configure them as Message Traits:
1components:
2 messages:
3 CustomerEventMessage:
4 name: CustomerEventMessage
5 title: Async Event for a Customer
6 summary: Async Event for a Customer
7 schemaFormat: application/vnd.aai.asyncapi;version=2.4.0
8 traits:
9 - $ref: '#/components/messageTraits/CommonHeaders' # 'CommonHeaders' contents will replace 'traits' property
10 payload:
11 $ref: '#/components/schemas/CustomerEventPayload'
12
13 messageTraits:
14 CommonHeaders:
15 headers:
16 type: object
17 properties:
18 my-app-header:
19 type: integer
20 minimum: 0
21 maximum: 100
And the same concept applies to Operation Traits.
Different Styles of Event Messages
Notification Messages
An Event Notification contains minimal information about the event and enough information for interested consumers to locate additional details. The specifics of what information is included in an event notification can vary depending on the system or use case.
1{
2 "headers": {
3 "event-type": "customer-created",
4 "event-id": "",
5 "aggregate-id": "1",
6 "aggregate-type": "customer"
7 },
8 "payload": {
9 "id": 1,
10 "eventType": "created",
11 "link": "/customers/1"
12 }
13}
State Transfer Messages
On the other hand, a State Transfer message contains the entire state of the aggregate, so a consumer does not need to make additional calls. This can be useful in situations where subscribers need to maintain a synchronized view of the data. Compacted keyed topics typically use this style of messages.
1{
2 "headers": {
3 "event-id": "",
4 "aggregate-id": "1",
5 "aggregate-type": "customer"
6 },
7 "payload": {
8 "id": 1,
9 "firstName": "string",
10 "lastName": "string",
11 "password": "string",
12 "email": "string",
13 "username": "string",
14 "address": {
15 "id": 1,
16 "street": "string",
17 "city": "string",
18 "state": "string",
19 "zip": "string"
20 }
21 }
22}
Domain Event Messages
Domain Event Messages contains information about the event and interesting portions of the underlying aggregate, but not the entire state of the aggregate. This style of events is typically used for Event Sourcing integration patterns.
1{
2 "headers": {
3 "event-type": "customer-address-updated",
4 "event-id": "",
5 "aggregate-id": "1",
6 "aggregate-type": "customer"
7 },
8 "payload": {
9 "id": 1,
10 "eventType": "address-updated",
11 "customer": {
12 "id": 1,
13 "new-address": {
14 "street": "string",
15 "city": "string",
16 "state": "string",
17 "zip": "string"
18 }
19 }
20 }
21}
Next: Java Code Generator for AsyncAPI
Next: Java Code Generator for AsyncAPI
Originally published at https://zenwave360.github.io