Saltearse al contenido

Diagrama de Componentes - Microservicio de Notificaciones

Descripción General

El Microservicio de Notificaciones es responsable de orquestar todas las notificaciones multicanal en la plataforma Algesta. Escucha eventos de dominio de otros microservicios y envía notificaciones vía email (SendGrid), WhatsApp (Jelou) y opcionalmente notificaciones push.

El microservicio maneja:

  • Activadores de notificación basados en eventos de los Microservicios de Órdenes y Proveedores
  • Entrega multicanal (email, WhatsApp, push)
  • Renderizado de mensajes basado en plantillas con datos dinámicos
  • Historial de notificaciones y seguimiento de estado
  • Mecanismo de reintento con retroceso exponencial para entregas fallidas
  • Notificaciones programadas (recordatorios, alertas de expiración)

Diagrama de Componentes

graph TB
    subgraph "Capa de Aplicación"
        NotificationController["NotificationController<br/>[REST Controller]<br/>Endpoints de notificación manual"]

        SendEmailHandler["SendEmailHandler<br/>[Command Handler]<br/>Envía notificación por email vía SendGrid"]
        SendWhatsAppHandler["SendWhatsAppHandler<br/>[Command Handler]<br/>Envía mensaje de WhatsApp vía Jelou"]
        SendPushHandler["SendPushHandler<br/>[Command Handler]<br/>Envía notificación push"]
        ScheduleNotificationHandler["ScheduleNotificationHandler<br/>[Command Handler]<br/>Programa notificación futura"]
        RetryFailedNotificationHandler["RetryFailedNotificationHandler<br/>[Command Handler]<br/>Reintenta notificación fallida"]

        GetNotificationHistoryHandler["GetNotificationHistoryHandler<br/>[Query Handler]<br/>Recupera historial de notificaciones"]
        GetNotificationEstadoHandler["GetNotificationEstadoHandler<br/>[Query Handler]<br/>Obtiene estado de entrega de notificación"]

        OrderCreatedEventHandler["OrderCreatedEventHandler<br/>[Event Handler]<br/>Envía confirmación cuando se crea orden"]
        OrderPublishedEventHandler["OrderPublishedEventHandler<br/>[Event Handler]<br/>Notifica a proveedores de nueva subasta"]
        AuctionClosedEventHandler["AuctionClosedEventHandler<br/>[Event Handler]<br/>Notifica al ganador y proveedores rechazados"]
        ProviderSelectedEventHandler["ProviderSelectedEventHandler<br/>[Event Handler]<br/>Notifica al proveedor de asignación"]
        QuotationSentEventHandler["QuotationSentEventHandler<br/>[Event Handler]<br/>Envía cotización al cliente"]
        OrderApprovedEventHandler["OrderApprovedEventHandler<br/>[Event Handler]<br/>Notifica al proveedor para iniciar ejecución"]
        ExecutionReportEventHandler["ExecutionReportEventHandler<br/>[Event Handler]<br/>Envía informe de ejecución al cliente"]
        DocumentoEstadoEventHandler["DocumentoEstadoEventHandler<br/>[Event Handler]<br/>Notifica al proveedor del estado del documento"]
        DocumentoExpiringEventHandler["DocumentoExpiringEventHandler<br/>[Event Handler]<br/>Envía recordatorio de expiración al proveedor"]

        NotificationRetryTask["NotificationRetryTask<br/>[Tarea Programada]<br/>Reintenta notificaciones fallidas cada hora"]
        ExpiryReminderTask["ExpiryReminderTask<br/>[Tarea Programada]<br/>Envía recordatorios de expiración diarios"]
    end

    subgraph "Capa de Dominio"
        NotificationEntity["Notification Entity<br/>[Aggregate Root]<br/>Modelo de dominio de notificación"]
        NotificationTemplateVO["NotificationTemplate<br/>[Objeto de Valor]<br/>Plantilla con marcadores de posición"]
        NotificationChannelEnum["NotificationChannel<br/>[Enum]<br/>EMAIL, WHATSAPP, PUSH"]
        NotificationEstadoEnum["NotificationEstado<br/>[Enum]<br/>PENDING, SENT, FAILED, RETRYING"]
        NotificationRepositorio["NotificationRepositorio<br/>[Interface]<br/>Abstracción de repositorio"]
    end

    subgraph "Capa de Infraestructura"
        NotificationRepositorioImpl["NotificationRepositorioImpl<br/>[Implementación Mongoose]<br/>Persistencia MongoDB para notificaciones"]
        SendGridEmailService["SendGridEmailService<br/>[Servicio Externo]<br/>Entrega de email vía SendGrid API"]
        WhatsAppService["WhatsAppService<br/>[Servicio Externo]<br/>Entrega de WhatsApp vía Jelou API"]
        PushNotificationService["PushNotificationService<br/>[Servicio Externo]<br/>Entrega de notificación push"]
        TemplateRenderingService["TemplateRenderingService<br/>[Servicio de Dominio]<br/>Renderiza plantillas con Handlebars"]
        MessageBrokerService["MessageBrokerService<br/>[Suscriptor de Eventos]<br/>Se suscribe a eventos de Redis/Kafka"]
    end

    subgraph "Dependencias Externas"
        MongoDB[("MongoDB<br/>[Base de datos]<br/>Base de datos de notificaciones")]
        SendGridAPI["SendGrid API<br/>[Sistema Externo]<br/>Servicio de entrega de email"]
        JelouAPI["Jelou API<br/>[Sistema Externo]<br/>Plataforma de bot de WhatsApp"]
        RedisKafka[("Redis/Kafka<br/>[Message Broker]<br/>Transmisión de eventos")]
    end

    NotificationController --> SendEmailHandler
    NotificationController --> SendWhatsAppHandler
    NotificationController --> SendPushHandler
    NotificationController --> ScheduleNotificationHandler
    NotificationController --> GetNotificationHistoryHandler
    NotificationController --> GetNotificationEstadoHandler

    SendEmailHandler --> NotificationEntity
    SendEmailHandler --> NotificationRepositorio
    SendEmailHandler --> SendGridEmailService
    SendEmailHandler --> TemplateRenderingService
    SendWhatsAppHandler --> NotificationEntity
    SendWhatsAppHandler --> NotificationRepositorio
    SendWhatsAppHandler --> WhatsAppService
    SendWhatsAppHandler --> TemplateRenderingService
    SendPushHandler --> NotificationEntity
    SendPushHandler --> NotificationRepositorio
    SendPushHandler --> PushNotificationService
    ScheduleNotificationHandler --> NotificationRepositorio
    RetryFailedNotificationHandler --> NotificationRepositorio
    RetryFailedNotificationHandler --> SendEmailHandler
    RetryFailedNotificationHandler --> SendWhatsAppHandler

    GetNotificationHistoryHandler --> NotificationRepositorio
    GetNotificationEstadoHandler --> NotificationRepositorio

    OrderCreatedEventHandler --> SendEmailHandler
    OrderCreatedEventHandler --> SendWhatsAppHandler
    OrderPublishedEventHandler --> NotificationRepositorio
    OrderPublishedEventHandler --> SendEmailHandler
    AuctionClosedEventHandler --> SendEmailHandler
    AuctionClosedEventHandler --> SendWhatsAppHandler
    ProviderSelectedEventHandler --> SendEmailHandler
    ProviderSelectedEventHandler --> SendWhatsAppHandler
    QuotationSentEventHandler --> SendEmailHandler
    QuotationSentEventHandler --> SendWhatsAppHandler
    OrderApprovedEventHandler --> SendEmailHandler
    OrderApprovedEventHandler --> SendWhatsAppHandler
    ExecutionReportEventHandler --> SendEmailHandler
    ExecutionReportEventHandler --> SendWhatsAppHandler
    DocumentoEstadoEventHandler --> SendEmailHandler
    DocumentoEstadoEventHandler --> SendWhatsAppHandler
    DocumentoExpiringEventHandler --> SendEmailHandler
    DocumentoExpiringEventHandler --> SendWhatsAppHandler

    NotificationRetryTask --> NotificationRepositorio
    NotificationRetryTask --> RetryFailedNotificationHandler
    ExpiryReminderTask --> NotificationRepositorio

    MessageBrokerService --> OrderCreatedEventHandler
    MessageBrokerService --> OrderPublishedEventHandler
    MessageBrokerService --> AuctionClosedEventHandler
    MessageBrokerService --> ProviderSelectedEventHandler
    MessageBrokerService --> QuotationSentEventHandler
    MessageBrokerService --> OrderApprovedEventHandler
    MessageBrokerService --> ExecutionReportEventHandler
    MessageBrokerService --> DocumentoEstadoEventHandler
    MessageBrokerService --> DocumentoExpiringEventHandler
    MessageBrokerService --> RedisKafka

    NotificationRepositorio --> NotificationRepositorioImpl
    NotificationRepositorioImpl --> MongoDB

    SendGridEmailService --> SendGridAPI
    WhatsAppService --> JelouAPI

    style NotificationController fill:#438dd5,stroke:#2e6295,color:#ffffff
    style SendEmailHandler fill:#85bbf0,stroke:#5a8bc4,color:#000000
    style SendWhatsAppHandler fill:#85bbf0,stroke:#5a8bc4,color:#000000
    style SendPushHandler fill:#85bbf0,stroke:#5a8bc4,color:#000000
    style ScheduleNotificationHandler fill:#85bbf0,stroke:#5a8bc4,color:#000000
    style RetryFailedNotificationHandler fill:#85bbf0,stroke:#5a8bc4,color:#000000
    style GetNotificationHistoryHandler fill:#a8d5ba,stroke:#7cb99a,color:#000000
    style GetNotificationEstadoHandler fill:#a8d5ba,stroke:#7cb99a,color:#000000
    style OrderCreatedEventHandler fill:#ffd93d,stroke:#ccad30,color:#000000
    style OrderPublishedEventHandler fill:#ffd93d,stroke:#ccad30,color:#000000
    style AuctionClosedEventHandler fill:#ffd93d,stroke:#ccad30,color:#000000
    style ProviderSelectedEventHandler fill:#ffd93d,stroke:#ccad30,color:#000000
    style QuotationSentEventHandler fill:#ffd93d,stroke:#ccad30,color:#000000
    style OrderApprovedEventHandler fill:#ffd93d,stroke:#ccad30,color:#000000
    style ExecutionReportEventHandler fill:#ffd93d,stroke:#ccad30,color:#000000
    style DocumentoEstadoEventHandler fill:#ffd93d,stroke:#ccad30,color:#000000
    style DocumentoExpiringEventHandler fill:#ffd93d,stroke:#ccad30,color:#000000
    style NotificationRetryTask fill:#ff9ff3,stroke:#cc7fc2,color:#000000
    style ExpiryReminderTask fill:#ff9ff3,stroke:#cc7fc2,color:#000000
    style NotificationEntity fill:#6bcf7f,stroke:#56a466,color:#ffffff
    style NotificationRepositorioImpl fill:#ff6b6b,stroke:#cc5555,color:#ffffff
    style MongoDB fill:#ff6b6b,stroke:#cc5555,color:#ffffff
    style SendGridAPI fill:#999999,stroke:#6b6b6b,color:#ffffff
    style JelouAPI fill:#999999,stroke:#6b6b6b,color:#ffffff
    style RedisKafka fill:#48dbfb,stroke:#2e9cba,color:#000000

Figura: Diagrama de Componentes del Microservicio de Notificaciones - Orquestación de notificaciones basada en eventos con entrega multicanal vía email y WhatsApp


Notification Flow Sequence Diagram

Event-Driven Notification Example: Order Created

sequenceDiagram
    participant OrdersMS as Orders MS
    participant Broker as Redis/Kafka
    participant EventHandler as OrderCreatedEventHandler
    participant TemplateService as TemplateRenderingService
    participant EmailHandler as SendEmailHandler
    participant WhatsAppHandler as SendWhatsAppHandler
    participant SendGrid as SendGrid API
    participant Jelou as Jelou API
    participant DB as MongoDB

    OrdersMS->>Broker: publish(OrderCreatedEvent)
    Broker->>EventHandler: OrderCreatedEvent received
    EventHandler->>TemplateService: render("order_created_email", orderData)
    TemplateService-->>EventHandler: Rendered email HTML
    EventHandler->>EmailHandler: execute(SendEmailCommand)
    EmailHandler->>SendGrid: send email to client
    SendGrid-->>EmailHandler: success
    EmailHandler->>DB: save notification (Estado: SENT, channel: EMAIL)
    EmailHandler-->>EventHandler: email sent

    EventHandler->>TemplateService: render("order_created_whatsapp", orderData)
    TemplateService-->>EventHandler: Rendered WhatsApp message
    EventHandler->>WhatsAppHandler: execute(SendWhatsAppCommand)
    WhatsAppHandler->>Jelou: send message to client
    Jelou-->>WhatsAppHandler: success
    WhatsAppHandler->>DB: save notification (Estado: SENT, channel: WHATSAPP)
    WhatsAppHandler-->>EventHandler: WhatsApp sent

    Note over EventHandler: Both notifications sent successfully

Figura: Order Created Notification Flow - Event-driven notification example showing multi-channel delivery (email + WhatsApp) triggered by order creation event


Stack Tecnológico

DependencyVersionPurpose
@nestjs/core11.xNestJS framework core
@nestjs/cqrs11.xCQRS pattern Implementación
@nestjs/mongoose11.xMongoDB integration via Mongoose
@nestjs/Microservicios11.xRedis/Kafka client for messaging
@nestjs/schedule4.xScheduled tasks for retries and reminders
mongoose8.xMongoDB ODM
@sendgrid/mail8.xSendGrid email API client
axios1.xHTTP client for Jelou API
handlebars4.xTemplate rendering engine
class-validator0.14.xDTO validation
class-transformer0.5.xDTO transformation

Key Design Patterns

Event-Driven Arquitectura

The Notifications MS is purely event-driven, listening to events from other Microservicios:

Subscribed Events:

  • OrderCreatedEvent - Send confirmation to client
  • OrderPublishedEvent - Notify eligible providers of new auction
  • AuctionClosedEvent - Notify winner and rejected providers
  • ProviderSelectedEvent - Notify provider of assignment
  • QuotationSentToClientEvent - Send quotation to client
  • OrderApprovedEvent - Notify provider to start execution
  • ExecutionReportSubmittedEvent - Send execution report to client
  • DocumentoEstadoChangedEvent - Notify provider of Documento approval/rejection
  • DocumentoExpiringEvent - Send expiry reminder to provider
  • DocumentoExpiredEvent - Notify provider of expired Documento

Each event handler is responsible for:

  1. Receiving the event from message broker
  2. Determining recipients (client, provider, agent)
  3. Selecting appropriate templates
  4. Rendering templates with event data
  5. Dispatching notifications via appropriate channels
  6. Recording notification history

Template Pattern for Notifications

Templates are stored as Handlebars templates with placeholders:

order_created_email.hbs
<!DOCTYPE html>
<html>
<head>
<title>Order Confirmation - Algesta</title>
</head>
<body>
<h1>Order Created Successfully</h1>
<p>Dear {{clientName}},</p>
<p>Your order #{{orderId}} has been created successfully.</p>
<h2>Order Details:</h2>
<ul>
<li>Service Type: {{serviceType}}</li>
<li>Description: {{description}}</li>
<li>Location: {{location}}</li>
<li>Priority: {{priority}}</li>
</ul>
<p>We will notify you once a provider is assigned.</p>
<p>Thank you for choosing Algesta!</p>
</body>
</html>
order_created_whatsapp.hbs
🎉 *Orden Creada Exitosamente*
Hola {{clientName}},
Tu orden #{{orderId}} ha sido creada.
*Detalles:*
- Servicio: {{serviceType}}
- Descripción: {{description}}
- Ubicación: {{location}}
- Prioridad: {{priority}}
Te notificaremos cuando se asigne un proveedor.
¡Gracias por elegir Algesta!

Template Rendering Service:

@Injectable()
export class TemplateRenderingService {
private handlebars = Handlebars.create();
async renderTemplate(templateName: string, data: any): Promise<string> {
const templatePath = path.join(__dirname, 'templates', `${templateName}.hbs`);
const templateSource = await fs.readFile(templatePath, 'utf-8');
const template = this.handlebars.compile(templateSource);
return template(data);
}
}

Retry Pattern with Exponential Backoff

Failed notifications are retried with exponential backoff:

@Injectable()
export class NotificationRetryTask {
private readonly maxRetries = 3;
private readonly baseDelay = 1000; // 1 second
@Cron('0 * * * *') // Every hour
async retryFailedNotifications() {
const failedNotifications = await this.notificationRepository.findByStatus('FAILED');
for (const notification of failedNotifications) {
if (notification.retryCount < this.maxRetries) {
const delay = this.calculateBackoff(notification.retryCount);
await this.delay(delay);
try {
await this.commandBus.execute(
new RetryFailedNotificationCommand(notification.id)
);
notification.status = 'SENT';
notification.retryCount++;
} catch (error) {
notification.retryCount++;
if (notification.retryCount >= this.maxRetries) {
notification.status = 'FAILED_PERMANENTLY';
}
}
await this.notificationRepository.update(notification);
}
}
}
private calculateBackoff(retryCount: number): number {
return this.baseDelay * Math.pow(2, retryCount); // 1s, 2s, 4s
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

Scheduled Tasks for Background Processing

NotificationRetryTask:

  • Runs hourly (via @Cron('0 * * * *'))
  • Queries failed notifications
  • Retries with exponential backoff
  • Marks as permanently failed after max retries

ExpiryReminderTask:

  • Runs daily at midnight
  • Queries scheduled notifications for the day
  • Sends expiry reminders (30, 15, 7 days before)
  • Sends other scheduled notifications

Notification Data Model

interface Notification {
id: string;
channel: NotificationChannel; // EMAIL, WHATSAPP, PUSH
status: NotificationStatus; // PENDING, SENT, FAILED, RETRYING, FAILED_PERMANENTLY
recipientType: 'CLIENT' | 'PROVIDER' | 'AGENT';
recipientId: string;
recipientEmail?: string;
recipientPhone?: string;
subject?: string; // For email
message: string;
templateName: string;
templateData: object;
sentAt?: Date;
failedAt?: Date;
errorMessage?: string;
retryCount: number;
maxRetries: number;
relatedEntityType: 'ORDER' | 'PROVIDER' | 'AUCTION' | 'DOCUMENT';
relatedEntityId: string;
createdAt: Date;
updatedAt: Date;
}

Integration Points

SendGrid Integration

Email Delivery:

@Injectable()
export class SendGridEmailService {
constructor(
@Inject('SENDGRID_API_KEY') private apiKey: string,
) {
sgMail.setApiKey(this.apiKey);
}
async sendEmail(to: string, subject: string, html: string): Promise<void> {
const msg = {
to,
from: 'notifications@algesta.com',
subject,
html,
};
try {
await sgMail.send(msg);
} catch (error) {
throw new EmailDeliveryException(error.message);
}
}
}

Jelou Integration

WhatsApp Delivery:

@Injectable()
export class WhatsAppService {
constructor(
private readonly httpService: HttpService,
@Inject('JELOU_API_URL') private jelouApiUrl: string,
@Inject('JELOU_API_KEY') private jelouApiKey: string,
) {}
async sendMessage(phone: string, message: string): Promise<void> {
const url = `${this.jelouApiUrl}/v1/messages`;
const headers = {
'Authorization': `Bearer ${this.jelouApiKey}`,
'Content-Type': 'application/json',
};
const body = {
phone,
message,
type: 'text',
};
try {
await this.httpService.post(url, body, { headers }).toPromise();
} catch (error) {
throw new WhatsAppDeliveryException(error.message);
}
}
}

Referencias Cruzadas