Skip to content

Request/reply pattern over ephemeral queues

A booking page asks the pricing service for a quote and waits half a second: an ephemeral inbox, a replyTo, and a fallback when no answer comes.

Updated View as Markdown

The booking page needs a price from the pricing service, now. If no answer comes in half a second, it shows “ask us”. A durable queue is too heavy for that, and a direct HTTP call ties the two services together.

Ephemeral queues live in memory, outside the log. The caller makes a private inbox, sends it as replyTo, and waits for the answer with a timeout. Any instance of the pricing service can answer.

The booking page sends a quote request for the deluxe room to the ephemeral queue quotes, with a private inbox as replyTo, and waits at most 500 milliseconds. A pricing service instance answers price 129 into the inbox in time. If no instance answers within 500 milliseconds, the page shows ask us.The booking page sends a quote request for the deluxe room to the ephemeral queue quotes, with a private inbox as replyTo, and waits at most 500 milliseconds. A pricing service instance answers price 129 into the inbox in time. If no instance answers within 500 milliseconds, the page shows ask us.
A quote with a deadline: the answer, or "ask us".
// The pricing service answers quote requests, in memory.
async function pricing() {
  const { messages } = await queen.ephemeral.pop('quotes', { group: 'pricing', wait: true })
  for (const m of messages) {
    await queen.ephemeral.push(m.payload.replyTo, [{ room: m.payload.room, price: 129 }])
  }
  await queen.ephemeral.ack('quotes', messages, { group: 'pricing' })
}

// The booking page asks, and waits at most half a second for the answer.
async function quote(room) {
  const inbox = `inbox-${crypto.randomUUID()}`
  await queen.ephemeral.push('quotes', [{ room, replyTo: inbox }])
  const reply = await queen.ephemeral.pop(inbox,
    { wait: true, timeout: 500, commitOnDelivery: true })
  return reply.messages[0]?.payload ?? { room, price: null }    // too slow: show "ask us"
}

const [deluxe] = await Promise.all([quote('deluxe'), pricing()])
console.log(deluxe)                               // { room: 'deluxe', price: 129 }

The answer came back in time: {room: 'deluxe', price: 129}. With no pricing service running, the caller gave up after half a second and got price: null.

Why
an answer that comes too late is worth nothing, so it does not need a disk. In memory a round trip costs very little, and the service scales like any consumer group.

One thing to know: a node that crashes loses the ephemeral messages it held. There are no transactions, dead letters or replays on these queues.

Built from: Ephemeral queues.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close