25 julio 2011

El patrón ViewModel en ASP.NET MVC 3

Hola a tod@s,

creo que una de las mejores formas de poner orden a tus ideas es escribiéndolas en un papel (bueno, hoy en día un blog también vale). En estos momentos estoy empezando a hacer mis primeros “pinitos” con ASP.NET MVC 3 y, como siempre que empiezas a trabajar con una tecnología “nueva”, te surgen dudas y te planteas cuestiones sobre su uso. En las siguientes entradas del blog voy a hablar sobre mis progresos con ASP.NET MVC3 plasmando aquí las soluciones que he dio adoptando.

En este primer post voy a enumerar las decisiones de diseño que he aplicado para definir los ViewModels en una aplicación ASP.NET MVC3, para ello plantearé las preguntas que me han surgido durante el desarrollo y expondré las conclusiones a las que he llegado después de analizar y/o probar diferentes alternativas.

A groso modo las alternativas que tenemos para definir el ViewModel son tres:

  • Usar las entidades de dominio directamente como nuestro ViewModel.
  • Usar una clase intermedia (un contenedor) que contenga las entidades del dominio que necesite la vista mas algunas propiedades de presentación.
  • Crear clases nuevas que solo contengan los campos necesarios para la vista y la lógica de presentación para representar nuestro ViewModel y mapear de/a estas clases las entidades del dominio.

¿Usar las entidades del dominio directamente como ViewModel?

La primera decisión a tomar es si vamos a usar como ViewModel directamente nuestras entidades del dominio o vamos a crear una abstracción nueva que represente el ViewModel.

El uso de las entidades del dominio directamente como ViewModel tiene un conjunto de desventajas importantes:

  • En aplicaciones multicapa muchas veces no es posible pasar directamente las entidades del dominio entre las diferentes capas de la aplicación.
  • Se pueden “degenerar” las entidades del dominio con lógica de presentación.
  • Si una vista necesita varias entidades del dominio se crearan “agregados” ficticios el la capa de dominio para resolver una problemática de presentación

La única ventaja que veo en el uso directo de entidades del dominio es que no hay que duplicar la lógica de validación y que requiere un esfuerzo de codificación menor.

El uso de una clase intermedia como contenedor solventa la degeneración de las entidades del dominio ya que permite agregar a la clase contenedor la lógica relacionada con la presentación, también solventa el problema de la creación de “agregados” ficticios en la capa del dominio y permite reaprovechar la lógica de validación pero sigue ligando las entidades del dominio a la capa de presentación, con esta aproximación seguimos teniendo que adecuar el formato de los datos del dominio a la capa de presentación y recibimos toda la información contenida en las entidades del dominio aunque no la necesitemos para la vista que queremos mostrar.

Usar un contenedor intermedio puede ser una buena solución para algunos casos en los que se quiera mostrar toda la información de las entidades de negocio y estas se pueden pasar entre las diferentes capas de la aplicación.

Bajo mi punto de vista el ViewModel de una vista es un concepto relacionado única i exclusivamente con la presentación y, como tal, solo debe lidiar con conceptos relacionados con la representación de los datos, nunca con conceptos de negocio. Si aceptamos la premisa anterior, es un error tratar de usar nuestras entidades del dominio como ViewModel ya que estaríamos introduciendo lógica de presentación en ellas (la entidades de negocio deberían ser agnósticas respecto a la presentación).

Mi opción preferida es crear clases nuevas para representar el ViewModel y hacer mapping entre estas clases y las entidades del dominio. Creo que esta solución mantiene el código mucho mas claro y con una separación de responsabilidades mayor. Como puntos en contra de esta aproximación tenemos la necesidad de duplicar la lógica de validación y un esfuerzo de codificación mayor.

¿Hay una relación 1:1 entre los ViewModel y las entidades del dominio? ¿Y con las vistas?

Claramente NO tiene porqué haber una relación 1:1 entre las entidades del dominio y los ViewModels, de hecho esta es una de las razones que motivan no utilizar las entidades del dominio como ViewModels. En cambio casi siempre hay una relación 1:1 entre el ViewModel y la vista que lo renderiza.

¿No estamos duplicando trabajo?

Si, como casi siempre, la flexibilidad que tenemos al usar ViewModels separados de las entidades del dominio es a costa de un mayor esfuerzo de codificación para crear los ViewModels específicos de cada vista y duplicar las validaciones de las entidades del dominio. Pensad, por el contrario, que un ViewModel bien definido debería reducir a 0 el código de formateo de datos de la vista.

¿Como construimos los ViewModels a partir de las entidades del dominio? ¿Y las entidades del dominio a partir del ViewModel?

La opción más simple y usada que he estado probando para construir el ViewModel a partir de las entidades del dominio es Automapper (se puede instalar vía paquete de NuGet en vuestro proyecto MVC3). En la red hay mucha información sobre el uso de Automapper y como integrarlo correctamente en aplicaciones ASP.NET MVC, algunas consideraciones que a tener en consideración son:

  • Se pude usar Automapper para mapear varias entidades del dominio a un ViewModel.
  • Normalmente dejo las convenciones por defecto para trasladar los atributos de las entidades del dominio a atributos del ViewModel.
  • Llamar a CreateMap solo una vez al inicializar la aplicación, no debemos hacerlo en el código de cada action.

Existen aproximaciones para no tener que realizar los mapeos en cada una de las actions, como la que explica Jimmy Bogart en esté post usando filtros para no tener que realizar siempre este mapeo, pero todavía no he podido analizar-lo ni probarlo :-(

La respuesta a la segunda pregunta puede parecer obvia, ¿no? Si usamos Automapper para construir el ViewModel a partir de las entidades del dominio pues también usaremos Automapper para hacer el paso inverso… pues NO, Automapper no está pensado para realizar un mapeo two-way entre modelo del dominio y ViewModel, solo nos ayudará en el mapeo Modelo –> ViewModel (tenéis una justificación en esta entrada del creador de Automapper). En mi caso estoy haciendo este mapeo de forma manual, creando un DTO y pasándolo a la capa de servicios ¿alguna otra idea? ¿cómo lo hacéis?

Resumen

Para la mayoría de proyectos complejos crearemos, para cada vista, su ViewModel asociado construirlo a partir de las entidades del dominio (o de DTO’s en caso de tener servicios distribuidos), y ubicaremos ahí toda la lógica de presentación que pueda requerir la vista para ser renderizada correctamente.

Saludos, Josep Maria

No hay comentarios: