Menu
Dev.to #systemdesign·May 22, 2026

Designing a Scalable Notification Microservice with Domain-Driven Principles

This article outlines the architectural evolution and design principles behind building a robust notification microservice, moving from simple functions to a complex, decoupled system. It emphasizes applying Domain-Driven Design (DDD) to separate concerns: business events, delivery policies, and channel adapters, ensuring scalability and maintainability as notification requirements grow.

Read original on Dev.to #systemdesign

Notifications in modern applications quickly evolve from simple functions to complex, multi-channel systems. What starts as a direct `sendEmail` call can balloon into a sprawling codebase handling various channels (email, SMS, push, Slack), user preferences, quiet hours, batching, throttling, and analytics. Without a proper architectural approach, this complexity can overwhelm engineering teams.

The Core Insight: Separating Concerns

The fundamental principle for a scalable notification system is the clear separation of concerns, aligning with Domain-Driven Design (DDD). The article identifies three distinct concerns within a notification workflow:

  1. Domain Event: What happened in the business (e.g., "order placed", "user mentioned"). The business service emits this event without knowing how it will be delivered.
  2. Delivery Policy: What kind of message to send and to whom, based on user preferences and event type (e.g., "transactional email", "high-urgency push"). This is determined by the notification service.
  3. Channel Adapter: How to actually send the message using a specific provider (e.g., "render email template, call SendGrid API").
ℹ️

Why Decouple?

Traditional approaches often collapse these three concerns into a single function, leading to tight coupling. Changes in business logic, delivery preferences, or external API integrations cause ripple effects across the codebase. Decoupling ensures that each concern can evolve independently, greatly improving maintainability and flexibility.

Architectural Components

A typical notification microservice leveraging these principles would involve:

  • Event Bus: Business services publish domain events (e.g., `order.shipped`) to an event bus (Kafka, RabbitMQ, etc.). This makes the operation asynchronous and fire-and-forget for the originating service.
  • Notification Service: Consumes these domain events. It queries user notification preferences (which could be stored internally or in a separate profile service) to determine which channels are enabled for a given user and event type.
  • Dispatcher: A core component within the notification service that handles an incoming event, applies user preferences and business rules (like quiet hours), and then generates specific delivery jobs for each eligible channel.
  • Channel-specific Workers/Queues: Each channel (email, SMS, push) has dedicated workers that process delivery jobs, rendering templates and interacting with external providers. This allows for specific retry policies, rate limiting, and error handling per channel.

This modular architecture allows for easy expansion to new notification channels, flexible management of user preferences, and robust handling of delivery logic, making the notification system highly adaptable to evolving business needs without impacting core business logic.

notificationsmicroservicesdomain-driven designevent-driven architecturedecouplingscalabilitysystem design patterns

Comments

Loading comments...