Logo

dev-resources.site

for different kinds of informations.

Flexibilidad y Escalabilidad: Usando Strategy y Factory Patterns

Published at
10/26/2024
Categories
dotnet
aspnetcore
csharp
patterns
Author
isaacojeda
Categories
4 categories in total
dotnet
open
aspnetcore
open
csharp
open
patterns
open
Author
10 person written this
isaacojeda
open
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);
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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
{
  //...
}
Enter fullscreen mode Exit fullscreen mode

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");
  }
}
Enter fullscreen mode Exit fullscreen mode

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");
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
  {
    //...
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
 }
Enter fullscreen mode Exit fullscreen mode

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 y IStringLocalizerFactory en ASP.NET Core. Al usar IStringLocalizer, 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/aspnetcore

Nota 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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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.

aspnetcore Article's
30 articles in total
Favicon
[ASP.NET Core] Try reading a word processing file by OpenXML 2
Favicon
Deploy Your Bot to Azure in 5 Minutes with Azure Developer CLI (azd)
Favicon
How to Implement Passkey in ASP.NET Core with Fido2-net-lib?
Favicon
Building Async APIs in ASP.NET Core - The Right Way
Favicon
How To Improve Performance Of My ASP.NET Core Web API In 18x Times Using HybridCache In .NET 9
Favicon
Using React for the client-side of an ASP.NET Core application
Favicon
.NET 9 Improvements for ASP.NET Core: Open API, Performance, and Tooling
Favicon
馃殌Build, Implement, and Test gRPC Services with .NET9
Favicon
[ASP.NET Core] Try reading a word processing file by OpenXML 1
Favicon
[Vite + React] Running an ASP.NET Core application behind a reverse proxy
Favicon
Implementing Idempotent REST APIs in ASP.NET Core
Favicon
鉁匒SP.NET Core API Gateway with Ocelot Part 4 (Rate Limiting)
Favicon
Problem Details for ASP.NET Core APIs
Favicon
[ASP.NET Core][EntityFramework Core] Update from .NET 6 to .NET 8
Favicon
HybridCache in ASP.NET Core - New Caching Library
Favicon
Link Many To Many entities with shadow join-table using Entity Framework Core
Favicon
馃殌 饾悂饾惃饾惃饾惉饾惌 饾悩饾惃饾惍饾惈 饾悁饾悘饾悎 饾悞饾悿饾悽饾惀饾惀饾惉 饾惏饾悽饾惌饾悺 饾悓饾惒 饾悗饾悳饾悶饾惀饾惃饾惌 饾悊饾悮饾惌饾悶饾惏饾悮饾惒 饾悇饾惉饾惉饾悶饾惂饾惌饾悽饾悮饾惀饾惉!
Favicon
Implementing HTTP Request and Response Encryption in ASP.NET Core with Custom Attributes
Favicon
Reducing Database Load Without Relying on MARS
Favicon
Task Cancellation Pattern
Favicon
Custom Role-Based Authorization with JWT in ASP.NET Core
Favicon
Flexibilidad y Escalabilidad: Usando Strategy y Factory Patterns
Favicon
Building a Custom Logging Provider in ASP.NET Core
Favicon
ASP.NET Core Middleware
Favicon
How to Use .NET Aspire (Part2)
Favicon
How To Test Integrations with APIs Using WireMock in .NET
Favicon
Connecting NestJS and ASP.NET Core with gRPC: A Step-by-Step Guide
Favicon
Implementing ASP.NET Identity for a Multi-Tenant Application: Best Practices
Favicon
Welcome to .NET 9 Preview
Favicon
How use a Blazor QuickGrid with GraphQL

Featured ones: