Unidad de trabajo para operaciones CRUD no triviales para múltiples repositorios

StackOverflow https://stackoverflow.com/questions/1822179

  •  10-07-2019
  •  | 
  •  

Pregunta

He visto un patrón de unidad de trabajo implementado con algo como el siguiente código:

    private HashSet<object> _newEntities = new HashSet<object>();
    private HashSet<object> _updatedEntities = new HashSet<object>();
    private HashSet<object> _deletedEntities = new HashSet<object>();

y luego hay métodos para agregar entidades a cada uno de estos HashSets.

Al confirmar UnitOfWork crea alguna instancia de Mapper para cada entidad y llama a Insertar, Actualizar, Eliminar métodos de algún Mapper imaginario.

El problema para mí con este enfoque es: los nombres de los métodos Insertar, Actualizar, Eliminar están codificados, por lo que parece que UnitOfWork solo es capaz de realizar operaciones CRUD simples. Pero, ¿y si necesito el siguiente uso:

UnitOfWork ouw = new UnitOfWork();
uow.Start();

ARepository arep = new ARepository();
BRepository brep = new BRepository(); 

arep.DoSomeNonSimpleUpdateHere();
brep.DoSomeNonSimpleDeleteHere();

uow.Commit();

Ahora el enfoque de tres HashSet falla porque pude registrar entidades A y B solo para operaciones de Insertar, Actualizar, Eliminar, pero ahora necesito esas operaciones personalizadas.

Entonces parece que no puedo siempre apilar las operaciones del Repositorio y luego realizarlas todas con UnitOfWork.Commit();

¿Cómo resolver este problema? La primera idea es: podría almacenar direcciones de métodos

arep.DoSomeNonSimpleUpdateHere();
brep.DoSomeNonSimpleDeleteHere(); 

en la instancia de UoW y ejecútelos en uow.Commit () pero también tengo que almacenar todos los parámetros del método. Eso suena complicado.

La otra idea es hacer que los repositorios sean completamente conscientes de UoW: en DoSomeNonSimpleUpdateHere puedo detectar que hay una UoW ejecutándose y, por lo tanto, no realizo DoSomeNonSimpleUpdateHere pero guardo el parámetros de operación y estado 'pendiente' en alguna pila de la instancia del Repositorio (obviamente no puedo guardar todo en UoW porque UoW no debería depender de implementaciones concretas del Repositorio). Y luego registro el Repositorio involucrado en la instancia de UoW. Cuando UoW llama a Commit , abre una transacción y llama a algo como Flush () para cada repositorio pendiente. Ahora, cada método de repositorio necesita algunas cosas para la detección de UoW y el aplazamiento de la operación para más adelante Commit () .

Entonces, la pregunta corta es: ¿cuál es la forma más fácil de registrar todos los cambios pendientes en múltiples repositorios en UoW y luego Commit () todos en una sola transacción?

¿Fue útil?

Solución

Parece que incluso las actualizaciones complicadas se pueden dividir en una serie de modificaciones a uno o más objetos de dominio. Llamar a DoSomeNonSimpleUpdateHere () puede modificar varios DomainObjects diferentes, lo que desencadenaría las llamadas correspondientes a UnitOfWork.registerDirty (DomainObject) para cada objeto. En el código de ejemplo a continuación, he reemplazado la llamada a DoSomeNonSimpleUpdateHere con un código que elimina a los usuarios inactivos del sistema.

UnitOfWork uow = GetSession().GetUnitOfWork();
uow.Start();

UserRepository repository = new UserRespository();
UserList users = repository.GetAllUsers();

foreach (User user in users)
{
  if (!user.IsActive())
    users.Remove( user );
}

uow.Commit();

Si le preocupa tener que iterar sobre todos los usuarios, aquí hay un enfoque alternativo que utiliza un objeto Criteria para limitar el número de usuarios extraídos de la base de datos.

UnitOfWork uow = GetSession().GetUnitOfWork();
uow.Start();

Repository repository = new UserRespository();
Criteria inactiveUsersCriteria = new Criteria();
inactiveUsersCriteria.equal( User.ACTIVATED, 0 );
UserList inactiveUsers = repository.GetMatching( inactiveUsersCriteria );
inactiveUsers.RemoveAll();

uow.Commit();

Los métodos UserList.Remove y UserList.RemoveAll notificarán a UnitOfWork de cada Usuario eliminado. Cuando se llama UnitOfWork.Commit (), eliminará a cada usuario encontrado en sus _deletedEntities. Este enfoque le permite crear código arbitrariamente complejo sin tener que escribir consultas SQL para cada caso especial. El uso de actualizaciones por lotes será útil aquí, ya que UnitOfWork tendrá que ejecutar varias declaraciones de eliminación en lugar de solo una declaración para todos los usuarios inactivos.

Otros consejos

El hecho de que tenga este problema sugiere que no está utilizando el patrón Repository como tal, sino algo más como puertas de enlace de datos de múltiples tablas. Generalmente, un repositorio es para cargar y guardar una raíz agregada. Como tal, cuando guarda una entidad, su capa de persistencia guarda todos los cambios en el gráfico de objetos de la instancia de entidad raíz agregada.

Si, en su código, tiene aproximadamente un '' repositorio '' por una tabla (o entidad), probablemente esté utilizando una pasarela de datos de tabla o un objeto de transferencia de datos. En ese caso, probablemente necesite tener un medio para pasar una referencia a la transacción activa (o la Unidad de trabajo) en cada método Save ().

En el libro Evans DDD, recomienda dejar el control de transacciones al cliente de un repositorio, y estaría de acuerdo en que no es una buena práctica, aunque puede ser más difícil de evitar si realmente está utilizando un patrón de pasarela de datos de tabla.

Finalmente encontré este:

http://www.goeleven.com/Blog/82

El autor resuelve el problema usando tres Listas para actualizar / insertar / eliminar, pero no almacena entidades allí. En su lugar, se almacenan los delegados del repositorio y sus parámetros. Entonces, Commit el autor llama a cada delegado registrado. Con este enfoque, podría registrar incluso algunos métodos de repositorio complejos y así evitar usar un TableDataGateway separado.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top