Skip to content

joseguzman25/notifications-lib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

📦 Notifications Library

Librería modular y extensible para envío de notificaciones construida con:

  • Java 21
  • Arquitectura Hexagonal (Ports & Adapters)
  • Sealed Classes
  • Records
  • Estrategia de Reintentos (Retry)
  • Notificaciones Asíncronas con CompletableFuture
  • Logging estructurado con SLF4J
  • Patrón Builder
  • Soporte multi-proveedor (SMS, Email, Push)

🏗 Arquitectura

El proyecto sigue el enfoque de Arquitectura Hexagonal, separando claramente responsabilidades:

notifications
├── notifications-app        # Proyecto consumidor (uso de la librería)
│   ├── src/main/java
│   ├── src/main/resources
│   └── Dockerfile
│
├── notifications-lib        # Librería principal
│   ├── application
│   ├── domain
│   ├── infrastructure
│   ├── bootstrap
│   └── test
│
└── pom.xml                  # Parent POM (multi-módulo)

🔎 Capas

🧠 Domain

  • Modelos de negocio (record)
  • Validaciones
  • Excepciones selladas (sealed)
  • Resultado de notificación (NotificationResult)

⚙ Application

  • Orquestación del envío (DefaultNotificationSender)
  • Estrategia de reintentos (SimpleRetryStrategy)
  • Soporte asíncrono (CompletableFuture)
  • Registro de canales (ChannelsRegistry)

🔌 Infrastructure

  • Adaptadores hacia proveedores externos:
    • Twilio (SMS)
    • Firebase (Push)
    • SendGrid (Email)
    • Mailgun (Email)

🚀 Bootstrap

  • Configuración mediante patrón Builder (NotificationSenderBuilder)

📬 Proveedores Soportados

📱 SMS

  • TwilioSmsAdapter

📧 Email

  • SendGridEmailAdapter
  • MailgunEmailAdapter

🔔 Push

  • FirebasePushAdapter

🧩 Principios de Diseño Aplicados

  • Fail-Fast (validación de configuración en constructor)
  • Separación clara de responsabilidades
  • Retry manejado en capa de aplicación
  • Infraestructura desacoplada del dominio
  • Logging estructurado
  • Extensible (Open/Closed Principle)
  • Soporte síncrono y asíncrono
  • Alta cohesión y bajo acoplamiento

🛠 Ejemplo de Uso

Construcción del NotificationSender (Síncrono)

NotificationSender sender = NotificationSenderBuilder.builder()
        .withSms(new TwilioSmsAdapter("SID", "TOKEN"))
        .withEmail(new SendGridEmailAdapter("SENDGRID_API_KEY"))
        .withPush(new FirebasePushAdapter("FIREBASE_SERVICE_KEY"))
        .retries(3)
        .build();

sender.send(new SmsNotification("+51999999999", "Mensaje SMS"));

sender.send(new EmailNotification(
        "joseguzman@oh.com",
        "Aceptación",
        "Ha sido aceptado"
));

sender.send(new PushNotification(
        "device-token",
        "Hello via PUSH!",
        "Ha sido aceptado"
));

Construcción del NotificationSender (Asíncrono)

NotificationSender sender = NotificationSenderBuilder.builder()
        .withSms(new TwilioSmsAdapter("SID", "TOKEN"))
        .withEmail(new SendGridEmailAdapter("SENDGRID_API_KEY"))
        .withPush(new FirebasePushAdapter("FIREBASE_SERVICE_KEY"))
        .retries(3)
        .build();

CompletableFuture<NotificationResult> future =
        sender.sendAsync(
                new SmsNotification("+51999999999", "Hello via SMS!")
        );

future.thenAccept(result ->
        System.out.println("Resultado async: " + result)
);

future.join();

🔁 Estrategia de Reintentos

El mecanismo de reintentos se implementa mediante la clase:

SimpleRetryStrategy

Esta estrategia permite ejecutar una operación múltiples veces en caso de error, hasta alcanzar un número máximo de intentos configurado.

La lógica de reintento se encuentra en la capa de aplicación, garantizando que los adaptadores de infraestructura permanezcan enfocados únicamente en la comunicación con el proveedor externo.

Beneficios:

  • Separación clara de responsabilidades
  • Mayor testabilidad
  • Reutilización de la estrategia
  • Posibilidad de implementar nuevas estrategias (por ejemplo, Exponential Backoff)

➕ Cómo Agregar un Nuevo Canal de Notificación

La arquitectura está diseñada para ser extensible sin modificar el código existente (Open/Closed Principle).

Para agregar un nuevo canal, por ejemplo WhatsApp, solo debes seguir estos pasos:


1️⃣ Crear el Modelo en Domain

Definir un nuevo record que implemente la interfaz Notification.

package com.notifications.lib.domain.model;

public record WhatsAppNotification(
        String phoneNumber,
        String message
) implements Notification {

    public WhatsAppNotification {
        if (phoneNumber == null || phoneNumber.isBlank()) {
            throw new ValidationException("Phone number cannot be empty");
        }

        if (message == null || message.isBlank()) {
            throw new ValidationException("Message cannot be empty");
        }
    }
}

2️⃣ Crear el Adapter en Infrastructure

Implementar un proveedor que use el puerto NotificationProviderPort.

package com.notifications.lib.infrastructure.whatsapp;

public class WhatsAppAdapter
        implements NotificationProviderPort<WhatsAppNotification> {

    @Override
    public NotificationResult send(WhatsAppNotification notification) {
        
        System.out.println("Sending WhatsApp to "
                + notification.phoneNumber());

        return NotificationResult.ok();
    }
}

3️⃣ Registrar el Nuevo Canal en el Builder

Extender el NotificationSenderBuilder:

public NotificationSenderBuilder withWhatsApp(
        NotificationProviderPort<WhatsAppNotification> provider
) {
    registry.register(WhatsAppNotification.class, provider);
    return this;
}

4️⃣ Usarlo en la Aplicación

NotificationSender sender = NotificationSenderBuilder.builder()
        .withWhatsApp(new WhatsAppAdapter())
        .build();

sender.send(new WhatsAppNotification(
        "+51999999999",
        "Hola desde WhatsApp"
));

🧪 Ejecutar Pruebas

Para ejecutar las pruebas unitarias:

mvn clean test

📦 Construir la Librería

Para generar el artefacto .jar e instalarlo en el repositorio local de Maven:

mvn clean install

Esto permitirá que otros proyectos puedan utilizar la librería como dependencia.


🐳 Docker

Si estás utilizando el módulo notifications-app, puedes construir una imagen Docker para ejecutar la aplicación en un contenedor.

Construir la imagen

docker build -t notifications-app .

Ejecutar el contenedor

docker run notifications-app

🚀 Mejoras Futuras

  • Implementar Exponential Backoff en la estrategia de reintentos
  • Agregar Circuit Breaker
  • Integración con Micrometer para métricas
  • Configuración de timeouts por proveedor
  • Soporte multi-provider con fallback automático
  • Observabilidad avanzada

👨‍💻 Autor

Jose Guzman Gonzales
Senior Java Developer

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published