Message Queues

We do not have to build the list of orders ourselves. There is ready-made software that holds work from one program until another program takes it. One kind is the message queue. Let’s name its parts.

Message

A message is the data that one program leaves for another to handle. Order 93 is a message:

{
  "orderId": "93",
  "shopId": "mugshop",
  "shopperId": "12",
  "cartId": "80",
  "idempotencyKey": "b71e"
}

The message carries the IDs that the program placing the order needs. The order itself is already saved with the status PROCESSING.

Message queue

A message queue is a list that holds messages until a program takes them. We call the list of orders the order queue. New orders join one end of the queue, and the program that places orders takes them from the other end:

graph TD
    C[Commerce service: handles requests] -->|order 95| Q
    subgraph Q[Order queue]
        direction LR
        O94[order 94] ~~~ O93[order 93] ~~~ O92[order 92]
    end
    Q -->|order 91| W[Commerce service: places orders]

Broker

The broker, or message broker, is the software that keeps the queues. Here, it keeps the order queue:

graph TD
    C[Commerce service: handles requests] -->|order 95| B
    subgraph B[Broker]
        subgraph Q[Order queue]
            direction LR
            O94[order 94] ~~~ O93[order 93] ~~~ O92[order 92]
        end
    end
    B -->|order 91| W[Commerce service: places orders]

Producer and consumer

The producer puts messages on a queue. Here, it is the part of the commerce service that handles placeOrder requests. In the diagram, it sends order 95 to the broker.

The consumer takes messages from a queue and handles them. Here, it is the part of the commerce service that places the orders. In the diagram, it takes order 91 from the broker.

The order queue

Here is the commerce service with the order queue inside it:

graph TD
    S[Storefront] -->|placeOrder| H
    subgraph C[Commerce service]
        H[Handles requests] -->|order 93| MQ[Message queue]
        MQ -->|order 93| W[Places orders]
    end
    H -->|save order 93: PROCESSING| DB[("Primary of Mugshop's shard")]
    W -->|place order 93| DB

Both parts of the commerce service write to the primary. The part that handles requests saves each new order with the status PROCESSING. The part that places orders runs the transaction.

Tracing order 93

Here is order 93 again, from the request to the payment, with the parts named:

sequenceDiagram
    participant S as Storefront
    participant C as Producer: handles requests
    participant B as Broker: order queue
    participant W as Consumer: places orders
    participant Pay as Payment service
    participant P as Primary
    S->>C: placeOrder
    C->>P: Save order 93: PROCESSING
    C->>B: Message: order 93
    B->>B: Save order 93 in the order queue
    B-->>C: Saved
    C-->>S: Order 93, processing
    Note over B: Order 93 waits behind earlier messages
    W->>B: Next message
    B-->>W: Order 93
    W->>P: Run the transaction
    W->>Pay: Take the payment
    W->>P: Set order 93: PLACED

A consumer that is waiting gets a new message as soon as the message reaches the queue. Brokers do this in one of two ways. In the first, the consumer keeps a connection open to the broker, and the broker sends the next message over it. In the second, the consumer sends a request for messages, and the broker holds the request until a message arrives. This second way is long polling, the same technique we used for chat.