Model-View-ViewModel

Qué organiza

Model-View-ViewModel (MVVM) separa representación concreta de un modelo de estado y comportamiento de presentación. ViewModel ofrece una forma de consumir información y acciones conveniente para la View; esta decide cómo representarlas. La colaboración suele apoyarse en binding, notificaciones y comandos.

En el Atlas es un Presentation Pattern dentro de Frontend and Client Architecture. No determina por sí solo distribución, deployment o persistencia. presentation-patterns compara su organización con model-view-controller y model-view-presenter, conservando sus variantes.

Origen y participantes

John Gossman presentó MVVM en el ecosistema Microsoft/WPF en 2005: una adaptación de la separación de presentación a una plataforma con binding y colaboración entre diseño de UI y desarrollo de aplicación. Su descripción distingue Model, View y un ViewModel que transforma información y expone comportamiento útil para la pantalla. Introducción de Gossman.

Josh Smith describe después cómo binding, comandos y templates de WPF permiten implementar esa separación y probar ViewModels independientemente. Esa realización histórica no convierte una biblioteca particular en requisito universal. WPF Apps With the Model-View-ViewModel Design Pattern, 2009.

View ↔ ViewModel → Model / Application

El esquema resume colaboración. No declara imports universales, sincronización obligatoriamente bidireccional ni dependencia del dominio hacia UI.

Model no equivale a database table ni a ORM entity. Puede incluir estado de dominio o aplicación, comportamiento, datos necesarios para presentación y abstracciones fuera de la interfaz. El alcance exacto depende de la formulación; ViewModel no necesita operar sobre entidades persistidas directamente.

ViewModel no es DTO, Domain Model ni View

ViewModel contiene estado de UI, valores transformados, decisiones derivadas y acciones. Un DTO transporta datos; tener propiedades llamadas Name y Error no demuestra por sí solo un modelo de comportamiento y sincronización de presentación. Un objeto pasivo puede formar parte de la solución sin constituir toda la responsabilidad.

Domain Model conserva significado y reglas del problema. ViewModel adapta ese conocimiento para una interacción: selección, borrador, mensaje, disponibilidad de acciones o indicación de trabajo pendiente. View representa controles y layout. Esas responsabilidades pueden colaborar sin volverse identidades equivalentes.

La relación con Presentation Model ayuda a entender esta separación: Fowler describe estado y comportamiento de presentación independientes de controles concretos, y reconoce la conexión terminológica con MVVM. No hace falta crear otro canonical para conservar ese antecedente. Presentation Model.

Un ejemplo propio: Domain conoce Order.Status y si la cancelación está permitida. ViewModel puede derivar CanCancel, StatusText e IsCancelButtonVisible. View decide si usa un botón, menú o enlace accesible. Ninguna de esas propiedades sustituye la regla autoritativa que impide cancelar después de shipment.

Cambiar el texto visible o el modo de indicar una operación pendiente no debería exigir cambiar esa regla. Tampoco conviene convertir ViewModel en una copia de todos los objetos del dominio: contiene lo pertinente a la interacción, con contratos que mantengan significado y autoridad.

Binding y estado observable

client-state-management permite precisar alcance y duración de ese estado: ViewModel puede gobernar una interacción sin contener toda la aplicación. Un borrador local y un Customer confirmado requieren relación explícita; conservar una copia en ViewModel no transfiere la autoridad comercial ni decide persistencia o vigencia de cache.

mobile-architecture añade restricciones de plataforma a esa colaboración: la vida de pantalla, proceso y workflow puede diferir. El patrón MVVM no garantiza conservar un borrador tras terminación; las garantías de un state holder concreto dependen de su plataforma y de los mecanismos de restauración o persistencia elegidos. Android ViewModel no debe confundirse con una definición universal del patrón.

Binding vincula valores de origen y destino, con reglas de actualización. MVVM no equivale a two-way binding:

MecanismoUso posible en nuestro formulario
One-wayMostrar ErrorMessage o IsSaving desde ViewModel.
Two-wayMantener el nombre editable entre campo y borrador.
One-timeInicializar información que no requiere actualizaciones posteriores durante esa vista.
Event/command bindingTraducir Guardar a una acción expuesta por ViewModel.

La documentación de WPF diferencia modos de binding, notificaciones y momento de propagación de entradas. Dirección y frecuencia son preguntas distintas: un binding bidireccional puede actualizar al escribir, perder foco o mediante una operación explícita. Data binding overview.

Event/command binding describe interacción, no otro modo de propagación de propiedades de WPF. Las APIs concretas varían según plataforma. One-time tampoco garantiza que todo framework gestione cambios de contexto igual.

Que un framework soporte binding no significa automáticamente MVVM. Debe existir un reparto coherente de estado y comportamiento. Enlazar controles directamente a una fila sin definir ownership de edición no proporciona esa separación por el nombre de la feature.

unidirectional-data-flow permite examinar el camino intención, transición y estado observable independientemente del nombre MVVM. El binding puede colaborar con ese flujo bajo reglas explícitas; two-way binding no es requisito universal de MVVM ni todo cambio desde View implica mutación arbitraria entre pares.

Las notificaciones mantienen View informada cuando cambia un valor observable. Si CanSave depende de DraftName e IsSaving, modificar esos valores requiere actualizar también su representación derivada. Un getter correcto puede producir una pantalla incorrecta cuando el mecanismo de sincronización nunca avisa que debe reevaluarlo.

Commands y operación de aplicación

Un comando expuesto por ViewModel conecta intención de usuario y comportamiento:

Save button → command binding → SaveCustomer action
→ application operation
→ observable result state → View binding

Microsoft documenta propiedades y comandos consumidos por View, notificaciones y acciones disponibles durante operaciones pendientes. ViewModel puede coordinar resultados sin conocer tipos concretos de controles. Model-View-ViewModel, guía conceptual de Microsoft.

No se exige una clase llamada Command ni identidad con el GoF Command Pattern. Puede haber relación conceptual, pero aquí importa el contrato de acción y estado. Un mecanismo de disponibilidad mejora interacción; no es autorización ni reemplaza validación de aplicación.

Si se necesita un diálogo, conviene describir qué decisión de presentación se solicita y dónde está su mecanismo concreto. Importar el tipo de ventana en cada ViewModel puede dificultar pruebas y cambio de UI. Tampoco toda operación visual necesita elevarse al modelo: foco, animaciones y layout pueden seguir siendo responsabilidades de View.

Ejemplo común: Edit Customer

El mismo caso propio de MVC y MVP carga cliente, edita nombre, valida, guarda y muestra éxito o error. Suponemos una operación autoritativa de aplicación y un borrador distinto del estado confirmado.

  1. ViewModel solicita cargar el cliente y expone DraftName, IsLoading y posibles errores.
  2. View consume ese estado. El campo editable puede sincronizar DraftName mediante two-way binding.
  3. ViewModel deriva CanSave a partir del borrador y estado de operación, y expone feedback de entrada.
  4. Guardar invoca SaveCustomer. ViewModel captura el nombre que enviará, marca IsSaving y delega en la aplicación.
  5. El resultado cambia estado observable: éxito actualiza la referencia confirmada; rechazo conserva el borrador y presenta los errores.

Aquí IsSaving controla feedback y coordinación local, no la garantía de persistencia. La regla sobre nombres permitidos vuelve a evaluarse al guardar. Una UI modificada o un segundo canal no deben poder saltarla.

Debe acordarse cuándo el valor del campo alcanza DraftName. Si solo se propaga al perder foco, Guardar necesita un contrato que no lea una edición anterior. También hay que definir qué ocurre si llega una respuesta después de seleccionar otro cliente: aplicar indiscriminadamente el resultado al ViewModel actual puede mostrar información incorrecta.

Estas preguntas son decisiones del ejemplo, no nuevas obligaciones universales del patrón. Identificar qué operación produjo el resultado y qué edición sigue activa ayuda a conservar coherencia. No hace falta inventar un framework adicional para analizar ownership y lifecycle.

MVVM frente a MVP

MVP suele expresar actualización mediante llamadas a un contrato de View; MVVM suele ofrecer estado y acciones que View consume a través de binding. No es una ley absoluta: Supervising Controller admite sincronización simple en View, y MVVM puede necesitar interacciones explícitas.

Comparar abstracción de View, llamadas, notificaciones, comandos, acoplamiento a plataforma y complejidad de sincronización. Eliminar setters explícitos no elimina trabajo: paths de binding incorrectos, conversiones, notificaciones omitidas y lifecycle siguen requiriendo revisión.

Podemos probar ViewModel sin controles: cargar, editar, rechazar y conservar el borrador; verificar CanSave y estado pendiente. También necesitamos pruebas de integración de bindings y gestos reales. Una propiedad correcta que nadie observa sigue sin actualizar la pantalla.

Composición, costos y nombres

MVVM puede organizar un desktop client cuyo núcleo usa ports-and-adapters, o presentación dentro de layered-architecture y clean-architecture. Los contratos interiores conservan políticas; adaptadores exteriores implementan necesidades tecnológicas. Flujo runtime hacia infraestructura no cambia por sí solo la dirección de dependencias del código.

El costo incluye estado duplicado, transformaciones, notificaciones y contratos de sincronización. Para una pantalla trivial, incorporar toda esa coordinación puede agregar más trabajo que claridad. Para una interacción rica puede permitir comprender y probar decisiones fuera de controles.

Carpetas Views/ViewModels/Models no prueban MVVM. Un tipo Web llamado ViewModel puede ser simplemente datos preparados para rendering, como documenta ASP.NET Core MVC. Revisar responsabilidades y mecanismos reales evita confundir el nombre con la separación.