Machine Coding Problem

Notification Service

maco30maco60macoAllinfrastructurefactory-methodstrategy-patterndecorator-patternrate-limiting
Commonly Asked By:MetaAmazonAppleGoogle

Requirements & System Scope

Functional Scope (In-Scope)

  • Multi-Channel Dynamic Routing: Route notifications dynamically via SMS, Email, and Push based on cost or speed strategies.
  • Highly Granular Priority Blocking queues: Implement urgent, high, and normal priority levels inside active queues.
  • Pluggable Message Enrichment (Decorator Pattern): Enrich notifications dynamically with features like tracking tokens, base64 encryption, or metadata signatures without changing the core structures.
  • Gateway Rate Limiting: Refill and throttle outbound dispatching to match target SMS/Email API rate capacities.
  • Deduplication Filtering: Safe idempotency check to avoid repeat deliveries of the same messages.

Explicit Boundaries (Out-of-Scope)

  • No Real Network Mail Transport Socket: Bypasses raw SMTP, Twilio API integrations, or APNS configurations.
  • No Dynamic Content Layout Templates: All template processing, localization, and rich formats reside out-of-scope.

Class Diagram & Entity Relationships

Structural layout showing relationships between service handlers, routers, token limiters, and message decorators:

Loading...
  • Polymorphic Delivery Channels: SMS, Email, and Push channels inherit a shared NotificationChannel structure.
  • Strategy Pattern for Routing: The routing strategy selects optimal channels dynamically based on Priority or Cost constraints.
  • Decorator for Content Enrichment: Content transforms dynamically under decorator chains (e.g. encrypting then attaching a tracking suffix).

Design Patterns & SOLID Principles

  • Factory Method Pattern (Channel Drivers): Decoupling specific channel implementations using a structural factory adheres to OCP, enabling developers to attach additional messaging channels (like Slack or WhatsApp) easily.
  • Decorator Pattern (Message Enrichment): Enables adding behaviors (like payload encryption, logging, or delivery tracking) to notifications dynamically at runtime without subclassing the concrete classes.
  • Strategy Pattern (Channel Routing): Encapsulates the decision engine of which channel to target (e.g. prioritize speed by selecting Push, or cost-minimization by using Email) into pluggable classes.
  • Single Responsibility Principle (SRP):NotificationChannel handles structural message formatting and delivery. NotificationService manages sorting, rate limiting, and queueing flows.

Core Execution Workflows

Alert Processing Sequence

  1. Caller submits notification: submitNotification(notification).
  2. Verify deduplication ID standing. If duplicate, discard.
  3. Push the enriched, decorated notification onto the thread-safe Priority Queue.
  4. Dedicated worker threads pick up entries:
    1. Check gateway Rate Limiter. If exhausted, push notification back to queue with transient delay.
    2. Evaluate RoutingStrategy to pick target channel.
    3. Acquire channel driver using ChannelFactory.
    4. Deliver the message. If failed, log and append to Dead Letter Queue (DLQ).

Concurrency & Thread Safety Strategy

When processing high volumes of notifications from concurrent application threads, the scheduler can suffer race conditions:

  • Thread-Safe Priority Queues: Manage operations in thread-safe PriorityBlockingQueue containers.
  • Concurrent Deduplication Stores: Store transaction IDs inside safe concurrency structures (ConcurrentHashMap.newKeySet()) to verify deduplication.
  • Thread-Safe Limiting: Coordinate token refills inside synchronized rate-limiting procedures.

Complete Clean Code Blueprint

Thread-safe priority messaging blueprints in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

enum ChannelType { SMS, EMAIL, PUSH }
enum Priority {
    URGENT(0), HIGH(1), NORMAL(2);
    private final int value;
    Priority(int val) { this.value = val; }
    public int getValue() { return value; }
}

interface SendableNotification {
    String getId();
    String getRecipient();
    String getContent();
    ChannelType getChannel();
    Priority getPriority();
}

class SimpleNotification implements SendableNotification {
    private final String id;
    private final String recipient;
    private final String content;
    private final ChannelType channel;
    private final Priority priority;

    public SimpleNotification(String id, String recipient, String content, ChannelType channel, Priority priority) {
        this.id = id;
        this.recipient = recipient;
        this.content = content;
        this.channel = channel;
        this.priority = priority;
    }

    public String getId() { return id; }
    public String getRecipient() { return recipient; }
    public String getContent() { return content; }
    public ChannelType getChannel() { return channel; }
    public Priority getPriority() { return priority; }
}

// โ”€โ”€โ”€ DECORATOR PATTERN (ENRICHMENT) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
abstract class NotificationDecorator implements SendableNotification {
    protected final SendableNotification decorated;
    public NotificationDecorator(SendableNotification decorated) { this.decorated = decorated; }
    public String getId() { return decorated.getId(); }
    public String getRecipient() { return decorated.getRecipient(); }
    public String getContent() { return decorated.getContent(); }
    public ChannelType getChannel() { return decorated.getChannel(); }
    public Priority getPriority() { return decorated.getPriority(); }
}

class EncryptedNotificationDecorator extends NotificationDecorator {
    public EncryptedNotificationDecorator(SendableNotification decorated) { super(decorated); }
    @Override
    public String getContent() { 
        return "[ENCRYPTED] " + Base64.getEncoder().encodeToString(decorated.getContent().getBytes()); 
    }
}

class TrackingNotificationDecorator extends NotificationDecorator {
    private final String trackerId;
    public TrackingNotificationDecorator(SendableNotification decorated, String trackerId) {
        super(decorated);
        this.trackerId = trackerId;
    }
    @Override
    public String getContent() { 
        return decorated.getContent() + " [TrackingRef: " + trackerId + "]"; 
    }
}

// โ”€โ”€โ”€ STRATEGY PATTERN (DYNAMIC ROUTING) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
interface RoutingStrategy {
    ChannelType route(SendableNotification notification);
}

class CostEffectiveRouting implements RoutingStrategy {
    @Override
    public ChannelType route(SendableNotification n) {
        if (n.getPriority() == Priority.URGENT) return ChannelType.SMS;
        return ChannelType.EMAIL;
    }
}

class PushPreferredRouting implements RoutingStrategy {
    @Override
    public ChannelType route(SendableNotification n) {
        return ChannelType.PUSH;
    }
}

// โ”€โ”€โ”€ RATE LIMITER (TOKEN BUCKET) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class TokenBucketRateLimiter {
    private final int capacity;
    private final long refillRateMs;
    private int tokens;
    private long lastRefillTime;

    public TokenBucketRateLimiter(int capacity, long refillRateMs) {
        this.capacity = capacity;
        this.refillRateMs = refillRateMs;
        this.tokens = capacity;
        this.lastRefillTime = System.currentTimeMillis();
    }

    public synchronized boolean tryAcquire() {
        refill();
        if (tokens > 0) {
            tokens--;
            return true;
        }
        return false;
    }

    private void refill() {
        long now = System.currentTimeMillis();
        long elapsed = now - lastRefillTime;
        if (elapsed >= refillRateMs) {
            int newTokens = (int) (elapsed / refillRateMs);
            tokens = Math.min(capacity, tokens + newTokens);
            lastRefillTime = now;
        }
    }
}

// โ”€โ”€โ”€ FACTORY METHOD FOR CHANNELS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
interface NotificationChannel {
    boolean send(SendableNotification notification);
}

class SMSChannel implements NotificationChannel {
    public boolean send(SendableNotification n) {
        System.out.println("[SMS Gateway] Sending to " + n.getRecipient() + ": " + n.getContent());
        return true;
    }
}

class EmailChannel implements NotificationChannel {
    public boolean send(SendableNotification n) {
        System.out.println("[Email Gateway] Sending to " + n.getRecipient() + ": " + n.getContent());
        return true;
    }
}

class PushChannel implements NotificationChannel {
    public boolean send(SendableNotification n) {
        System.out.println("[Push Gateway] Sending to " + n.getRecipient() + ": " + n.getContent());
        return true;
    }
}

class ChannelFactory {
    private static final Map<ChannelType, NotificationChannel> channels = new ConcurrentHashMap<>();
    static {
        channels.put(ChannelType.SMS, new SMSChannel());
        channels.put(ChannelType.EMAIL, new EmailChannel());
        channels.put(ChannelType.PUSH, new PushChannel());
    }
    public static NotificationChannel getChannel(ChannelType type) {
        return channels.get(type);
    }
}

// โ”€โ”€โ”€ CONCURRENT NOTIFICATION SERVICE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class NotificationService {
    private final PriorityBlockingQueue<SendableNotification> queue = new PriorityBlockingQueue<>(100,
            Comparator.comparingInt(n -> n.getPriority().getValue()));
    private final Set<String> idempotencyStore = ConcurrentHashMap.newKeySet();
    private final List<SendableNotification> deadLetterQueue = new CopyOnWriteArrayList<>();
    private final TokenBucketRateLimiter rateLimiter = new TokenBucketRateLimiter(5, 500); // 5 per 500ms
    private final RoutingStrategy routingStrategy;
    private final ExecutorService executor = Executors.newFixedThreadPool(2);
    private volatile boolean running = true;

    public NotificationService(RoutingStrategy routingStrategy) {
        this.routingStrategy = routingStrategy;
        for (int i = 0; i < 2; i++) {
            executor.submit(this::processQueue);
        }
    }

    public boolean submitNotification(SendableNotification n) {
        if (!idempotencyStore.add(n.getId())) {
            System.out.println("[Service] Discarding duplicate notification ID: " + n.getId());
            return false;
        }
        queue.offer(n);
        return true;
    }

    private void processQueue() {
        while (running) {
            try {
                SendableNotification n = queue.poll(100, TimeUnit.MILLISECONDS);
                if (n == null) continue;

                if (!rateLimiter.tryAcquire()) {
                    System.out.println("[Service] Rate limit hit! Re-queueing notification: " + n.getId());
                    queue.offer(n);
                    Thread.sleep(100);
                    continue;
                }

                ChannelType targeted = routingStrategy.route(n);
                NotificationChannel channel = ChannelFactory.getChannel(targeted);
                
                boolean success = channel.send(n);
                if (!success) {
                    System.out.println("[Service] Delivery failed for: " + n.getId() + ". Sending to DLQ.");
                    deadLetterQueue.add(n);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    public void shutdown() {
        running = false;
        executor.shutdown();
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== Notification Service Concurrent Driver ===");
        NotificationService service = new NotificationService(new CostEffectiveRouting());

        SendableNotification n1 = new SimpleNotification("MSG-001", "+15550199", "Your OTP is 4829", ChannelType.SMS, Priority.URGENT);
        SendableNotification n2 = new SimpleNotification("MSG-002", "alice@example.com", "Welcome onboard!", ChannelType.EMAIL, Priority.NORMAL);
        
        // Wrap with Decorators
        SendableNotification enriched = new TrackingNotificationDecorator(
            new EncryptedNotificationDecorator(n2), "TRACK-999"
        );

        service.submitNotification(n1);
        service.submitNotification(enriched);
        service.submitNotification(n1); // Duplicate check

        Thread.sleep(1000);
        service.shutdown();
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...