Hola estimados devs, casi todos hemos escuchado de los principios SOLID, sin embargo creo que muy pocos han tenido la oportunidad de ver cómo se puede aplicar en un escenario real, y es por eso que te traigo este artículo donde no sólo aplicaremos el principio y aprenderás qué es, sino también aprenderás a usarlo en un entorno realista, comencemos! 🐿️
Tengo dos artículos sobre SOLID, en este puedes aprender sus definiciones teóricas, y aquí algunos ejemplos didácticos.
Qué es Inversión de dependencias?
También conocido como DIP, es uno de los principios SOLID, que establece que tu código debe depender de abstracciones en vez de implementaciones concretas de una clase.
En palabras sencillas, lo que este principio busca es desacoplar tu código de los detalles de implementación. Esto es especialmente útil cuando trabajamos con librerías de terceros, ya que evita que tu aplicación esté atada directamente a esas dependencias.
Hasta aquí ya sabes la teoría, ahora vamos a la práctica, mediante un ejemplo realista.
El ejemplo que usaré para enseñarte DIP será de una aplicación que tiene que usar una librería externa o de terceros para el envío de emails como SendGrid de Twilio.
Código con acoplamiento alto: Usando librería externa
Imagina que quieres enviar correos electrónicos usando la librería SendGrid.
Sin aplicar el principio DIP tu código quedaría algo así:
using SendGrid;
using SendGrid.Helpers.Mail;
public class EmailService
{
private readonly SendGridClient _client;
public EmailService(string apiKey)
{
_client = new SendGridClient(apiKey);
}
public async Task SendEmailAsync(string toEmail, string subject, string content)
{
var from = new EmailAddress("noreply@example.com", "Example App");
var to = new EmailAddress(toEmail);
var msg = MailHelper.CreateSingleEmail(from, to, subject, content, content);
var response = await _client.SendEmailAsync(msg);
if (!response.IsSuccessStatusCode)
{
throw new Exception($"Error al enviar el correo: {response.StatusCode}");
}
}
}
Y qué problemas tiene usar este enfoque?
- Alta dependencia: tu servicio EmailService depende directamente de SendGridClient.
- Alto acoplamiento: esto está muy relacionado con el punto anterior.
- Difícil para los tests: Tus tests unitarios se complican porque necesitas llamar a un API de terceros y esto puede ser costoso, lento, más propenso a errores y además difícil de mockear.
- Difícil mantenimiento: Debido al acoplamiento y dependencia altos, el mantenimiento se complica.
- Baja escalabilidad: estás atado a SendGrid y va a ser algo más difícil cambiar de proveedor o de crecer.
Aplicando Inversión de Dependencias DIP
Ahora apliquemos las buenas prácticas de código, en este caso el principio DIP.
Cuando en la teoría te dije que el código debe depender de abstracciones me refiero a Interfaces.
Por eso vamos a crear una interfaz que defina las operaciones necesarias para enviar un email.
Después crearemos su implementación concreta osea una clase que implementa dicha interfaz y es allí donde irá SendGrid.
Creando la interfaz IEmailService:
public interface IEmailService
{
Task SendEmailAsync(string toEmail, string subject, string content);
}
Ahora crearemos la implementación concreta, es decir una clase que implemente la interfaz antes creada.
La clase se llamará SendGridService
using Microsoft.Extensions.Configuration;
using SendGrid;
using SendGrid.Helpers.Mail;
public class SendGridService : IEmailService
{
private readonly SendGridClient _client;
public SendGridService(IConfiguration configuration)
{
var apiKey = configuration["SendGrid:ApiKey"];
_client = new SendGridClient(apiKey);
}
public async Task SendEmailAsync(string toEmail, string subject, string content)
{
var from = new EmailAddress("noreply@example.com", "Example App");
var to = new EmailAddress(toEmail);
var msg = MailHelper.CreateSingleEmail(from, to, subject, content, content);
var response = await _client.SendEmailAsync(msg);
if (!response.IsSuccessStatusCode)
{
throw new Exception($"Error al enviar el correo: {response.StatusCode}");
}
}
}
Si estás trabajando con un API REST, entonces en tu appsettings.json deberás incluir el API KEY de SendGrid:
{
"SendGrid": {
"ApiKey": "Tu-Api-Key-De-SendGrid"
}
}
Y en tu controlador en lugar de inyectar SendGridService, inyectas la interfaz IEmailService
Aquí es donde tu código deja de depender de implementaciones concretas y pasa a depender de abstracciones. Aquí es donde se concluye la implementación de DIP y estás aplicando lo que dice el libro. Anótalo! Está clarito ahora verdad? 🎖️🫡
public class NotificationController
{
private readonly IEmailService _emailService;
public NotificationController(IEmailService emailService)
{
_emailService = emailService;
}
public async Task NotifyUser(string email)
{
await _emailService.SendEmailAsync(email, "Bienvenido", "Gracias por registrarte en la aplicación.");
}
}
En este artículo te hablo más de la inyección de dependencias, el cual es un tema bastante relacionado con este.
Finalmente registra los servicios inyectados en Program.cs quedando algo así:
using Microsoft.Extensions.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IEmailService, SendGridService>();
var app = builder.Build();
// más codigo...
app.Run();
De esta forma si eventualmente tu sistema crece y usarás más de un proveedor para enviar emails como Mailgun por ejemplo, lo que harás será crear una nueva implementación concreta de la interfaz IEmailService a la que podrías llamar MailgunService, entonces ahora tendrías dos implementaciones concretas y podrás usar cualquiera según tus necesidades y buena parte del código en tu aplicación no se modificaría.
Todo esto que hemos hecho es también una forma de aplicar otro principio SOLID: El Open-Closed (OCP), pero eso será motivo de otro artículo.
Lecciones aprendidas
Como notarás es mejor depender de abstracciones en lugar de implementaciones.
Cuando tengas que testear, será mucho más fácil crear un mock de IEmailService que de SendGrid que llamaba al API de un tercero.
El mantenimiento y la escalabilidad mejoran, si mañana cambias a otro proveedor como Mailgun o Amazon SES o el que quieras necesitarás una nueva implementación, lo demás queda como está.
Los principios en ingeniería de software te ayudan a tener una mejor calidad del código aunque al inicio te hagan escribir más líneas de código son tus aliados.
Ahora sí, practica y hazte un referente. Después sal y practica deporte y hazte excelente 🥳⚡.
Si esta entrada te ha gustado, y espero que así haya sido, entonces compártela genio 🎖️🫡
Créditos de imagen de portada: Foto de ThisisEngineering en Unsplash