Connecting System Design with Design Patterns
There is often a gap between writing clean code (using design patterns) and building scalable infrastructure (system design). However, these two disciplines are deeply connected. Design patterns often serve as the "micro-architecture" that eventually inspires "macro-architecture" at the system level.
The Conceptual Bridge
System design is often about managing entities over a network, while design patterns are about managing objects in memory.
| Concept | Design Pattern (Local) | System Design (Distributed) |
|---|---|---|
| Communication | Method calls, Events | API Calls, RPC, Message Queues |
| State | Instance variables | Databases, Caches |
| Identity | Object References | UUIDs, URIs |
| Fault Tolerance | Try-catch, Proxies | Retries, Circuit Breakers |
Deep Dive: Observer Pattern vs. Pub/Sub
One of the best examples of this evolution is the Observer Pattern scaling into a Distributed Pub/Sub system.
1. The Design Pattern: Observer (Local)
In the Observer pattern, an object (the Subject) maintains a list of its dependents (Observers) and notifies them automatically of any state changes.
Low-Level Diagram
JavaScript Implementation
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
notify(data) {
this.observers.forEach((observer) => observer.update(data));
}
}
class UserObserver {
update(data) {
console.log(`[User] User stats updated: ${data}`);
}
}
class EmailObserver {
update(data) {
console.log(`[Email] Sending alert: ${data}`);
}
}
// Usage
const newsFeed = new Subject();
newsFeed.subscribe(new UserObserver());
newsFeed.subscribe(new EmailObserver());
newsFeed.notify("New System Design Post Available!");2. The System Design: Pub/Sub (Distributed)
When your application outgrows a single process, you can't just pass object references. You need a Message Broker (e.g., Redis, RabbitMQ, Kafka).
High-Level System Architecture
Mock Distributed Implementation
// A simple mock of a message broker using an external registry
const MockMessageBroker = {
topics: {},
publish(topic, message) {
console.log(`[BROKER] Published to ${topic}: ${JSON.stringify(message)}`);
if (this.topics[topic]) {
this.topics[topic].forEach((callback) => callback(message));
}
},
subscribe(topic, callback) {
if (!this.topics[topic]) this.topics[topic] = [];
this.topics[topic].push(callback);
},
};
// Subscriber Services
const EmailService = (msg) =>
console.log(`[Email Service] Sending: ${msg.body}`);
const AnalyticsService = (msg) =>
console.log(`[Analytics] Logging event: ${msg.id}`);
MockMessageBroker.subscribe("updates", EmailService);
MockMessageBroker.subscribe("updates", AnalyticsService);
// Publisher
MockMessageBroker.publish("updates", {
id: 101,
body: "System Design Patterns released",
});Why This Connection Matters
- Mental Models: If you understand the Observer pattern, you already understand the fundamentals of Event-Driven Architecture.
- Implementation vs. Abstraction: Design patterns provide the "logic," while System Design provides the "infrastructure."
- Growth Path: Every great system designer started by understanding how to organize code at the project level.
Summary
- Observer Pattern = In-process communication.
- Pub/Sub System = Inter-process/Inter-service communication.
- Same goal: Decoupling the producer of data from the consumers.
