Luego de buen tiempo sin escribir (casí 2 meses o más), he decidido regresar con fuerza y retomarlo con una entrada sobre patrones de diseño, especialmente con este patrón que lo he venido usando en un proyecto y me parece una muy buena propuesta junto a otros patrones adicionales.
¿Unit of Work?
En primera instancia UoW soluciona un problema sobre las conexiones a la base de datos y permite encapsular dichas consultas a la DB en una sola pudiendo así hacer varias tareas y al final realizar los cambios.
Por ejemplo, si necesitamos realizar una app para retirar dinero del cajero automático (ATM), los pasos serían los siguientes:
- Ingresar el monto a retirar
- Validar si tiene el saldo necesario
- Registrar la salida (log)
- Actualizar el nuevo saldo
La solución práctica sería meter todo el código en un solo método y problema resuelto. Pero mejor es tener cada acción por métodos separados de esta manera el código queda más limpio y fácil de mantener, y hacemos que sea reutilizable en otros componentes de nuestro proyecto.
public class BankOperation
{
public decimal decimal Verify(int userId)
{
using (var context = DbHelper.Open())
{
// Logic
return context.UserAmount.Single(x => x.UserId == userId).Amount;
}
}
public decimal InputOutput(int userId, decimal amount)
{
using (var context = DbHelper.Open())
{
// Logic
context.SaveChanges();
}
}
public decimal Update(int userId, decimal amount)
{
using (var context = DbHelper.Open())
{
// Logic
context.SaveChanges();
}
}
}
¿Cuál es el problema?
Cada método abre una instancia a la base de datos por lo cual podemos conllevar a problemas de performance. Más allá de esto, cada método gestiona su propia escritura de los cambios (problema real), es decir que al llamar a un método la acción para confirmar el INSERT/UPDATE/DELETE es ejecutado independientemente y esto no es muy bueno. ¿Supongamos que quisieramos tener todo en un bloque de transaction por si algo falla?, pues no, no vamos a poder hacerlo.
¿Cómo debería ser implementando UoW?
En pocas palabras UoW te dice, hey yo voy a gestionar la instancia a la BD y hacerme cargo de las escrituras, tu encargate de la lógica del negocio.
Creamos la clase de UnitOfWork. En la siguiente publicación haremos algo más robusto, por ahora es una introducción.
public class UnitOfWork : IUnitOfWork
{
private readonly DbContext _context;
public DbContext Open() {
DbContext = new DbContext();
return DbContext();
}
public void SaveChanges()
{
_context.SaveChanges();
}
}
Refactorizando nuestra clase BankOperation quedaría de esta manera
public class BankOperation : IDisposable
{
public BankOperation(DbContext _context)
{
_context = context;
}
public decimal Verify(int userId)
{
return _context.UserAmount.Single(x => x.UserId == userId).Amount;
}
public decimal InputOutput(int userId, decimal amount)
{
// Logic
}
public decimal Update(int userId, decimal amount)
{
// Logic
}
}
Si se dieron cuenta cada método ya no hace una instancia a la Db y se removieron los SaveChange(). Es decir, el UnitOfWork inyecta la instancia a la BD y el confirmar los cambios de escritura se encargará UoW, ya no la clase BankOperation.
Ejemplo en marcha
var uow = new UnitOfWork();
using(var context = uow.Open())
{
// Registramos la dependencia de la base de datos, ahora es solo una compartida por la memoria
var bankOperation = new BankOperation(context);
// Parámetros de prueba
var userId = 1;
var amount = 2000;
// Si tiene el saldo necesario a retirar
var currentAmount = bankOperation.Verify(userId);
if(currentAmount >= amount)
{
// Registra la entrada y salida
bankOperation.InputOutPut(userId);
// Actualiza el nuevo saldo
bankOperation.Update(userId, currentAmount - amount);
// Ejecuta cambios
uow.SaveChanges();
}
// Libera la conexión
uow.Dispose();
}
En la siguiente publicación sobre UnitOfWork vamos agregar Repository Pattern, Services Pattern para complementar el ejemplo con un CRUD y algo más robusto que nos pueda servir en nuestros proyectos. Asimismo, mencinaré las ventajas/desventajas porque no todo es perfecto en la vida.