Cancelling with a Topic

Recall how cancelling an order worked when we split Shopend into services. A shopper cancels an order before it ships. The commerce service marks the order as cancelled and puts the stock back. Then it calls the payment service to refund the payment, the shipping service to stop the shipment, and the notification service to send an email. In the original service-oriented architecture, each call was a remote procedure call, so the commerce service had to wait for each one to return before it responded to the shopper. At the time, we left open what happens when one of these calls fails.

Since then, we have added queues and topics to Shopend. Let’s use them for the cancellation.

The order’s status

An order is now PROCESSING, PLACED, or FAILED. A cancelled order needs a status of its own, so we add CANCELLED:

enum OrderStatus {
  PROCESSING
  PLACED
  FAILED
  CANCELLED
}

A shopper can cancel an order that is PLACED. An order that is still PROCESSING cannot be cancelled, because the order worker may be placing it at that moment.

Publishing the cancellation

Suppose the shopper cancels order 93. The storefront sends this mutation:

mutation {
  cancelOrder(id: "93") {
    id
    status
  }
}

The commerce service does two things:

  1. In one transaction, it sets order 93 to CANCELLED and puts the mug back in stock.
  2. It publishes a message to a new topic, order-cancelled, and waits until the broker confirms that it has the message.

Then it responds to the storefront with order 93 and the status CANCELLED.

The message carries the shop ID and the order ID:

{ "shopId": "mugshop", "orderId": "93" }

Three services subscribe to order-cancelled:

  • The payment service, to refund the payment. Its new refund queue is on the topic’s list.
  • The shipping service, to stop the shipment. Its shipping queue is already on the list of order-placed, and now it is on the list of order-cancelled too.
  • The notification service, to send the cancellation email. Its email queue is on the list of order-cancelled too.

Recall that each copy arrives with the name of its topic. So the shipping service can tell an order to ship from an order to stop, and the notification service can tell which email to send.

graph TD
    C[Commerce service] -->|publish| OC
    subgraph B[Broker]
        OC[order-cancelled topic] --> RQ[Refund queue]
        OC --> SQ[Shipping queue]
        OC --> EQ[Email queue]
    end
    RQ --> P[Payment service]
    SQ --> Sh[Shipping service]
    EQ --> N[Notification service]

The analytics service could subscribe as well, to count cancelled orders. I leave it out to keep the example small.

Why a topic

Recall the rule for when to use a queue: the sender does not need the result in its response, and the work comes in bursts, takes too long to do inside a request, or goes to a service that may be slow or down. Each of the three calls fits. The commerce service does not need any of the results to tell the shopper that the order is cancelled, and each call goes to a service that may be slow or down. More than one service acts on the same message, so the three queues are on one topic’s list.

If the payment service is down when the shopper cancels, the refund message waits in the refund queue. The payment service takes it when it is back. Meanwhile, the commerce service cancels the order and responds to the shopper without waiting for the refund.

What each message costs to lose

The three messages are not equally important. If the cancellation email is lost, the shopper can still see on the storefront that the order is cancelled. If the refund is lost, the shopper paid for an order that was cancelled and never gets the money back.

So the payment service must make the refund. It must also make it only once. If the shopper gets the money back twice, Mugshop loses the price of the order.

If the parcel has already left when the message to stop it arrives, the cancellation needs more steps, such as a return. That is beyond the scope of this course.