Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: introducing request/reply #2071

Closed
wants to merge 16 commits into from
Closed
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
240 changes: 240 additions & 0 deletions pages/docs/tutorials/getting-started/request_reply.md
@@ -0,0 +1,240 @@
---
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
title: Request and reply pattern
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
weight: 20
---

A quite common messaging pattern is [request-reply](https://www.enterpriseintegrationpatterns.com/patterns/messaging/RequestReply.html), which describes a **requester**, which sends a request message and waits for a reply and a **replier** which receives the request and responds with a reply.
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

As of AsyncPAI v3, this pattern can be described natively.
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

# Describing a requester

We are gonna use a very simple ping and pong example where a requestor sends the ping and responder respond with pong. Take notice this is an application-level ping and pong and is not related to ping/pong in standards such as WebSocket.
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

To describe a **requester** in AsyncAPI we make use of an operation that `send`s the ping and expects a `reply` over `pong`.
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

```yml
asyncapi: 3.0.0

info:
title: Ping/pong example with the requester
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
version: 1.0.0
description: Simple example with a requester that initiates the request-reply pattern.
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

channels:
ping:
address: /
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
messages:
ping:
$ref: '#/components/messages/ping'
pong:
address: /
messages:
pong:
$ref: '#/components/messages/pong'

operations:
pingRequest:
action: send
channel:
$ref: '#/channels/ping'
reply:
channel:
$ref: '#/channels/pong'
```

# Describing a replier

To describe a **replier**, we take the same structure as for the requester, with the simple difference, of using the `receive` action instead.
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

```yml
asyncapi: 3.0.0

info:
title: Ping/pong example with replier
version: 1.0.0
description: Simple example with a replier that replies to the request.

channels:
// Same as for the requester
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

operations:
pongReply:
action: receive
channel:
$ref: '#/channels/ping'
reply:
channel:
$ref: '#/channels/pong'
```

# Sub-patterns in request and reply
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

In the simple example above we saw how you could set up a request and reply pattern across two applications, where the request and reply happened over the same channel `/` on an unknown server and protocol, which could have been HTTP, Kafka or WebSocket, in this simple example it does not really matter, cause the only difference would be how the server information is defined.

However, there are sub-patterns to request and reply that AsyncAPI v3 supports, let's take a look at them.

## Request/reply over different channels
If you come from a REST or WebSocket environment, this sub-pattern might seem weird, but in the event-driven world of Kafka or NATS this is a common pattern to utilize where you do the request over one channel, and reply on a different one.
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

In this example, the reply is on a statically defined channel so you at design time know exactly where the reply is returned to.
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

The only difference in this AsyncAPI document, in relation to the simple example is that each channel has now been given a different address `/ping` and `/pong` respectively.

```yml
asyncapi: 3.0.0

info:
title: Ping/pong example for a requester with static reply channel
version: 1.0.0
description: Example with a requester that initiates the request/reply pattern on a different channel than the reply is using.

channels:
ping:
address: /ping
messages:
ping:
$ref: '#/components/messages/ping'
pong:
address: /pong
messages:
pong:
$ref: '#/components/messages/pong'

operations:
pingRequest:
action: send
channel:
$ref: '#/channels/ping'
reply:
channel:
$ref: '#/channels/pong'
```

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

```yml
asyncapi: 3.0.0

info:
title: Ping/pong example for replier with static reply channel
version: 1.0.0
description: Example with a replier that returns the response on a different channel than the request happened on.

channels:
//.Same as for the requester
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

operations:
pongReply:
action: receive
channel:
$ref: '#/channels/ping'
reply:
channel:
$ref: '#/channels/pong'
```

## Request/reply with dynamic response channel

A second sub-pattern is where we do not know the reply channel at design time, but instead, it's dynamic and determined at runtime. This could for example be using the request message payload or header to dictate the response channel.

Take notice how we utilize `address: null` to define that we dont know the address and instead
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

```yml
asyncapi: 3.0.0

info:
title: Ping/pong example for a requester with dynamic reply channel
version: 1.0.0
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.

channels:
ping:
address: /ping
messages:
ping:
$ref: '#/components/messages/ping'
pong:
address: null
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
messages:
pong:
$ref: '#/components/messages/pong'

operations:
pingRequest:
action: send
channel:
$ref: '#/channels/ping'
reply:
address:
description: The reply address is dynamically determined based on the request header `replyTo`
location: "$message.header#/replyTo"
channel:
$ref: '#/channels/pong'
```

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

```yml
asyncapi: 3.0.0

info:
title: Ping/pong example for replier with dynamic reply channel
version: 1.0.0
description: Example with a replier that returns the response on a channel determined by the header `replyTo` of the request.

channels:
//.Same as for the requester

operations:
pongReply:
action: receive
channel:
$ref: '#/channels/ping'
reply:
address:
description: The reply address is dynamically determined based on the request header `replyTo`
location: "$message.header#/replyTo"
channel:
$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`.
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

## Multiple messages over the same channel with request/reply

In for example WebSocket, often you encounter that a channel will contain multiple messages over the same channel, but when you design the request/reply operations, you want to explicitly state which messages are "active".
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

In the following example it's very close to the first requester example, where the difference is 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.
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

```yml
asyncapi: 3.0.0

info:
title: Ping/pong example when a channel contains multiple multiples
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
version: 1.0.0
description: Simple example with a requester that initiates the request-reply pattern, where the root channel contains multiple messages.

channels:
rootChannel:
address: /
messages:
ping:
$ref: '#/components/messages/ping'
pong:
$ref: '#/components/messages/pong'

operations:
pingRequest:
action: send
channel:
$ref: '#/channels/rootChannel'
messages:
- $ref: "/components/messages/ping"
reply:
messages:
- $ref: "/components/messages/pong"
channel:
$ref: '#/channels/rootChannel'
```