In this document, I design and implement YASMIN (Yet Another System to Manage Instant Noti- ﬁcations) that supports sending notiﬁcations using any channel (e.g. SMS, Email, Sockets). The software includes abstract classes and entities that allow the building of further notiﬁcation systems depending on the concrete project. YASMIN also provides capabilities to write as many channel implementations as you want. Keywords OOP Notiﬁcation system Design and Implementation Software Engineering 1 Introduction How many times have software developers solved the problem of sending notiﬁcations? Quite a lot, I think. And I bet that implementation differs from project to project. First, you start sending only email notiﬁcations, but as the project grows, you realise that now your customer wants to integrate different notiﬁcation channels. They could include Slack messages, SMS, Push, and many more. To deal with that, there should be a simple uniﬁed system that could handle all those channels easily. During my research, I encountered a few notiﬁcation systems that could help to solve this problem. But some of them are not what I was looking for, some are too complicated to integrate into a small project [1, 2]. What I propose is to design a high-level, abstract system that can be easily extended to meet your project needs. It is important to understand that YASMIN is not a full implementation, but only the idea. It only gives you basic "ﬂow" of how to extend your system further. You still need to write your channel providers (e.g. Sendgrid integration for Email channel, Twilio for SMS, etc.). I have implemented notiﬁcations using this approach on several projects and it works just ﬁne! If for some reason YASMIN basic implementation is not suitable for your project (notiﬁcation entity or provider should have additional ﬁelds and methods, etc.), you can always rewrite it or create your own implementation following the architecture and main concept (which is very easy as you will see). 2 Design The main requirement of the system is to handle several notiﬁcation channels at the same time and be easily extendable. For now, we can think of the architecture that might look like Figure 1. We have an email notiﬁer that sends a message to a bunch of email addresses. To extend receiving channels we create a new class that extends the base Email Notiﬁer. It is quite simple, isn’t it? Of course it is simple and this implementation will work. You just create a class for each notiﬁcation channel and use it. But it brings problems if we want to send notiﬁcations through different channels at the same time. With this https://liashchynskyi.net Project YASMIN TECHNICAL REPORT Figure 1: The simplest design Figure 2: New structure implementation, we would need to somehow combine existing notiﬁers. Something like this: we must have Email + SMS Notiﬁer, Email + Slack Notiﬁer, Email + SMS + Slack, etc. You got the idea: we must create a combined type with each of the existing ones. That is not a very clean and good approach. Because the number of total channel combinations will grow alongside channel numbers that need to be implemented. We can choose another and more elegant way. We could decorate our providers. With this approach we’d have a base Provider with only one method send() and a bunch of other providers (email, sms) that will implement the Provider bahaviour. This approach gives something like chain method execution. For example, we ﬁrst send Email notiﬁcation, then SMS, then Slack, etc. The best thing about it is that we can conﬁgure chain of providers for each user in the system. 2 Project YASMIN TECHNICAL REPORT As can be seen from Figure 2, the notiﬁcation system consists of a few classes. Let’s discuss the code implementation. 3 Implementation Firstly, let’s create some interfaces and abstract classes. export interface Notification { id: string; receiverId: string; payload: any; } export class TestNotification implements Notification { id = "random-uui"; receiverId = "user-uuid"; payload = { text: "hello" }; } export interface INotifier { send(notification: Notification): Promise<Notification>; } export abstract class Notifier implements INotifier { protected constructor(private readonly notifier: INotifier) {} async send(notification: Notification): Promise<Notification> { return this.notifier.send(notification); } } Notification interface is used as the basic notiﬁcation that other notiﬁcations will inherit from. There are a lot of properties that your project might need here, but you can start with mandatory ones such as ID of the notiﬁcation, ID of the notiﬁcation receiver (usually id of the user in database), and notiﬁcation payload (this is the data, you want to transmit). Notifier is the abstract class that other providers for each of the receiving channels must extend. Let’s deﬁne some providers (aka notiﬁers). export class StubNotifier implements INotifier { async send(notification: Notification): Promise<Notification> { return notification; } } The next notiﬁer is Email. Remember that you can pass needed dependencies in constructor. export class EmailNotifier extends Notifier { constructor(notifier: INotifier, private readonly emailService: any) { super(notifier); } async send(notification: Notification): Promise<Notification> { // Add logic to send via Email here and call super // Your code goes here ->>> return super.send(notification); } } SMS notiﬁer goes below. 3 Project YASMIN TECHNICAL REPORT export class SmsNotifier extends Notifier { constructor(notifier: INotifier) { super(notifier); } async send(notification: Notification, private readonly smsService: any): Promise<Notification> { // Add logic to send via SMS here and call super // Your code goes here ->>> return super.send(notification); } } This is the example how to use it. let notifier = new DefaultNotifier(); // Check if receiver has Email channel enabled const emailService; // sendgrid, mailchimp, etc. notifier = new EmailNotifier(notifier, emailService) // Check if receiver has SMS channel enabled const smsService; // AWS SNS, Twilio, etc. notifier = new SmsNotifier(notifier, smsService) const notification = notifier.send(new TestNotification()) // Do further processing, save to database, etc. 4 Discussion With the YASMIN approach, you can conﬁgure as many providers as possible. Just extend the base class, put the logic, call the send from the superclass and that’s it. The main advantage is that you can enable/disable some of the providers based on the receiver’s notiﬁcation p