AsyncNotify is a practical asynchronous notification project developed to consolidate knowledge in clean architecture, event-driven architecture, and microservices.
The idea is to simulate a real notification sending flow: a message sent by a client is transformed into an event and processed asynchronously, ensuring decoupling and scalability in the system.
This project was developed with a focus on:
-
Applying good architectural practices to the backend
-
Using messaging with Redis for inter-service communication
-
Building an ecosystem of integrated microservices with Docker and FastAPI
-
Simulating real-world notification channels such as email, SMS, and push notifications
-
Clean Architecture: Clear separation of responsibilities between the domain, application, infrastructure, and interface layers, ensuring modularization, decoupling, and good coding practices, including SOLID principles.
-
Messaging with Redis: Use of Redis as a shared event queue between microservices.
-
Asynchronous Processing: Implementation of asynchronous tasks to process requests without blocking new requests or tasks.
-
Communication between Microservices: Use of the HTTP protocol and message queues for direct and indirect communication between services.
-
Event-Driven Architecture: Use of an event queue (notifications) to promote asynchronous processing between services.
-
APIs with FastAPI: Creation of clear, high-performance endpoints with integrated documentation.
-
API Gateway: Creation of an API gateway so that clients do not connect directly to microservices and are redirected through it.
-
Logging: Implementation of logs to record and monitor system behavior.
-
Dockerized Microservices: Containerization and local orchestration of services with Docker Compose.
A first view of the Async Notifications is showed below:
Explained the flow:
- A client or service sends a request to create a notification to the API Gateway.
- The API Gateway forwards the request to the notification-publisher-service.
- The notification-publisher-service creates a notification event:
- Fetches contact data from the user-service.
- Publishes the event to the notifications queue.
- The notification-dispatcher-worker consumes the events from the notifications queue.
- The dispatcher processes each event and converts it into a concrete notification.
- The notification is sent to the user through the specified channel (email, SMS, push, etc.).
- The user receives the notification.
- Receive notification requests via HTTP API
- Publish notification events to a Redis queue
- Process notification events asynchronously, without blocking the client
- Forward notifications to specific channels (email, SMS, etc.)
- Query user contact information in a dedicated microservice
- Modular structure that allows you to easily add new notification channels
- Python + FastAPI
- Uvicorn
- Redis
- Dockerfile
- Docker Compose
The notification system consists of two main microservices and one worker:
notification-publisher: Receives a client message, converts it to an event, and publishes the event to a message queue.
user-service: Provides user contact information to assist in sending notifications.
notification-dispatch: Consumes events from the message queue, processes them, and forwards them to specific notification dispatch channels (email, SMS, push, etc.).
Responsible for receiving notification requests and publishing an event.
POST /notification: receive notication with:
{
"user_id": "123",
"message": "Seu agendamento foi confirmado.",
"channel": "email" // or "sms"
}The field "channel" can be equal to "email"or "sms"
Send request by curl:
curl -X POST http://localhost:8000/notifications \
-H "Content-Type: application/json" \
-d '{"user_id":"testuser","message":"test message", "channel":"email"}'
Responsible for sending user contact information to assist in sending notifications.
GET /users/{user_id}/contact-info
Returns:
{
"email": "usertest@gmail.com",
"sms": "++55 99999999",
"preferred_channel":"email"
}Send request by curl:
curl http://localhost:8000/users/testuser/contact-info
The system includes a worker that listens to the message queue, processes incoming events, and routes notifications to specific channels such as Email and SMS.
The worker is organized into two main submodules:
DispatcherChannel– responsible for transforming generic notifications into channel-specific formats and sending them via the corresponding provider.
Consumer – responsible for consuming messages from the queue, validating events, and passing them to the dispatcher for delivery.
The following sections describe each submodule in detail.
The DispatchChannel is responsible for receiving generic notifications and forwarding them to specific delivery channels.
Each channel has its own dispatcher:
-
SMSDispatchChannel: converts the notification to SMS format and sends it via SMS service.
-
EmailDispatchChannel: converts the notification to email format and sends it via email service.
This structure keeps the system decoupled, facilitates the addition of new channels, and ensures that each notification is delivered through the correct channel in a scalable manner.
Flow of notificaiton inside the DispatchChanel:
-
Clean Architecture: I adopted Clean Architecture to keep the code organized, with clear separation of responsibilities and loose coupling. This facilitates maintenance and the addition of new technologies without significantly impacting the core code.
-
Redis: I used Redis as an event queue to implement asynchronous processing between services. I chose Redis because it was a simple and efficient solution for the initial scope of the project, allowing for easy implementation and subsequent evolution to more robust tools, if necessary.
-
Event-Driven Architecture: I combined Clean Architecture with an event-driven approach, enabling services to process events asynchronously and decoupled.
-
Asynchronous Programming: I applied asynchronous programming to avoid API blocking and ensure higher performance when processing multiple concurrent tasks.
-
FastAPI: I chose FastAPI as the framework for building the APIs due to its performance, native support for data validation with Pydantic, and automatic generation of interactive documentation.
-
Microservices: I structured the system into two microservices, reinforcing decoupling and separation of responsibilities.
-
Asynchronous Worker: I created a dedicated worker for executing background tasks. Instead of being a full microservice, it acts as a specialized component for consuming the queue and processing events.
Orchestration with Docker Compose: I used Docker Compose to orchestrate the application's services locally. This ensures a standardized environment, facilitates running multiple services in containers, and simplifies the development and testing process.
The project is executed using docker-compose. So first it is necessary to have an installed docker in machine.
After,
1- Clone the repository:
git clone https://github.com/karllabatista/AsynNotify.git
cd asyncnotify
2- Up the services
docker-compose up --build
3- Send a request:
curl -X POST -d '{"user_id":"user_test","message": "your purchase was completed successfully"}' http://localhost:8000/notifications
- The user service has an in-memory database. This means that existing users are present in these in-memory database.
- The notification-dispatcher worker has a routing channel that directs notifications through specific channels. For this project and for an initial architecture, the notification sending services were called FakerChannelTypeService. But the architecture has the possibility of adding a real email/SMS service.
-
Orchestrate microservices using a cloud orchestrator such as Kubernetes, Amazon ECS, or similar solutions.
-
Enhance API Gateway with connection retries and failure handling when communicating with services.
-
Introduce a new message queue such as Apache Kafka or Amazon SQS/SNS to improve scalability and event-driven architecture.
-
Deploy the system to the cloud (preferably AWS) for more realistic production scenarios.
-
Implement fault recovery, idempotency, and resilience mechanisms to ensure reliable processing.
-
Expand automated test coverage to increase confidence and maintainability.
-
Integrate real sending providers (e.g. SMTP, SES, SendGrid)

