c# - unit - Repositorio y patrones de unidad de trabajo-Cómo guardar cambios
repository and unit of work (5)
Estoy luchando para entender la relación entre los patrones del Repositorio y la Unidad de trabajo a pesar de que este tipo de pregunta se hace muchas veces. Esencialmente, aún no entiendo qué parte guardaría / confirmaría los cambios en los datos: ¿el repositorio o la unidad de trabajo?
Dado que todos los ejemplos que he visto se relacionan con el uso de estos en conjunto con una base de datos / O mapeador, hagamos un ejemplo más interesante: permite conservar los datos en el sistema de archivos en archivos de datos; De acuerdo con los patrones, debería poder hacer esto porque el lugar donde van los datos es irrelevante.
Así que para una entidad básica:
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
}
Me imagino que se usarían las siguientes interfaces:
public interface IAccountRepository
{
Account Get(int id);
void Add(Account account);
void Update(Account account);
void Remove(Account account);
}
public interface IUnitOfWork
{
void Save();
}
Y creo que en términos de uso se vería así:
IUnitOfWork unitOfWork = // Create concrete implementation here
IAccountRepository repository = // Create concrete implementation here
// Add a new account
Account account = new Account() { Name = "Test" };
repository.Add(account);
// Commit changes
unitOfWork.Save();
Teniendo en cuenta que todos los datos se conservarán en archivos, ¿a dónde va la lógica para agregar / actualizar / eliminar estos datos?
- ¿Va al repositorio a través de los métodos
Add()
,Update()
yRemove()
? Me parece lógico tener todo el código que lee / escribe archivos en un solo lugar, pero ¿cuál es el punto de la interfazIUnitOfWork
? - ¿Se
IUnitOfWork
en la implementación deIUnitOfWork
, que para este escenario también sería responsable del seguimiento del cambio de datos? Para mí, esto sugiere que el repositorio puede leer archivos mientras que la unidad de trabajo tiene que escribir archivos, pero que la lógica ahora está dividida en dos lugares.
Ehe, las cosas son complicadas. Imagine este escenario: un repositorio guarda algo en una base de datos, otro en el sistema de archivos y el tercero en la nube. ¿Cómo comprometes eso?
Como guía, la UoW debe cometer cosas, sin embargo, en el escenario anterior, Commit es solo una ilusión, ya que tiene 3 cosas muy diferentes para actualizar. Ingrese consistencia eventual, lo que significa que todas las cosas serán consistentes eventualmente (no en el mismo momento en que se usa con un RDBMS).
Ese UoW se llama Saga en una arquitectura dirigida por mensajes. El punto es que cada bit de saga se puede ejecutar en un momento diferente. Saga se completa solo cuando se actualizan los 3 repositorios.
No ve este enfoque tan a menudo, porque la mayoría del tiempo trabajará con un RDBMS, pero hoy en día NoSql es bastante común, por lo que un enfoque transaccional clásico es muy limitado.
Entonces, si está seguro de que SOLO trabaja con ONE rdbms, use una transacción con el UoW y pase la conexión asociada a cada repositorio. Al final, UoW llamará a commit.
Si sabe o espera que tenga que trabajar con más de un rdbms o un almacenamiento que no admita transacciones, intente familiarizarse con una arquitectura basada en mensajes y con el concepto de saga.
El repositorio puede funcionar sin Unidad de Trabajo, por lo que también puede tener el método Guardar.
public interface IRepository<T>
{
T Get(int id);
void Add(T entity);
void Update(T entity);
void Remove(T entity);
void Save();
}
Unidad de trabajo se utiliza cuando tiene varios repositorios (puede tener un contexto de datos diferente). Realiza un seguimiento de todos los cambios en una transacción hasta que llama al método Commit para persistir todos los cambios en la base de datos (archivo en este caso).
Por lo tanto, cuando llama a Agregar / Actualizar / Eliminar en el Repositorio, solo cambia el estado de la entidad, lo marca como Agregado, Eliminado o Sucio ... Cuando llama a Comprometer, la Unidad de trabajo recorrerá los repositorios y realizará la persistencia real :
Si los repositorios comparten el mismo contexto de datos, la Unidad de trabajo puede trabajar directamente con el contexto de datos para un mayor rendimiento (abrir y escribir archivos en este caso).
Si los repositorios tienen un contexto de datos diferente (bases de datos o archivos diferentes), la Unidad de Trabajo llamará al método Guardar de cada repositorio en un mismo TransactionScope.
En realidad soy bastante nuevo en esto, pero como nadie más sabio ha publicado:
El código que pasa con los CRUD en los repositorios como cabría esperar, pero cuando se llama a Account.Add (por ejemplo), todo lo que sucede es que se agrega un objeto Account a la lista de cosas que se agregarán más adelante (se realiza un seguimiento del cambio) .
Cuando se llama a unitOfWork.Save (), los repositorios pueden revisar su lista de lo que ha cambiado O la lista de UoW de lo que ha cambiado (dependiendo de cómo elija implementar el patrón) y actuar de manera apropiada, por lo que, en su caso, podría ser un campo List<Account> NewItemsToAdd
que ha estado siguiendo qué agregar según las llamadas a .Add (). Cuando UoW dice que está bien guardar, el repositorio puede conservar los nuevos elementos como archivos y, si tiene éxito, borrar la lista de nuevos elementos para agregar.
AFAIK el punto de la UoW es administrar el Guardar en múltiples repositorios (que combinados son la unidad lógica de trabajo que queremos comprometer).
Me gusta mucho tu pregunta. He usado Uow / Repository Pattern con Entity Framework y muestra cuánto EF realmente hace (cómo el contexto rastrea los cambios hasta que finalmente se llama a SaveChanges). Para implementar este patrón de diseño en su ejemplo, necesita escribir un poco de código para administrar los cambios.
Usar el sistema de archivos puede complicar las cosas bastante si quiere hacerlo usted mismo.
Solo escribe cuando el UoW esta comprometido.
Lo que debe hacer es dejar que los repositorios pongan en cola todas las operaciones de IO en UnitOfWork. Algo como:
public class UserFileRepository : IUserRepository
{
public UserFileRepository(IUnitOfWork unitOfWork)
{
_enquableUow = unitOfWork as IEnquableUnitOfWork;
if (_enquableUow == null) throw new NotSupportedException("This repository only works with IEnquableUnitOfWork implementations.");
}
public void Add(User user)
{
_uow.Append(() => AppendToFile(user));
}
public void Uppate(User user)
{
_uow.Append(() => ReplaceInFile(user));
}
}
Al hacerlo, puede obtener todos los cambios escritos en los archivos al mismo tiempo.
La razón por la que no necesita hacer eso con los repositorios de bases de datos es que el soporte de transacciones está integrado en la base de datos. Por lo tanto, puede decirle a la base de datos que inicie una transacción directamente y luego usarla para falsificar una Unidad de Trabajo.
Soporte de transacciones
Será complejo, ya que tendrá que poder revertir los cambios en los archivos y también evitar que diferentes subprocesos / transacciones accedan a los mismos archivos durante transacciones simultáneas.
normalmente, los repositorios manejan todas las lecturas, y la unidad de trabajo maneja todas las escrituras, pero seguro que puede manejar todas las lecturas y escrituras usando solo uno de estos dos (pero si solo usa el patrón de repositorio, será muy tedioso mantenerlo) 10 repositorios, peor aún, pueden resultar en lecturas y escrituras inconsistentes que se sobrescriben), la ventaja de la mezcla al usar ambos es la facilidad de seguimiento del cambio de estado y la facilidad de manejo de la concurrencia y los problemas constantes. para una mejor comprensión, puede consultar los enlaces: Patrón de repositorio con Entity Framework 4.1 y Relaciones entre padres e https://softwareengineering.stackexchange.com/questions/263502/unit-of-work-concurrency-how-is-it-handled y https://softwareengineering.stackexchange.com/questions/263502/unit-of-work-concurrency-how-is-it-handled