Examples: AMQP 1.0 (Azure Service Bus, RabbitMQ 4)
AMQP 1.0: publish to Azure Service Bus and deliver a subscription, consume RabbitMQ 4 in SSE and WebSocket.
ExampleOperationsSince version 1.6.0
On this page
Operations guide: configure, deploy and observe the gateway. What the feature does, product side: product guide; calling it: developer guide.
AMQP 1.0: publish to Azure Service Bus, deliver from a subscription
Note
Available since 1.6.0; the example is validated by the binary and pinned by the tests.
An HTTP service publishes into its tenant's Service Bus topic (202 after the accepted disposition);
a partner receives, as webhooks, what the subscription the operator created reads. Two channels, one to
write, one to read: no extension. The gateway authenticates with a SAS key over SASL PLAIN.
# AMQP 1.0 toward Azure Service Bus (1.6.0): an HTTP service publishes to its tenant's
# topic - 202 after the disposition accepted -, a partner receives by webhook what the subscription the
# operator created reads. Two channels, one to write, one to read: no extension. The gateway
# authenticates with a SAS key over SASL PLAIN.
version: 1
contracts:
openapi: ./openapi.yaml
asyncapi: ./asyncapi.yaml
upstreams:
billing-bus:
protocol: amqp
protocol_version: "1.0" # AMQP 1.0; 0-9-1 stays the default
brokers: ["amqps://acme-billing.servicebus.windows.net"]
sasl: { mechanism: PLAIN, username: flexway-send, password: "file:/etc/flexway-secrets/servicebus/key" } # SAS key name and key
amqp1: { broker: servicebus } # publication and webhook; server-sent events and WebSocket refused
timeout: 3s # the disposition accepted included
partner-hook:
protocol: webhook
url: https://hooks.partner.example/invoices
secrets: ["file:/etc/flexway-secrets/hooks/current"]
auth:
issuers:
- name: customers
discovery: https://login.example.net/.well-known/openid-configuration
audience: api
tenant_claim: org# examples/async-amqp1-servicebus/asyncapi.yaml
asyncapi: 3.0.0
info: { title: Invoices, version: 1.0.0 }
servers:
servicebus: { host: acme-billing.servicebus.windows.net, protocol: amqp, protocolVersion: "1.0" }
channels:
invoices:
address: "invoices-{tenant}" # the tenant's Service Bus topic, created by the operator
parameters:
tenant: { x-flexway-bind: tenant }
bindings: { amqp1: {} }
messages:
issued:
payload: { type: object, required: [invoiceId], properties: { invoiceId: { type: string } } }
invoicesForPartner:
address: "invoices-{tenant}/Subscriptions/flexway-partner" # the subscription, created by the operator
parameters:
tenant: { x-flexway-bind: tenant }
bindings: { amqp1: {} }
messages:
issued:
payload: { type: object, required: [invoiceId], properties: { invoiceId: { type: string } } }
operations:
publishInvoice: { action: send, channel: { $ref: "#/channels/invoices" }, x-flexway-upstream: billing-bus }
deliverInvoices:
action: receive
channel: { $ref: "#/channels/invoicesForPartner" }
x-flexway-upstream: billing-bus
x-flexway-deliver:
- { upstream: partner-hook, tenant: acme, dead_letter: invoices-dead } # a dead-letter queue of its own, with problemcode# examples/async-amqp1-servicebus/openapi.yaml
openapi: 3.1.0
info: { title: Invoices, version: "1.0.0" }
components:
securitySchemes:
customers: { type: openIdConnect, openIdConnectUrl: https://login.example.net/.well-known/openid-configuration }
paths:
/invoices:
post:
operationId: issueInvoice
x-flexway-publish: { operation: publishInvoice, idempotency: required } # Idempotency-Key -> the message-id
security: [ { customers: [invoices:write] } ]
requestBody:
required: true
content:
application/json: { schema: { type: object, required: [invoiceId], properties: { invoiceId: { type: string } } } }
responses: { "202": { description: Accepted by Service Bus } }| What happens | AMQP 1.0 |
|---|---|
POST /invoices |
The broker settles the message: accepted -> 202; rejected -> 502 upstream_error; past the budget -> 504, unknown outcome |
| Delivery | Competing receivers on the subscription (one replica per message), accepted after the 2xx; retries spent: the event in invoices-dead with problemcode, never the broker's own dead-letter queue |
| SSE or WebSocket on Service Bus | > [!WARNING] |
Refused at load: no non-competing source without a broker-specific means | | Entra ID token (CBS) | Out of the scope of
1.6.0: SAS key only |
AMQP 1.0: consume RabbitMQ 4 in SSE and WebSocket
Note
Available since 1.6.0; the example is validated by the binary and pinned by the tests.
RabbitMQ 4 speaks AMQP 1.0 natively. Each player reads its own notifications (a channel bound to the
verified subject), in SSE or acknowledged WebSocket; everyone reads the public scoreboard. amqp1.broker: rabbitmq
says how to open a non-competing source: an exclusive queue bound to the exchange, as the AMQP 0-9-1
adapter already does; a webhook delivery reads a /queues/... queue as it is. The gateway authenticates over SASL PLAIN
on TLS; EXTERNAL takes its client certificate (tls.cert_file), ANONYMOUS is admitted with mTLS or inside a declared
mesh only. SSE or WebSocket on a /queues/... queue: refused at load (its consumers share it).
# AMQP 1.0 toward RabbitMQ 4 (1.6.0): each player reads its own notifications (bound to the
# verified subject) in server-sent events or an acknowledged WebSocket; anyone reads the public
# scoreboard. amqp1.broker: rabbitmq says how to read without competing: a queue of the gateway's own,
# bound to the exchange through RabbitMQ 4's management node. The gateway authenticates as its own
# user (SASL PLAIN over TLS); EXTERNAL takes its client certificate instead.
version: 1
contracts:
openapi: ./openapi.yaml
asyncapi: ./asyncapi.yaml
upstreams:
games-bus:
protocol: amqp
protocol_version: "1.0"
brokers: ["amqps://rabbitmq.internal:5671"]
tls: { server_name: rabbitmq.internal } # cert_file and key_file with sasl EXTERNAL: the certificate is the identity
sasl: { mechanism: PLAIN, username: flexway, password: "file:/etc/flexway-secrets/rabbitmq/password" }
amqp1: { broker: rabbitmq } # an exclusive queue bound through the management node
auth:
issuers:
- name: players
discovery: https://login.example.net/.well-known/openid-configuration
audience: games
tenant_claim: org# examples/async-amqp1-rabbitmq/asyncapi.yaml
asyncapi: 3.0.0
info: { title: Game events, version: 1.0.0 }
channels:
notifications:
address: "/exchanges/notifications/players.{sub}" # a RabbitMQ 4 v2 address: exchange and routing key
parameters:
sub: { x-flexway-bind: subject } # a player reads its own channel, and only it
bindings: { amqp1: {} }
messages:
note:
payload: { type: object, required: [text], properties: { text: { type: string } } }
scores:
address: "/exchanges/scores/public"
bindings: { amqp1: {} }
messages:
# Written as application/cloudevents+json by an outbox: read in structured mode.
score:
payload: { type: object, required: [match], properties: { match: { type: string } } }
operations:
receiveNotifications: { action: receive, channel: { $ref: "#/channels/notifications" }, x-flexway-upstream: games-bus }
receiveScores:
action: receive
channel: { $ref: "#/channels/scores" }
x-flexway-upstream: games-bus
x-flexway-public: true # read by anyone, anonymous included# examples/async-amqp1-rabbitmq/openapi.yaml
openapi: 3.1.0
info: { title: Game events, version: "1.0.0" }
components:
securitySchemes:
players: { type: openIdConnect, openIdConnectUrl: https://login.example.net/.well-known/openid-configuration }
paths:
/me/notifications:
get:
operationId: streamMyNotifications
# WebSocket: settled accepted after {"ack": "<id>"}, released past ack_timeout - delivered again
x-flexway-subscribe: { operation: receiveNotifications, transport: [sse, websocket], max_streams: 3, ack_timeout: 20s }
security: [ { players: [] } ]
responses: { "200": { description: The caller's own notifications } }
/scores:
get:
operationId: streamScores
x-flexway-subscribe: { operation: receiveScores, transport: [sse], max_anonymous_streams: 500, history: 128 }
security: []
responses: { "200": { description: The public scoreboard } }With Artemis (broker: artemis, a multicast attach) or Solace (broker: solace, topic://), the same
declaration; with generic or servicebus, SSE and WebSocket are refused at load.
Run
Save the
flexway.yamlshown and the contracts it references in one directory, next to it (examples/async-amqp1-servicebus/,examples/async-amqp1-rabbitmq/in the release sources).Validate offline, without a broker or a network - the validator ships in the published image:
docker run --rm -v "$PWD:/etc/flexway:ro" ghcr.io/flexwiz/flexway:<version> validate -vMount the secrets the file references (
file:/etc/flexway-secrets/...), then start:docker run --rm -p 8080:8080 -p 9090:9090 \ -v "$PWD:/etc/flexway:ro" -v "$PWD/secrets:/etc/flexway-secrets:ro" \ ghcr.io/flexwiz/flexway:<version>
Verify
validate -vexits0and lists every resolved operation; a refusal names the faulty field and exits1.- Once started,
curl -s localhost:9090/readyzanswers200{"status":"ready","operations":<n>}; a missing secret makes the start refuse, naming it. - Each row of the case table replays from the developer side: Publish and subscribe, Receive signed webhooks.