First look at request and reply in AsyncAPI v3

Jonas Lagoni Avatar

Jonas Lagoni

ยท6 min read

This post was originally intended for the AsyncAPI website, but they had other plans and I had already written it, so now you get the pleasure of a teaser instead ๐Ÿ˜„ Thanks Heiko Henning, Hhridyesh Bisht, Animesh Kumar, Sergio Moya, and Alejandra Quetzalli for the initial reviews ๐Ÿ™

A fair warning, this post is very cut and dry and goes straight to the point assuming some of the basic knowledge of the other v3 changes. If you want more in-depth information, take a look here: https://v3.asyncapi.com/blog/release-notes-3.0.0

A common messaging pattern is request and reply and up until now, it has been near impossible to describe it in AsyncAPI, that changes with AsyncAPI v3. Within it, we will see there are different sub-patterns that of course are all supported, but enough talk, let's jump straight into it.

Describing a requester

We are going to use a very simple ping and pong example where a requester sends the ping and the replier responds with a pong. To describe a requester in AsyncAPI, we make use of an operation that sends to the ping channel and expects a reply over pong.

1asyncapi: 3.0.0
2
3info:
4  title: Ping/pong example for a requester
5  version: 1.0.0
6  description: Example with a requester that initiates the request/reply pattern on a different channel than the reply is using.
7
8channels:
9  ping:
10    address: /ping
11    messages:
12      ping:
13        $ref: '#/components/messages/ping'
14  pong:
15    address: /pong
16    messages:
17      pong:
18        $ref: '#/components/messages/pong'
19
20operations:
21  pingRequest:
22    action: send
23    channel: 
24      $ref: '#/channels/ping'
25    reply:
26      channel: 
27        $ref: '#/channels/pong'

The reply section defines all the necessary information to properly reply to the request, such as where to, and with what message. This is just a simple example, but you can check the full list of properties under the Operation Reply Object

Describing a replier

Defining the replier is the same as for the requester, where we instead make use of the receive action.

1asyncapi: 3.0.0
2
3info:
4  title: Ping/pong example for replier
5  version: 1.0.0
6  description: Example with a replier that returns the response on a different channel than the request happened on.
7
8channels:
9  // Same as for the requester
10
11operations:
12  pongReply:
13    action: receive
14    channel: 
15      $ref: '#/channels/ping'
16    reply:
17      channel: 
18        $ref: '#/channels/pong'

This means that we receive a message over ping and we are expected to return a reply over pong.

Sub-patterns in request/reply

In the simple example above, we saw how you could set up a request/reply pattern across two applications where one application is the requester and the other is the replier.

However, in a protocol-agnostic world, there are many different sub-patterns to the simple request/reply. All of which AsyncAPI v3 enables.

Request/reply with dynamic response channel

In some cases, we do not know the reply channel at design time, but instead, it's dynamically determined at runtime. This could, for example, be using the request message payload or header to dictate the response address.

Take notice of how we utilize address: null to define that we don't know the address just yet. This is just for illustration purposes as you can also omit the property entirely. We then utilize the Operation Reply Address Object to define that the address of where to send the reply is located dynamically in the message header under replyTo.

1asyncapi: 3.0.0
2
3info:
4  title: Ping/pong example for a requester with a dynamic reply channel
5  version: 1.0.0
6  description: Example with a requester that initiates the request/reply pattern where the reply will happen on whatever is defined in the header `replyTo` of the request.
7
8channels:
9  ping:
10    address: /ping
11    messages:
12      ping:
13        $ref: '#/components/messages/ping'
14  pong:
15    address: null
16    messages:
17      pong:
18        $ref: '#/components/messages/pong'
19
20operations:
21  pingRequest:
22    action: send
23    channel: 
24      $ref: '#/channels/ping'
25    reply:
26      address:
27        description: The reply address is dynamically determined based on the request header `replyTo`
28        location: "$message.header#/replyTo"
29      channel: 
30        $ref: '#/channels/pong'

Defining the replier is the same as for the requester, again using the receive action instead is the only difference.

1asyncapi: 3.0.0
2
3info:
4  title: Ping/pong example for replier with a dynamic reply channel
5  version: 1.0.0
6  description: Example with a replier that returns the response on a channel determined by the header `replyTo` of the request.
7
8channels:
9  // Same as for the requester
10
11operations:
12  pongReply:
13    action: receive
14    channel: 
15      $ref: '#/channels/ping'
16    reply:
17      address:
18        description: The reply address is dynamically determined based on the request header `replyTo`
19        location: "$message.header#/replyTo"
20      channel: 
21        $ref: '#/channels/pong'

You can use different types of location values here as it's not limited to headers specifically. You can also use payload properties with $message.payload#/replyTo. These types of values are Runtime Expressions.

Request/reply over the same channel

The request/reply can also occur over the same channel (for example /), which could be HTTP or WebSocket.

To do this it's as simple as having both channels use the same address.

1asyncapi: 3.0.0
2
3info:
4  title: Ping/pong example with requester over the same channel
5  version: 1.0.0
6  description: Requester example initiating the request-reply pattern that are using the same channel for the reply
7
8channels:
9  ping:
10    address: /
11    messages:
12      ping:
13        $ref: '#/components/messages/ping'
14  pong:
15    address: /
16    messages:
17      pong:
18        $ref: '#/components/messages/pong'
19
20operations:
21  pingRequest:
22    action: send
23    channel: 
24      $ref: '#/channels/ping'
25    reply:
26      channel: 
27        $ref: '#/channels/pong'

Defining the replier is the same as for the requester, again using the receive action instead is the only difference.

1asyncapi: 3.0.0
2
3info:
4  title: Ping/pong example with replier
5  version: 1.0.0
6  description: Simple example with a replier that replies to the request.
7
8channels:
9  // Same as for the requester
10
11operations:
12  pongReply:
13    action: receive
14    channel: 
15      $ref: '#/channels/ping'
16    reply:
17      channel: 
18        $ref: '#/channels/pong'

Multiple messages over the same channel with request/reply

In WebSocket, you often encounter that a channel will contain multiple messages, which means you will have to make your operations explicitly define which messages are used for each operation.

The following example is very similar to the above example, with the difference being that we merged the two ping and pong channels into a single one (because they use the same address). The request operation then explicitly defined the request message among the available channel messages and the same for the reply.

1asyncapi: 3.0.0
2
3info:
4  title: Ping/pong example when a channel contains multiple messages
5  version: 1.0.0
6  description: Simple example with a requester that initiates the request-reply pattern, where the root channel contains multiple messages.
7
8channels:
9  rootChannel:
10    address: /
11    messages:
12      ping:
13        $ref: '#/components/messages/ping'
14      pong:
15        $ref: '#/components/messages/pong'
16
17operations:
18  pingRequest:
19    action: send
20    channel: 
21      $ref: '#/channels/rootChannel'
22    messages:
23      - $ref: "/components/messages/ping"
24    reply:
25      messages:
26        - $ref: "/components/messages/pong"
27      channel: 
28        $ref: '#/channels/rootChannel'

Notice how we have to add messages to the operation and reply information, to explicitly state which messages are used for when.

Photo by Steven Skerritt on Unsplash