dev-resources.site
for different kinds of informations.
Flexibilidad y Escalabilidad: Usando Strategy y Factory Patterns
Introducci贸n
En el desarrollo de software, construir aplicaciones modulares y flexibles es esencial para poder adaptarse a cambios de requerimientos y tecnolog铆as. Los patrones de dise帽o como Strategy y Factory son herramientas poderosas que nos permiten crear soluciones robustas, facilitando la extensi贸n y el mantenimiento del c贸digo. Estos patrones nos ayudan a dise帽ar componentes intercambiables, permitiendo que la l贸gica central (Core) de la aplicaci贸n permanezca independiente de los detalles espec铆ficos de cada implementaci贸n. En este art铆culo, exploraremos c贸mo emplear ambos patrones para gestionar diferentes proveedores de notificaciones de forma din谩mica y escalable.
Implementaremos un sistema de notificaciones en el que INotificationsStrategy
act煤a como la interfaz central que define el contrato para enviar notificaciones, mientras que el Factory Pattern se encarga de construir la estrategia de notificaci贸n adecuada seg煤n el proveedor configurado. Adem谩s, utilizaremos el patr贸n Decorator para simplificar la inyecci贸n de dependencias, inspir谩ndonos en la arquitectura de ASP.NET Core. Veremos c贸mo esta combinaci贸n de patrones nos permite intercambiar servicios de notificaci贸n de manera sencilla, hacerlos configurables y extender nuestro sistema sin modificar el Core de la aplicaci贸n.
C贸digo Fuente: DevToPosts/StrategyFactoryPattern at main 路 isaacOjeda/DevToPosts 路 GitHub
Strategy Pattern
El Strategy Pattern es un patr贸n de dise帽o de comportamiento que permite definir una familia de algoritmos, encapsular cada uno y hacerlos intercambiables. Este patr贸n permite que el algoritmo var铆e independientemente del cliente que lo utiliza.
Utilizando el Strategy Pattern, podemos aplicar diferentes estrategias de ejecuci贸n para un mismo comportamiento en funci贸n de configuraciones o condiciones externas. Este patr贸n es ideal cuando necesitamos flexibilidad en la l贸gica de negocio que se aplicar谩 seg煤n diferentes condiciones sin modificar el c贸digo principal.
Factory Pattern
El Factory Pattern es un patr贸n de dise帽o creacional que abstrae el proceso de creaci贸n de objetos, permitiendo a las clases delegar la responsabilidad de instanciar sus dependencias. La f谩brica selecciona y devuelve la instancia correcta seg煤n las necesidades del cliente.
El factory elimina la necesidad de conocer detalles de las clases concretas en el c贸digo del cliente, centralizando la creaci贸n de objetos y reduciendo el acoplamiento. Este patr贸n es fundamental cuando el tipo de instancia a crear depende de configuraciones externas o de los datos en tiempo de ejecuci贸n.
Al combinar el Strategy Pattern con el Factory Pattern, se logra una arquitectura flexible y escalable, ideal para aplicaciones que deben manejar m煤ltiples implementaciones de un servicio (por ejemplo, m煤ltiples proveedores de notificaciones). La f谩brica es responsable de crear la estrategia apropiada en tiempo de ejecuci贸n, permitiendo al cliente utilizar la estrategia correcta sin necesidad de l贸gica adicional.
Ventajas, Desventajas y Consideraciones de Uso
Ventajas
- Flexibilidad y Extensibilidad: Implementar patrones como Strategy y Factory permite integrar f谩cilmente distintas estrategias o servicios sin afectar el Core de la aplicaci贸n. Esto minimiza el riesgo de errores al realizar cambios y facilita la expansi贸n de funcionalidades.
- Separaci贸n de Responsabilidades: Estos patrones promueven principios como el de Responsabilidad 脷nica y la Inversi贸n de Dependencias, lo que resulta en un c贸digo m谩s organizado y f谩cil de mantener.
- Facilidad para Pruebas: Al estar desacopladas las implementaciones, resulta sencillo realizar pruebas unitarias y de integraci贸n mediante mocks o stubs de las interfaces.
- Escalabilidad: A medida que la aplicaci贸n crece, puedes a帽adir nuevas estrategias o servicios sin modificar la l贸gica del Core, lo cual facilita la evoluci贸n del sistema.
Desventajas
- Complejidad A帽adida: En proyectos peque帽os o con l贸gica sencilla, la implementaci贸n de estos patrones puede resultar en una complejidad innecesaria, generando m谩s trabajo del que realmente aporta.
- Impacto en el Rendimiento: La abstracci贸n adicional puede ralentizar levemente la ejecuci贸n. En aplicaciones de alto rendimiento, es fundamental medir si el costo es justificable.
- Curva de Aprendizaje: Para desarrolladores sin experiencia en patrones de dise帽o, esta estructura puede ser dif铆cil de comprender y mantener.
Consideraciones para su uso
Cu谩ndo utilizar estos patrones:
- Si el sistema interact煤a con m煤ltiples proveedores o servicios que podr铆an cambiar, o si el sistema debe ser flexible y f谩cil de extender, estos patrones son ideales.
- Cuando se usa una arquitectura modular (como Hexagonal o Clean Architecture) y se necesita proteger el Core de la aplicaci贸n de los detalles de implementaci贸n.
Cu谩ndo no utilizarlos:
- En sistemas con una sola implementaci贸n fija que no necesita cambiarse.
- En aplicaciones simples, donde la abstracci贸n adicional puede considerarse sobreingenier铆a.
Implementando Strategy Pattern Con Factory
En este ejemplo, implementamos una infraestructura de notificaciones utilizando Strategy para definir varias maneras de enviar notificaciones (a trav茅s de SendGrid o SMTP) y Factory para decidir cu谩l estrategia utilizar en funci贸n de la configuraci贸n.
Application Layer
No entraremos en detalle sobre la organizaci贸n espec铆fica del c贸digo, pero estas estrategias suelen emplearse en arquitecturas como Hexagonal o Clean Architecture. Aunque no nos centraremos en c贸mo estructurar el c贸digo, s铆 es esencial entender qu茅 elementos forman parte del "Core" de la aplicaci贸n y cu谩les corresponden a detalles externos de los que queremos aislarnos.
Definir un contrato en el Core es fundamental. Este contrato determina las entradas y salidas esperadas sin depender de detalles de implementaci贸n espec铆ficos. De esta forma, el Core act煤a como una "caja negra": no le interesa c贸mo se implementa cada detalle, solo que se cumpla el contrato definido. Esta flexibilidad hace que el c贸digo sea altamente intercambiable, y aqu铆 es donde los patrones Strategy y Factory se vuelven especialmente 煤tiles.
Nota 馃挕: He implementado estas estrategias en diversos escenarios (almacenamiento, pasarelas de pago, procesamiento de documentos, etc.), y este v铆deo me inspir贸 a escribir este art铆culo, ya que es una t茅cnica valiosa en muchos de mis proyectos.
Notifications > INotificationsStrategy
Definimos una interfaz INotificationsStrategy
, que act煤a como contrato para las distintas estrategias de notificaci贸n. Cada implementaci贸n de esta interfaz representar谩 un proveedor de notificaciones distinto (por ejemplo, SendGrid y SMTP).
public interface INotificationsStrategy
{
聽 聽 Task<SendNotificationResponse> SendNotification(SendNotificationRequest request);
}
La interfaz asegura que cualquier servicio de notificaci贸n que implementemos tendr谩 el mismo m茅todo SendNotification
. Esto permite que los clientes (la aplicaci贸n) usen cualquier servicio de notificaci贸n de manera intercambiable, asegurando consistencia en la estructura del c贸digo.
Es fundamental que SendNotificationRequest
y SendNotificationResponse
permanezcan en el Core de la aplicaci贸n y est茅n completamente desvinculadas de los detalles espec铆ficos de cada proveedor de notificaciones. Estas clases son las piezas de informaci贸n que el Core necesita para realizar una notificaci贸n, y al definirlas de forma abstracta, nos aseguramos de que el Core pueda operar independientemente de los detalles espec铆ficos de cada implementaci贸n.
Dentro de SendNotificationRequest
, podr铆amos incluir datos como el correo electr贸nico, n煤mero de tel茅fono u otros detalles del usuario que son comunes en las notificaciones, sin que estos datos dependan de un proveedor espec铆fico. Cada implementaci贸n puede necesitar configuraciones 煤nicas, pero el Core deber铆a mantenerse lo m谩s aislado posible de estas particularidades.
Por ejemplo, una implementaci贸n podr铆a requerir una API Key para acceder a su servicio, mientras que otra podr铆a exigir autenticaci贸n mediante un usuario y clave secreta para obtener un token. Aunque estas diferencias pueden ser significativas, el Core no debe preocuparse por estos detalles de autenticaci贸n y configuraci贸n. Los patrones Strategy y Factory nos ayudan a gestionar estas variaciones, permiti茅ndonos abstraer esas diferencias y mantener el Core limpio y centrado en su funci贸n principal.
Notifications > INotificationsFactory
La INotificationsFactory
es la interfaz del factory. Define m茅todos para crear instancias de servicios de notificaci贸n bas谩ndose en el proveedor configurado.
public interface INotificationsFactory
{
聽 聽 INotificationsStrategy CreateNotificationService();
}
La f谩brica encapsula la l贸gica de selecci贸n de proveedores. Esto significa que cualquier cambio en el m茅todo de selecci贸n no afectar谩 a los consumidores de los servicios de notificaci贸n.
Notifications > NotificationsConfig
NotificationsConfig
es una clase de configuraci贸n que contiene las configuraciones de los distintos proveedores de notificaciones.
public class NotificationsConfig
{
聽 聽 public NotificationProvider Provider { get; set; }
聽 聽 public SendGridConfig? SendGrid { get; set; }
聽 聽 public SmtpConfig? Smtp { get; set; }
}
public class SendGridConfig
{
聽 聽 //...
}
public class SmtpConfig
{
聽 聽 //...
}
La propiedad Provider
en NotificationsConfig
define el proveedor predeterminado configurado en nuestra aplicaci贸n. Este proveedor se establece a trav茅s de appsettings.json
, lo que facilita configurar y ajustar el servicio de notificaciones sin necesidad de modificar el c贸digo.
Si bien en este ejemplo usamos appsettings.json
, el prop贸sito del Strategy Pattern es permitir la intercambiabilidad de estrategias en tiempo de ejecuci贸n. Esto significa que, adem谩s de ser configurable en el archivo de configuraci贸n, la estrategia de notificaci贸n podr铆a adaptarse din谩micamente seg煤n las preferencias del usuario, del tenant o cualquier otra l贸gica espec铆fica de la aplicaci贸n. Este enfoque a帽ade flexibilidad al sistema y ayuda a personalizar el servicio para distintos contextos o necesidades en tiempo real.
Infrastructure Layer
Notifications > SendGrid
Implementamos SendGridNotificationService
, que utiliza el proveedor de SendGrid para enviar notificaciones. Esta clase es una implementaci贸n concreta de INotificationsStrategy
.
public class SendGridNotificationService : INotificationsStrategy
{
聽 聽 private readonly ILogger<SendGridNotificationService> _logger;
聽 聽 public SendGridNotificationService(ILogger<SendGridNotificationService> logger)
聽 聽 {
聽 聽 聽 聽 _logger = logger;
聽 聽 }
聽 聽 public async Task<SendNotificationResponse> SendNotification(SendNotificationRequest request)
聽 聽 {
聽 聽 聽 聽 _logger.LogInformation("Using SendGrid to send notification");
聽 聽 聽 聽 // SendGrid logic here
聽 聽 聽 聽 await Task.Delay(0);
聽 聽 聽 聽 return new SendNotificationResponse(true, "Notification sent successfully");
聽 聽 }
}
Notifications > Smtp
SmtpNotificationsService
es otra implementaci贸n de INotificationsStrategy
, que env铆a notificaciones utilizando un servidor SMTP.
public class SmtpNotificationsService : INotificationsStrategy
{
聽 聽 private readonly ILogger<SmtpNotificationsService> _logger;
聽 聽 public SmtpNotificationsService(ILogger<SmtpNotificationsService> logger)
聽 聽 {
聽 聽 聽 聽 _logger = logger;
聽 聽 }
聽 聽 public async Task<SendNotificationResponse> SendNotification(SendNotificationRequest request)
聽 聽 {
聽 聽 聽 聽 _logger.LogInformation("Using SMTP to send notification");
聽 聽 聽 聽 // Smtp logic here
聽 聽 聽 聽 await Task.Delay(0);
聽 聽 聽 聽 return new SendNotificationResponse(true, "Notification sent successfully");
聽 聽 }
}
Cada clase implementa su propia l贸gica de env铆o de notificaciones, pero siguen el mismo contrato INotificationsStrategy
. Esto asegura que el c贸digo cliente puede cambiar entre distintas implementaciones sin conocer detalles de su funcionamiento interno.
NotificationsService y NotificationsFactory
En NotificationsFactory
, creamos las instancias de los servicios de notificaci贸n bas谩ndonos en el proveedor configurado.
public class NotificationsFactory(
聽 聽 ILogger<NotificationsFactory> logger,
聽 聽 IServiceProvider serviceProvider,
聽 聽 IOptions<NotificationsConfig> notificationsConfig) : INotificationsFactory
{
聽 聽 public INotificationsStrategy CreateNotificationService()
聽 聽 {
聽 聽 聽 聽 var provider = notificationsConfig.Value.Provider;
聽 聽 聽 聽 logger.LogInformation("Creating notification service for provider {Provider}", provider);
聽 聽 聽 聽 return provider switch
聽 聽 聽 聽 {
聽 聽 聽 聽 聽 聽 NotificationProvider.SendGrid => serviceProvider.GetRequiredService<SendGridNotificationService>(),
聽 聽 聽 聽 聽 聽 NotificationProvider.Smtp => serviceProvider.GetRequiredService<SmtpNotificationsService>(),
聽 聽 聽 聽 聽 聽 _ => throw new NotImplementedException($"Notification provider {provider} is not implemented")
聽 聽 聽 聽 };
聽 聽 }
聽 聽 public INotificationsStrategy CreateNotificationService(NotificationProvider provider)
聽 聽 {
聽 聽 聽 聽 //...
聽 聽 }
}
NotificationsFactory
centraliza la creaci贸n de objetos. Dependiendo del proveedor especificado, crea una instancia del servicio correspondiente sin que el cliente necesite saber c贸mo se instancia.
Ejemplo de appsettings.json
:
聽 "Notifications": {
聽 聽 "Provider": "Smtp",
聽 聽 "SendGrid": {
聽 聽 聽 "ApiKey": "SG.1234567890"
聽 聽 },
聽 聽 "Smtp": {
聽 聽 聽 "Server": "smtp.server.com",
聽 聽 聽 "Port": 587,
聽 聽 聽 "Username": "username",
聽 聽 聽 "Password": "password"
聽 聽 }
聽 }
Aunque en este punto podemos utilizar el Factory directamente en cualquier parte de nuestro c贸digo, podemos aplicar el patr贸n Decorator para simplificar a煤n m谩s el uso de INotificationsStrategy
y hacer que la creaci贸n de instancias sea m谩s transparente. Para ello, crearemos la clase NotificationService
, que ser谩 el punto de acceso principal cuando solicitemos la dependencia INotificationsStrategy
. Esto significa que nuestro c贸digo usar谩 NotificationService
, mientras que NotificationService
se encargar谩 internamente de invocar al Factory para crear la instancia de la estrategia correcta.
Nota 馃挕: Este m茅todo de creaci贸n de objetos est谩 inspirado en la implementaci贸n de
IStringLocalizer
yIStringLocalizerFactory
en ASP.NET Core. Al usarIStringLocalizer
, el framework invoca internamente el Factory para crear las instancias requeridas en funci贸n de la configuraci贸n establecida.
M谩s informaci贸n: aspnetcore/src/Localization/Abstractions/src/StringLocalizerOfT.cs at main 路 dotnet/aspnetcoreNota 2馃挕: Explorar el c贸digo fuente puede ser de gran ayuda. Revisar, leer y ejecutar el c贸digo no solo profundiza el conocimiento de estos conceptos, sino que tambi茅n puede ayudarte a ver c贸mo los patrones se aplican en la pr谩ctica.
public class NotificationService(INotificationsFactory notificationsFactory) : INotificationsStrategy
{
聽 聽 private INotificationsStrategy notificationsStrategy = notificationsFactory.CreateNotificationService();
聽 聽 public async Task<SendNotificationResponse> SendNotification(SendNotificationRequest request)
聽 聽 {
聽 聽 聽 聽 return await notificationsStrategy.SendNotification(request);
聽 聽 }
}
Program.cs
En el archivo Program.cs
, configuramos la inyecci贸n de dependencias. Aqu铆 registramos INotificationsFactory
, INotificationsStrategy
y las implementaciones espec铆ficas de cada proveedor.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<INotificationsStrategy, NotificationService>();
builder.Services.AddScoped<INotificationsFactory, NotificationsFactory>();
builder.Services.AddScoped<SendGridNotificationService>();
builder.Services.AddScoped<SmtpNotificationsService>();
builder.Services.Configure<NotificationsConfig>(builder.Configuration.GetSection("Notifications"));
var app = builder.Build();
app.MapPost("/send", async (SendNotificationRequest request, INotificationsStrategy notificationService) =>
{
聽 聽 var response = await notificationService.SendNotification(request);
聽 聽 return response.IsSuccess ? Results.Ok(response) : Results.BadRequest(response);
});
app.Run();
Conclusi贸n
Al combinar los patrones Strategy y Factory, hemos creado un sistema de notificaciones flexible y f谩cil de extender, en el que distintas implementaciones de notificaci贸n pueden integrarse sin afectar la l贸gica central de la aplicaci贸n. Esta estructura modular permite que el c贸digo sea adaptable y escalable, lo que es fundamental en entornos en los que los requisitos y las tecnolog铆as cambian constantemente. La incorporaci贸n de un patr贸n Decorator adicional facilita la inyecci贸n de dependencias, manteniendo el uso de las notificaciones simple y organizado.
Estos patrones aportan claridad y robustez a la arquitectura del sistema, permitiendo que el Core de la aplicaci贸n se mantenga desacoplado de los detalles de implementaci贸n espec铆ficos de cada proveedor de notificaciones. Este enfoque tambi茅n fomenta la reutilizaci贸n y facilita el mantenimiento a largo plazo, ya que se pueden agregar o modificar servicios sin necesidad de realizar cambios significativos. La implementaci贸n de patrones de dise帽o bien aplicados no solo mejora la estructura del c贸digo, sino que tambi茅n enriquece el proceso de desarrollo, proporcionando una base s贸lida para futuros requerimientos y funcionalidades.
Featured ones: