# Example: RabbitMQ in AMQP 0-9-1

> RabbitMQ in AMQP 0-9-1: a channel bound to each person, a public feed read without a token.

Example - Since version 1.5.0 - Flexway v1.7.1 - https://flexway.io/en/docs/operations-examples-rabbitmq/

> Operations guide: configure, deploy and observe the gateway. What the feature does, product side: [product guide](https://flexway.io/en/docs/product-overview/); calling it: [developer guide](https://flexway.io/en/docs/developer-overview/).

## RabbitMQ in AMQP 0-9-1: a channel per person, a public feed

Each player publishes and reads **its own** notifications - the channel is bound to the token's verified
subject -; the scoreboard is a **public** feed, read without a token, resumed over 128 messages. A producer
outside the gateway writes it as `application/cloudevents+json`: read in structured mode. The gateway
connects with its own user, on its own virtual host (`/games`, the URL's path).

<!-- example: examples/async-amqp/flexway.yaml -->

```yaml
# RabbitMQ in AMQP 0-9-1 behind the gateway's controls (1.5.0): each player reads its
# own notifications only (bound to the verified subject), anyone - anonymous included - reads the
# declared public feed. The gateway authenticates itself: its own user, its own virtual host.
version: 1
contracts:
  openapi: ./openapi.yaml
  asyncapi: ./asyncapi.yaml
upstreams:
  bus:
    protocol: amqp                               # AMQP 0-9-1 (protocol_version: 0-9-1, the default)
    brokers: ["amqps://rabbitmq.internal:5671/games"]   # the path is the virtual host; never a password in the URL
    sasl: { mechanism: PLAIN, username: flexway, password: "file:/etc/flexway-secrets/rabbitmq/password" }
    timeout: 2s                                  # publisher confirm included
auth:
  issuers:
    - name: players
      discovery: https://login.example.net/.well-known/openid-configuration
      audience: games
      tenant_claim: org
```

```yaml
# examples/async-amqp/asyncapi.yaml
asyncapi: 3.0.0
info: { title: Game events, version: 1.0.0 }
channels:
  notifications:
    # A topic exchange the broker already has: the gateway never declares one.
    address: "players.{sub}.notifications"
    parameters:
      sub: { x-flexway-bind: subject }           # the verified subject: a player reads its own channel only
    bindings:
      amqp: { is: routingKey, exchange: { name: notifications, type: topic } }
    messages:
      note:
        contentType: application/json
        payload: { type: object, required: [text], properties: { text: { type: string } } }
  scoreboard:
    address: scores.public                       # binds nothing, filters nothing: declared public below
    bindings:
      amqp: { is: routingKey, exchange: { name: scores, type: topic } }
    messages:
      # Written by a producer outside the gateway (an outbox) as application/cloudevents+json: read in
      # structured mode; any other content type is read in binary mode.
      score:
        payload: { type: object, required: [match], properties: { match: { type: string }, score: { type: string } } }
operations:
  publishNotification: { action: send, channel: { $ref: "#/channels/notifications" }, x-flexway-upstream: bus }
  receiveNotifications: { action: receive, channel: { $ref: "#/channels/notifications" }, x-flexway-upstream: bus }
  receiveScores:
    action: receive
    channel: { $ref: "#/channels/scoreboard" }
    x-flexway-upstream: bus
    x-flexway-public: true                       # read by anyone, anonymous included
```

```yaml
# examples/async-amqp/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:
    post:
      operationId: notifyMe
      x-flexway-publish: { operation: publishNotification }
      security: [ { players: [] } ]
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [text], properties: { text: { type: string } } }
      responses: { "202": { description: Confirmed by the broker } }
    get:
      operationId: streamMyNotifications
      # WebSocket: at least once - the client acknowledges {"ack": "<id>"} within ack_timeout.
      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
      # SSE only; one consumer per replica whatever the audience, 128 messages kept for Last-Event-ID.
      x-flexway-subscribe: { operation: receiveScores, transport: [sse], max_streams: 2, max_anonymous_streams: 500, history: 128 }
      security: []                               # anonymous door: only toward a public operation
      responses: { "200": { description: The public scoreboard } }
```

| Case | What the gateway does |
| --- | --- |
| `POST /me/notifications` | Published on `players.<sub>.notifications` of the `notifications` exchange, `202` after the confirm |
| `GET /me/notifications` | The subject's own channel only; a value outside letters, digits, `_` and `-` is refused (`403`) |
| `GET /scores` without a token | Declared public: up to 500 anonymous callers together, 2 streams per address; a subscriber too slow is closed, the others go on |
| Webhook | The same declaration as in [Kafka: publish, stream, deliver](https://flexway.io/en/docs/operations-examples-kafka/#kafka-publish-stream-deliver): a named queue shared by the replicas |

## Run

1. Save the `flexway.yaml` shown and the contracts it references in one directory, next to it (`examples/async-amqp/` in the release sources).
2. Validate offline, without a broker or a network - the validator ships in the published image:

   ```bash
   docker run --rm -v "$PWD:/etc/flexway:ro" ghcr.io/flexwiz/flexway:<version> validate -v
   ```

3. Mount the secrets the file references (`file:/etc/flexway-secrets/...`), then start:

   ```bash
   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

1. `validate -v` exits `0` and lists every resolved operation; a refusal names the faulty field and exits `1`.
2. Once started, `curl -s localhost:9090/readyz` answers `200` `{"status":"ready","operations":<n>}`; a missing secret makes the start refuse, naming it.
3. Each row of the case table replays from the developer side: [Publish and subscribe](https://flexway.io/en/docs/developer-async/), [Receive signed webhooks](https://flexway.io/en/docs/developer-webhooks/).
