lunes, 9 de julio de 2012

Unit of Work y los ORMs

El patrón Unit of Work está definido en el libro Patterns of Enterprise Application Architecture del conocido Martin Fowler.

Mucha gente recomienda implementar este patrón, veamos lo que dice Fowler:

unitOfWorkInterfaceA Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.

En otras palabras, el Unity of Work se ocupa de controlar los cambios que sufren las entidades, es decir todas las altas bajas y modificaciones que se deben producir en en la base de datos después de hacer una acción.

¿No recuerda el Unit of Work a la session de Hibernate o al DbContext de Enity Framework? Lo cierto es que los ORMs ya se encargan de hacer seguimiento de las entidades porque tienen que saber qué hacer cuando haya que persistir la información en la base de datos.

Si decides abstraerte del mecanismo de acceso a datos, no queda otro remedio de añadir una nueva capa en la infraestructura de tu aplicación para controlar los unit of work. Pero si te gusta tu ORM y no abstraes su uso, puedes utilizarlos para tener este control. Es más los responsables de (N)Hibernate no dudan en asociar el concepto de session con el de Unit of Work.

(N)Hibernate es un ORM más maduro, mucho más asentado y con mucha mayor base de usuarios que Entity Framework. Por lo que está mejor documentado cuando abrir una unidad de trabajo (o una session).

Se hablan de varias estrategias, pero hay dos principales:

Session per request u Open session in view: Se trata de crear el Unit of Work por cada evento que ocurra en el interface, por ejemplo, al hacer click a un botón. Por ejemplo en una aplicación web podríamos decir (a grandes rasgos) que el unit of work se crearía cuando comienza a servirse una petición web y se cierra cuando el servidor termina de servir la página.

Session per conversation o Conversation per busines transaction: Esta estrategia nos recomienda crear un Unity of Work cuando tenemos mostramos a un asistente al usuario. Es decir cuando una operación se alarga más de una pantalla.

Una mala práctica comentada apuntada por la comunidad de (N)Hibernate es el Session per operation. Al crear una session o un dbcontext se levanta una caché para controlar los objetos y además perdemos la posibilidad de envolver las operaciones en una transación. En .Net podemos gestionar la transacción mediante el sistema de transacciones distribuidas del framework (System.Transactions), pero es mucho menos eficiente consume muchos más recursos, cuando una base de datos transaccional podría manejar sin problemas este escenario.

Unit of Work con Entity Framework

El DbContext ya se encarga de hacer el seguimiento de las entidades. Al llamar al método SaveChanges se abre una transaccion y se aplican todos los cambios (inserts, updates y deletes) en la base de datos. A diferencia de NHibernate, EF no dispone un DbContextFactory y de un contexto para poder recuperar el unit of work adecuado en cada situación. Así que debemos crear a mano el DbContext y destruirlo.

Unit of Work con Hibernate y NHibernate

Aunque los chicos de (N)Hibernate no paran de repetir que la session funciona como Unit of Work, la verdad es que podemos romper con mucha facilidad la unidad de trabajo. Como ya hemos explicado en NoCompila, si delegamos en la base de datos la creación de los IDs, cuando asociemos un objeto nuevo a la session, Hibernate lanzará un insert para recuperar el ID de base de datos. Esto rompe con el Unit of Work, Fabio Maulo en este post.

Las recomendaciones de Fabio para implementar Unit of Work para (N)Hibernate son:

Porner el FlushMode a Never (en .NET) o Manual (Java) y no usar estrategias de generación de IDs que dependan de la base de datos (así evitamos el insert). De esta forma al cerrar el unit of work decidimos si hacemos Flush para mandar los cambios a la base de datos o no.

La mayoría de los usuarios de la comunidad de NHibernate recomiendan la estrategia HiLo para generar los IDs. Pero al utilizar esta estrategia, desde mi opinión, hace más difícil la gestión de la base de datos. Ya que al hacer esto cualquier acceso a nuestra base de datos fuera de la aplicación deberá tener en cuenta el algoritmo HILO y conocer como está configurado. Además cada vez que creemos la sessesion factory (por ejemplo cada vez que reiniciemos la aplicación) se vuelve a leer el valor Hi del HiLo.

Por otro lado, si usamos una generación de IDs, la capacidad de hacer operaciones por lotes en (N)Hibernate se ve mermada, ya que los inserts los hará de uno en uno cuando los necesite.

Fabio en su blog tiene un post explicando el comportamiento de los generadores de ID en NHibernate

Volviendo al Unit of Work, quizás lo que haría sería al abrir la session, iniciar un atransacción y al cerrar la unity of work decidimos si hacer rollback o no. ¿qué puede salir mal con esta política? Si utilizamos session per conversation y las operaciones son largas podemos tener bloqueadas por la transacción muchos registros que den problemas a otras transacciónes.

Como decía Frederick Brooks, no hay bala de plata. Pero eso es parte de nuestro. Evaluar los pros y contras y decidir (o diseñar) la mejor solución.

No hay comentarios:

Publicar un comentario