IDbSet, IObjectSet como repositorios o tus propias abstracciones

La verdad es que llevo algunos dias pensando en si escribir o no este post, y, por fin, me he decidido a hacerlo. Tal y como se puede sobreentender por el titulo del post, la idea es hablar sobre el uso de elementos como IDbSet ( o sus correspondientes en versiones anteriores ) en el caso de EF como repositorios o bien hacer alguna abstracción sobre este patrón. La idea, surge después de leer algún que otro post sobre este tema y también algunos comentarios que me llegan de twitter ( de personas que, por cierto, bien podían preguntar directamente, pero esto es otro tema ..). Lógicamente, como todo lo que digo y cuento en mi blog esta es una opinión personal y yo no tengo/creo tener, al contrario que otros, una patente de corso por la cual todo lo que haga o diga sea correcto.

Entremos en materia. Yo no voy a negar el hecho de que IDbSet es realmente un repositorio, de hecho, una implementación buenísima gracias a todo el trabajo que podemos hacer con árboles de expresiones, puesto que este elemento, cumple todos los criterios que marca este patrón tal y como lo describe Fowler aquí. Por lo tanto, dicho esto, tranquilamente podríamos hacer uso de los diferentes elementos expuestos por nuestras unidades de trabajo generadas por EF o hechas por nosotros si usamos Code First en aquellos puntos donde fuese necesario. Hacer esto, desde luego es una simplificación, puesto que cierto código que podriamos escribir ya lo tenemos de forma gratuíta y por lo tanto, la pregunta es porque hacer una abstracción sobre este concepto cuando en realidad ya lo tienes implementado ( lo mismo se podría aplicar a otros ORM). La respuesta a esta pregunta no es fácil, de entrada, yo diría que nunca, excepto cuando tengas algún escenario dónde esta abstracción te pueda suponer una serie nuevas de ventajas que sean interesannes para tu solución. A continuación, intentaré enumerar algunos elementos que podrían hacer inclinar la balanza en el hecho de usar o no una abstracción para representar un repositorio.

 

  • Las atribuciones de cada agregado: Cuando se definen repositorios, y por lo tanto, los agregados de un dominio, también se definen las atribuciones que cada uno de estos elementos va a tener. De tal forma, que, no todos los agregados tienen porque tener las atribuciones de los demás. Es decir, pueden existir agregados dónde no puedas realizar ciertas operaciones de mantenimiento de datos o no puedas realizar determinadas consultas. El uso de un elemento como IDbSet<T> no facilita esta tarea para nada, puesto que el consumidor no tendrá una forma de ver una “restricción” de las operaciones que este agregado puede tener, ni siquiera, de hecho, puedes impedir que una operación ( que no forma parte de sus atribuciones ) pueda ser realizada. Lógicamente, el hecho de poner una capa de abstracción sobre la definición de los repositorios de cada agregado es una forma de poder dar y dejar “en un contrato” cuales son las atribuciones que cada agregado tiene.
  • Los repositorios de los agredos no tienen porque ser solamente genéricos, es decir, estos repositorios pueden tener operaciones de consulta importantes dentro del dominio y querer que estas se muestren de forma explícita a los consumidores. De esta forma permitimos que el consumidor del repositorio se pueda desacoplar del criterio de consulta que una operacion del repositorio pueda tener, permitiendo un mejor mantenimiento en caso de cambio de este criterio de consulta. Por supuesto, este problema se podría minimizar haciendo uso del patron specifications pero también la abstracción podría dar un soporte mejor para esto.
  • Cuando uno define un agragado raiz y define las entidades que forman parte de este agregado en realidad también se está decidiendo como será la carga de ciertas asociaciones entre entidades. En ocasiones, queremos/necesitamos imponer que una raiz se carge siempre con ciertos elementos del agregado, por ejemplo, simplemente de caracter didáctico, siempre que carguemos un Order queremos incluir sus detalles. Hacer esto implica establecer de forma explítica un Include en todos los sitios de nuestros código dónde manejemos este agregado raiz. Puesto que el negocio cambia y el dominio por lo tanto también, esto, tiene ciertas implicaciones de mantenimiento, puesto que no es algo localizable de forma directa. El hecho de poner una abstracción nos permite tener un solo punto dónde estos criterios de carga se esetablezca y que por lo tanto, el mantenimiento sea mucho más sencillo y adaptable.
  • Incluir conceptos para la monitorización o la salud es mucho más sencillo cuando puedes escribir código en tus abstracciones, antes, durante o después de las operaciones del repositorio.
  • IDbSet es más, es decir, esta interfaz posee más conocimiento de lo que realmente nosotros necesitamos cuando hacemos uso de los repositorios. De hecho, tiene conocimiento por ejemplo sobre el tracking de los objetos, algo que no es su responsabilidad ( sino de la unidad de trabajo ) y por lo tanto nos enseña realmente más de lo que necesitamos sabes y por lo tantoes una vía abierta para introducir otros problemas.

Seguramente que me dejo algo por cada lado, aún así, yo creo que más o menos ya nos podemos dar una idea de lo tenemos con una o con otra opción. Lógicamente, debemos ser cada uno de nosotros los que elijamos intentando entender las dos opciones y cual va a ser para nuestro desarrollo la mejor, es nuestra REPONSABILIDAD  y por lo tanto hay que tomar la decisión valorando bien pros/contras.

 

P.D; Para el siguiente post intentaré hablar sobre otro tema del que se habla y es si poner un contrato base para nuestras unidades de trabajo o no…

Saludos

Unai

6 comentarios sobre “IDbSet, IObjectSet como repositorios o tus propias abstracciones”

  1. Muy buen post y bastante exhaustivo.

    Yo añadiría, como motivación para tener un repositorio «propio», la facilidad para el desarrollo de pruebas, tanto del repositorio como de sus consumidores, al contar con métodos más concretos y cercanos al negocio en lugar de genéricos. Por ejemplo, si implemento GetUpdatedProducts(), se extrae el criterio de esta consulta en el repositorio, con lo que lo utilizamos en toda la aplicación por igual, y es más sencillo suplantar para pruebas este método que si tuviéramos directamente un dc.Products.Where(p => p.IsUpdated) en el código de negocio.

    El argumento que más valoro es, como se ve, el de que los repositorios no deben ser genéricos (2º), sino que deben contar con una responsabilidad de mayor nivel, más cercana al negocio, que además permita construir un código de negocio más sencillo y legible. Es el principal argumento para desarrollar unos repositorios personalizados, en mi opinión.

    Gracias. Ya estoy salivando por el siguiente artículo.

  2. Fantástica explicación, sobre todo la afirmación de que se trata de la responsabilidad de cada uno. La responsabilidad puede recaer en analizar «a priori» cual va a ser el grado de complejidad del sistema. A poco que el dominio de negocio sea intrincado, el uso de repositorios y de otras abstracciones ayudan a la hora de mantener el sistema, y facilitan la tarea de probarlo, aunque compliquen su desarrollo inicialmente. Lo importante es preverlo con antelación, porque creo que el cambio entre una estrategia y otra puede ser «doloroso»…

  3. Hola Unai,

    excelente post como la mayoría que escribes! Me ha servido de referencia en un post muy básico sobre como aplicar el patrón respositorio cuando trabajas con un ORM (http://xampi.blogspot.com/2011/07/repository-y-unitofwork-con-ef-41.html)

    Una pregunta relacionada indirectamente con la temática del post: He estado viendo la implementación que se ha hecho de los patrones repositorio y unidad de trabajo en N-Layered SampleApp .NET 4.0 – EF4.1 y me ha parecido entender que se define la unidad de trabajo como un atributo de las abstracciones de los repositorios, ¿hay algún motivo para hacerlo así? ¿No seria mas natural que fuera la unidad de trabajo la que nos diera acceso a los repositorios, operar con ellos y luego hacer la operación que toque con la UoW? Parece que EF opera de esta manera…

    Perdona si la pregunta es un poco «off-topic» pero como el post me pareció muy interesante y creo que estas colaborando en la elaboración de la guía para aplicaciones DDD…

    Saludos! Josep Maria

  4. Josep Maria, perdona la tardanza en responder, estoy de vacaciones y no habia visto este comentario.
    Bueno, la verdad es que ya varias personas me an preguntado por lo mismo, para mi, los repositorios y la unidad de trabajo son elementos diferentes, y dentro de las responsabilidades de una unidad de trabajo no esta el hecho de instanciar un repositorio. De todas formas, esto es algo para hablar tranquilamente porque tiene varias vertientes posibles, habría que hablar por ejemplo de como manejar conversaciones con este escenario etc etc etc

    Unai

  5. Hola Unai, gracias por la respuesta,

    es un tema interesante ya que, si decides abstraer los repositorios y la unidad de trabajo de EF, en todas las opciones aparecen «pero’s»!

    A ver si saco tiempo y cuelgo un post en el blog con las diferentes alternativas que he analizado para que podamos debatir sobre los pros y contras de cada opción.

    Saludos! Josep Maria

Responder a unai Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *