Lab
Publish / subscribe
// A scan on a topic: the topic is what happened, to which parcel.
type Message = { topic: string; parcel: string };
// A subscription: the topics it wants, and its own inbox of unread copies.
type Subscription = {
id: number;
topics: string[];
inbox: Message[];
};
// The depot's broker: scanners publish to it, the depot's screens and
// services subscribe to it, and none of them know each other.
class Broker {
nextId = 1;
subscriptions: Subscription[] = [];
subscribe(topics: string[]): number {
const id = this.nextId++;
this.subscriptions.push({ id, topics, inbox: [] });
return id;
}
// Leaves a copy in every subscription that wants this topic.
publish(topic: string, parcel: string) {
for (const s of this.subscriptions) {
if (!s.topics.includes(topic)) continue;
s.inbox.push({ topic, parcel });
}
}
// Hands one subscriber its unread copies, oldest first, and empties its inbox.
drain(id: number): Message[] {
const s = this.subscriptions.find((s) => s.id === id);
if (!s) return [];
const messages = s.inbox;
s.inbox = [];
return messages;
}
}
// The notifier texts the customer when a parcel is delivered or fails,
// through an SMS gateway that is sometimes down.
class Notifier {
gatewayUp = true;
sent: string[] = [];
text(parcel: string, event: string) {
if (!this.gatewayUp) throw new Error('sms gateway down');
this.sent.push(parcel + ' ' + event);
}
// Runs every few minutes.
catchUp(broker: Broker) {
// Nothing to read yet: the depot texts directly.
}
}
// The depot: scanners at its doors report each parcel's scan here.
class Depot {
broker = new Broker();
notifier = new Notifier();
// A scan: sorted, out (for delivery), delivered, or failed (delivery).
scan(parcel: string, event: string) {
if (event === 'delivered' || event === 'failed') {
this.notifier.text(parcel, event);
}
}
// Each service reads what is waiting for it.
catchUp() {
this.notifier.catchUp(this.broker);
}
}