Command Palette

Search for a command to run...

PHASE 4Intermediate ~8 min· topic 12 of 18Level 3

Problem 4.12 — Notification System (LLD)

In one line

The factory + strategy + observer showcase: channels, templates, fan-out and retries.

0/18 · 0%

Think of it like this

The notifications you get from an app: sometimes an email, sometimes an SMS, sometimes a push alert. The system behind it decides which channel to use and what to say, then sends it and retries if it fails.

Key ideas

  1. 01

    Channels: EMAIL, SMS, PUSH, IN-APP — each a ChannelStrategy with its own send() and failure semantics.

  2. 02

    Templates: {userName} placeholders rendered against a template engine — keeps content consistent.

  3. 03

    Delivery: best-effort with retries + dead-letter for undeliverable; per-channel backoff policies.

  4. 04

    Fan-out: one event (order placed) → many users (customer, restaurant, admin) → Observer/EventBus.

  5. 05

    Preference system: user can mute channels (opt-out rules evaluated before dispatch).

  6. 06

    Batching & rate limits: don't hammer the SMS gateway; queue per channel.

  7. 07

    Track per-notification status: QUEUED, SENT, FAILED, RETRYING, DLQ — feed the ops story.

Java / Spring map

  • →

    Spring: NotificationService(ChannelRegistry channels, TemplateEngine templates); @EventListener per channel consumer.

Code & diagrams

NotificationService.javajava

Strategy (channels) + Factory (registry) + Observer (event fan-out), the three patterns this problem is built to showcase.

public interface Channel {
  String name();
  void send(String to, String message) throws ChannelException;
}

@Component class EmailChannel implements Channel {
  public String name() { return "EMAIL"; }
  public void send(String to, String msg) { /* SMTP */ }
}
@Component class SmsChannel implements Channel {
  public String name() { return "SMS"; }
  public void send(String to, String msg) { /* SMS gateway */ }
}

@Service
public class NotificationService {
  private final Map<String, Channel> channels;          // built from all Channel beans — Factory/Registry
  private final UserPreferences preferences;

  public NotificationService(List<Channel> all, UserPreferences preferences) {
    this.channels = all.stream().collect(Collectors.toMap(Channel::name, c -> c));
    this.preferences = preferences;
  }

  @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2))
  public void notify(String userId, String channelName, String template, Map<String, String> vars) {
    if (preferences.isMuted(userId, channelName)) return;      // opt-out checked before dispatch
    Channel channel = channels.get(channelName);
    if (channel == null) throw new IllegalArgumentException("unknown channel: " + channelName);
    String message = render(template, vars);
    try {
      channel.send(preferences.addressFor(userId, channelName), message);
    } catch (ChannelException e) {
      throw e;                                                 // @Retryable retries; after 3 tries → DLQ handler
    }
  }

  private String render(String template, Map<String, String> vars) {
    String out = template;
    for (var e : vars.entrySet()) out = out.replace("{" + e.getKey() + "}", e.getValue());
    return out;
  }
}

// Fan-out (Observer): one event, many notifications, each independently retried and mutable.
@EventListener
public void onOrderPlaced(OrderPlacedEvent e) {
  notificationService.notify(e.customerId(), "SMS", "order-confirmed", Map.of("id", e.orderId()));
  notificationService.notify(e.restaurantId(), "PUSH", "new-order", Map.of("id", e.orderId()));
}

Explain without notes

01

Order of decisions: who decides a user gets an SMS — the orchestrator or the channel? Justify.

Practice

01

Add an EMAIL channel with HTML template + retry-3-times-with-backoff; add a 'mute marketing' preference.

Trade-offs

  • ↔

    Sync sends (simple, slow) vs queue-backed async (fast, exactly-once-adjacent but complex). Choose async, defend it.

Run it in production

Completion checklist

  • I can justify channel + template + preference + retry separation from first principles.

Back to phase