Model View ViewModel

lunes, 26 de julio de 2010

En un mail reciente de MSDN apareció un artículo que toca un tema de diseño interesante.
Y es el patrón Model-ViewModel-Model, en el artículo se hace referencia a su implementación en WPF/Silverlight, sin embargo es un tema con el que seguramente muchos se topen al incursionar en Asp.Net Mvc, y en realidad es imposible de esquivar en cualquier implementación de MVC, ya sea en Asp.Net, WinForms, Ruby o Haskell.

ViewModels

Cuando trabajamos con un modelo MVC, especialmente si elegimos algún mecanismo de DataBinding entre M y V (y en un sentido amplio de la palabra consideremos los templates (como las vistas de Asp.Net Mvc) como un caso particular de DataBinding), resulta inmediato que cualquier aplicación que escape de lo elemental necesita algun tipo de transformación entre un Modelo y su Vista, ahi es donde llegan al rescate los ViewModels.

Para hacerlo menos abstracto, pongamos un ejemplo, imaginemos que nuestros amigos de Facebook® quieren construir la pagina de perfil de un usuario:


Perfil de Usuario
 supongamos también que utilizan MVC y construyen su "Model" de la siguiente forma:
Nota: el modelo real de Facebook® podría ser más complejo
La duda es cómo obtener los datos resaltados en amarillo en la captura de ejemplo

  • Antigüedad del mensaje de estado ("lunes pasado").
  • Ubicación ("Villa Devoto" en lugar de el GUID correspondiente)
  • Amigos en común (con el usuario logueado actualmente)
Para esto podemos "envolver" estas entidades del modelo con otras más emparentadas a la vista que queremos construir:

Estas clases extienden a las anteriores con campos calculados, listas filtradas, consolidaciones, u otro tipo de transformaciones sobre el modelo original.
Para ilustrar ésta sería una versión (muy naive) de la nueva propiedad "CommonFriends" en UserVM:

public IList<UserVM> CommonFriends        {            get            {                return this.Friends                    .Where(f => f.Friends                      .Any(ff => ff.UserID == User.CurrentUserID))                    .ToList();            }        }

Si usted ya estuvo agregando este tipo de campos calculados y transformaciones, y se sentía culpable por haber violado la prístina pureza de su modelo MVC


 siéntase liberado de la culpa!, esto ya tiene nombre y se llama ViewModel!

Separación Model-ViewModel-Model

Por supuesto en esto podemos imaginar un gradiente de creciente complejidad:
  • Propiedades agregadas directamente a las entidades de nuestro modelo
  • Algunas clases nuevas y propiedades agregadas a entidades que descienden de las de nuestro modelo
  • Una capa de ViewModels que se interpone y separa completamente View de Model.
Volviendo un poco al principio, el artículo de MSDN trata sobre la última opción, implementada como assemblies separados para View, Model y ViewModel, en el que no existen referencias entre View y Model.

Véase este gráfico explicativo que tomé prestado de un artículo que resume muy bien el tema

 

Rápidamente puede verse el problema burocrático en que nos pone este enfoque, ya que implica tener para casi todas las clases del modelo, una equivalente en ViewModel, con wrappers para todas las propiedades visibles. Imagínense la flagrante violación al principio DRY que supone crear páginas y páginas de código como este:

public string Fullname        {            get            {                return InnerEntity.Fullname;            }            set            {                InnerEntity.Fullname = value;            }        }

Tiene que haber algo mejor!


Dynamic Proxies

Como dice la máxima "El mejor código es aquel que no se escribe", y el artículo al que hice referencia propone generar estos ViewModels como proxies dinámicos, implementar esos "gets" y "sets" automáticamente con el nuevo tipo dynamic en .Net 4.0, un poco de Reflection y unos atributos muy elegantes para declarar dependencias entre propiedades (ej: Edad <-depende de-> FechaDeNacimiento).

Existen también librerías muy famosas para la creación de proxies dinámicos (que funcionan con .Net 2.0), como por ejemplo las que son usadas en NHibernate (para crear proxies que soporten lazy loading):

que hacen fácil agregarle a estos proxies interceptores en getters y setters (u otros métodos) que implementen notificación de cambios, logging, autorización y otras cosas por el estilo.

Y finalmente...KISS! es importante recordar que puede resultar más que suficiente con mantener las clases en el modelo accesibles a las vistas, y las clases "ViewModel" mezcladas con el "Model", o separadas simplemente por namespace.