Notification System
A service that sends the same alert over email, SMS, or push without the caller ever
knowing which one actually fired. The interesting decision isn't "how do I send an SMS" -
it's how to keep NotificationService ignorant of the channel it's using.
Requirements
Functional
- A caller sends a notification to a user through one or more channels (email, SMS, push).
- Each channel has its own delivery mechanism and its own way of formatting a message.
- A user can opt out of a channel; the service must not deliver through a channel the user has disabled.
- Failed sends should be retried a bounded number of times before being given up on.
Non-functional
- Adding a new channel (say, WhatsApp) must not require editing
NotificationServiceor any existing channel class. - Sending must not block the caller on slow network channels for longer than necessary (this page models the retry loop; a real system would hand delivery to a queue).
Design
NotificationService takes a message and a list of NotificationChannel implementations
and fires each one - it never has an if (channel == EMAIL) anywhere. Each channel owns
its own formatting and its own transport, so a new channel is a new class, not a new branch.
- 1The caller hands over a user and a message - nothing about how it should be delivered.
- 2The service asks preferences which channels this user actually wants, not which channels exist.
- 3For each enabled channel the service calls the same method, regardless of channel type.
- 4Each channel formats the message its own way - SMS truncates, email adds a subject line.
- 5Retry policy lives in the service so it applies uniformly, not per channel.
Retrying lives in the service, not the channel, because "how many times to retry" is a policy decision that should be the same regardless of which channel is flaky today.
Class diagram
implementsuses
Code
Design decisions
NotificationChannelis an interface, not a switch statement. The alternative - one method with a branch per channel type - means every new channel touches a shared file and risks breaking an unrelated one. An interface makes each channel additive: drop in a class, register it, done.- Opt-out lives on
NotificationPreferences, not baked into each channel. A channel only knows how to send; whether it should send for a given user is a separate question answered once, in one place, instead of every channel re-implementing the same check. - Retry is a wrapper around
send, not duplicated in every channel.NotificationServicecallssendWithRetry, which calls the channel'ssendup to a fixed number of times. Every channel gets retry behavior for free and none of them had to write it themselves. - What's missing for a real system: delivery here is synchronous and in-process; a production version would enqueue each channel send onto a message queue so a slow SMS provider can't hold up an email that would have gone through instantly, and would track delivery receipts per channel rather than a single boolean.