Machine Coding Problem

Notification Service

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

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.

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();
    }
}