Defining Event-Driven Architectures — AsyncAPI

Photo by Amir Hosseini on Unsplash

In this article, I am going to be talking about how we can define your event-driven architectures using the AsyncAPI definition.

Introduction

A while ago, I published an article about how you can document REST APIs using Open API 3 specification. This was for synchronous APIs.

Today we are going to look at how we can describe asynchronous APIs that we create when we design event-driven architectures.

Let’s look at the various parts of an AsyncAPI specification that are most useful.

  • API version — This defines the version of AsycnAPI. We will be dealing with version 2.0.0
  • Info — In this section, we define some metadata about the API.
  • Servers — In this section we define servers or brokers where we want to connect.
  • Channels — This section contains the messaging channel information. Here we define the type of messages sent on the topic/queue.
  • Components — This section defines the message definitions and we then refer to them inside the channel.

There are also tags and external docs section, But right now it's not that important for a new start. You can always refer to the documentation about these sections in more detail here.

If you are already familiar with the OpenAPI definition, then these sections are more or less the same. Here is a nice page to understand the similarity between OpenAPI and AsyncAPI.

Now that we have looked at the various sections. Let’s look at a minimal definition.

asyncapi: 2.0.0
info:
title: Banking backend events
version: 1.0.0
servers:
development:
url: localhost:{port}
protocol: kafka
variables:
port:
default: '9092'
channels:
banking.transaction.000:
description: |-
This contains events related to transactions.
publish:
operationId: consumeTransactionEvent
message:
$ref: '#/components/messages/transactionEvent'
bindings:
kafka:
groupId: myGroupId

components:
messages:
transactionEvent:
name: trasactionEvent
contentType: 'application/json'
payload:
$ref: '#/components/schemas/TransactionEventPayload'
schemas:
TransactionEventPayload:
type: object
properties:
transactionId:
description: Transaction Id
type: string
amount:
type: integer

Now, In this, I am creating an API definition to connect to Kafka, wherein a producer is publishing a transaction event to a Kafka topic named banking.transaction.000

Usually, when we dealt with OpenAPI 3 specification for synchronous communication, We always knew who was the server and the client. The server implements the OpenAPI definition and the client uses the definition to know how to interact with the server.

Now, there is a difference in the case of asynchronous communication. We don't have a nation of servers and clients. An application can consume an event and also emit an event.

So let's look a little deeper at the channel section from the above definition to understand this better.

channels:
banking.transaction.000:
description: |-
This contains events related to transactions.
publish:
operationId: consumeTransactionEvent
message:
$ref: '#/components/messages/transactionEvent'
bindings:
kafka:
groupId: myGroupId

In the channel section, we have publish section, which contains an operation Id, message, and binding.

Now, When we read the definition, we have to read it from the perspective of a viewer, who is looking at the API definition that is already implemented by a provider application. This means, If you want to interact with the provider’s application (who has implemented the API definition), then you have to consume events from the banking.transaction.000 topic.

This was a little confusing when I was generating the code.

So with this understanding, Let’s generate some code.

AsyncAPI provides templates to create code from the API definition. There are quite a few code generation templates for languages like Java, Nodejs, and python. You can also generate an HTML page for the API definition using the HTML template. You can find the list of templates here

We will be generating our code in Java using the Spring framework.

To generate the code from the template, we would have to use an async generator command, which we can install by command-line or use a docker image to run the command. You can find more details about the installation here.

To install the command line utility, run the following command:

npm install -g @asyncapi/generator

Now, We would be using the @asyncapi/java-spring-template to generate our API providers and viewer’s code. This template makes use of the spring-kafka library to generate your consumers and publishers to the topic. You can find various options that are provided for this template here

To generate the API consumer’s code, we would have to run the following command

ag asyncapi.yml \
@asyncapi/java-spring-template \
-p javaPackage=com.amrut.prabhu.consumer \
-p inverseOperations=false \
--force-write \
-o async-kafka-consumer

You can alternatively run the command using docker in the following way:

docker run --rm -it -v ${PWD}:/app asyncapi/generator \
asyncapi.yml \
@asyncapi/java-spring-template \
-p javaPackage=com.amrut.prabhu.consumer \
--force-write \
-o async-kafka-consumer

Note: if you use the docker way of creating the code, It might take some time to generate, because it downloads the template every time you run the command.

Now with this, we have created a project with code to consume the events from the banking.transaction.000 topic. The class that handles the part of consuming events is MessageHandlerService . It makes use of the @kafkaListener annotation, to define a handler for the incoming messages.

Now let's look at generating the API provider's code for us.

To generate the API definition provider’s code which in our case is the producer to thebanking.transaction.000 topic, we would have to run the following command

ag asyncapi.yml \
@asyncapi/java-spring-template \
-p javaPackage=com.amrut.prabhu.producer \
-p inverseOperations=true \
--force-write \
-o async-kafka-producer

Now, here the command is pretty much the same, but a small difference. In this case, we have to set the inverseOperation property to true. This tells the generator to generate the code as the provider of the API definition.

In this case, We have the CommandLinePublisher that publishes a message to Kafka. It internally makes use of PublisherService to publish the message using the KafkaTemplate.

In this article, we had a look at how an AsyncAPI definition looks like and also generate code using a code template. Some things can be a bit tricky to understand but they have wonderful documentation which you can refer to here. I feel the code templates need some improvement but I can understand the templates are still in their early stages.

As to how we have OpenAPI as standards for documenting REST APIs, AsyncAPIs, I guess would become a standard for documenting asynchronous or event-driven architectures.

As always, I have uploaded the generated code here.

Feel free to share the article and join me on Twitter. You can also subscribe to my newsletter on RefactorFirst.com

This article was originally published on RefactorFirst.com

If you like my writing you can always buy me a coffee.😊️

Software Craftsman, Tech Enthusiast. I run https://refactorfirst.com to post all my articles