Publish/Subscribe and Asynchronous Processing
Everything in this chapter so far is asynchronous. The order worker publishes and does not wait, and each subscriber handles its copy later. But publish/subscribe and asynchronous processing are two separate ideas:
- Asynchronous processing is about time: the work is done later, and the sender does not wait.
- Publish/subscribe is about who receives a message.
Asynchronous without publish/subscribe
Neither idea needs the other. The order queue, for example, is asynchronous, and the order worker is the only receiver of each message on it.
In practice, the two are often used together. A publisher with many subscribers should not wait for all of them to finish.
Publish/subscribe inside one program
The other way around is publish/subscribe that is not asynchronous. Node.js has it inside one program:
const { EventEmitter } = require("node:events");
const orders = new EventEmitter();
orders.on("order-placed", (order) => console.log("Email for order", order.orderId));
orders.on("order-placed", (order) => console.log("Ship order", order.orderId));
orders.emit("order-placed", { shopId: "mugshop", orderId: "93" });
console.log("Done");
on subscribes a function to order-placed, and emit publishes to it. The emit call does not name the functions, so the code that publishes does not know who receives the message. Is Done printed before or after the two other lines? Here is the output:
Email for order 93
Ship order 93
Done
Done is printed last. emit calls each subscribed function and returns only after the last one finishes. So the publisher waits for every subscriber. This is publish/subscribe, and it is synchronous. Node’s documentation says that emit “synchronously calls each of the listeners”.
Email is printed before Ship for a different reason. Node calls the functions in the order they were subscribed.
You may have heard that Node’s events are asynchronous. Node’s I/O is asynchronous: a file read, a network request, or a timer finishes later, and Node calls the callback then. But emit itself is a loop that calls each function and then returns. If a function takes two seconds, Done waits two seconds. Other frameworks work the same way. In Spring, a Java framework, publishing an event waits by default until every listener has finished.
Three kinds of independence
So publish/subscribe and asynchronous processing are separate ideas. Each one makes a sender and a receiver independent in a different way. There are three kinds of independence:
- Identity: the sender does not need to know who receives the message.
- Time: the receiver does not need to be running when the message is sent.
- Waiting: the sender does not wait for the receiver to handle the message.
These three come from a 2003 survey of publish/subscribe, “The Many Faces of Publish/Subscribe”, by Eugster, Felber, Guerraoui, and Kermarrec.
What each one adds
Each idea adds some of the three:
- Asynchronous processing adds independence of time and waiting: the message waits in the broker until the receiver takes it.
- Publish/subscribe adds independence of identity: the sender does not know who receives the message, or how many receivers there are.
- A topic in a broker is both, so it has all three.