Skip to content

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.

ConceptDesign Pattern (Local)System Design (Distributed)
CommunicationMethod calls, EventsAPI Calls, RPC, Message Queues
StateInstance variablesDatabases, Caches
IdentityObject ReferencesUUIDs, URIs
Fault ToleranceTry-catch, ProxiesRetries, 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

javascript
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

javascript
// 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

  1. Mental Models: If you understand the Observer pattern, you already understand the fundamentals of Event-Driven Architecture.
  2. Implementation vs. Abstraction: Design patterns provide the "logic," while System Design provides the "infrastructure."
  3. 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.

Released under the ISC License.