Imagine you are launching an online shop. A customer, Leyla, places an order for 87 AZN, and then several things must happen: the order is saved in the database, the warehouse reserves the goods, and Leyla receives a confirmation email. The question is how services tell each other what has happened. The answer decides whether the shop survives Black Friday and the failure of a single service. In this lesson we compare synchronous and asynchronous communication, meet message brokers and learn their delivery guarantees.

Synchronous communication: a chain of HTTP calls

The most obvious way is synchronous: one service calls another over HTTP and waits for the answer, like a phone call. You cannot hang up until the other side answers. Here is an order handler in the orders service (Express 5) that calls the warehouse and the email service one after another:

javascript
app.post('/orders', async (req, res) => {
  const order = await saveOrder(req.body);

  await fetch('http://inventory/reserve', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ orderId: order.id }),
  });
  await fetch('http://notifications/send-email', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ orderId: order.id }),
  });

  res.status(201).json(order);
});

The code is short and clear, but it hides three problems.

Three problems of a call chain

  1. Cascading failures. The order succeeds only if every service answers. Availability multiplies: three services that are each up 99.9% of the time (about 9 hours of downtime a year) together give 0.999³ ≈ 99.7%, already about 26 hours. The email service is down, so orders stop too, although the email is not the main thing.
  2. Latency adds up. The client waits for the sum of all calls: three calls of 200 ms make 600 ms, and if one service "thinks" for 5 seconds, the customer waits as long.
  3. Tight coupling. The orders service knows the addresses of inventory and notifications and the format of every request. If you want to add analytics, you have to change and redeploy the orders service.

A fourth problem is the nastiest: partial success. The warehouse has already reserved the goods, and the call to the email service failed. The customer sees a 500 error, presses "Pay" again and creates a duplicate, and the reserve has to be rolled back by hand.

⚠️ Automatic retries (retry) make such a chain even worse: an already overloaded service receives even more requests.

Asynchronous communication: messages and a broker

The way out is not to call but to leave a message. Picture a restaurant kitchen: the waiter does not stand over the cook. He pins the order slip to the rail and goes to the next table. The cooks take slips when they are free. If a cook is on a break, the slips wait on the rail and are not lost.

In programming the same idea is described with five terms: