Datos, pantallas o código: ¿por dónde empezar?
Cuando nos encontramos frente a un problema de desarrollo podemos atacarlo de tres modos diferentes:
- Datos primero (Data first)
- Pantallas primero (UI first)
- Código primero (Domain first)
Históricamente la mayoría de problemas de desarrollo se han atacado utilizando el modo “Datos primero”. Me atrevería a decir que, aún a día de hoy, esta es la manera más utilizada.
Sin embargo la tendencia está cambiando y el modo “código primero” va adquiriendo cada vez más adeptos.
En este post voy a explicar algunas ventajas y desventajas de las diferentes maneras en las que podemos afrontar problemas de desarrollo.
1. Datos primero (base de datos)
Este planteamiento es el más utilizado en la mayoría de desarrollo de software. En él se asume que la piedra angular de la aplicación va a ser una base de datos relacional.
Lo primero que se hace es diseñar una estructura de tablas que representa el modelo a resolver. Se crean las relaciones entre las tablas y claves principales con la intención de impedir inconsistencias entre los registros. Por ejemplo, que no se creen duplicados o que no se pueda crear un registro si previamente no hay otro tipo de registro ya creado. Se establecen longitudes máximas para los tipos string, etc.
Eventualmente las consultas a los datos se pueden complicar, por eso se utilizan las vistas SQL que permiten recuperar registros de diferentes tablas con la intención de crear accesos a datos simplificados.
En algunos casos se va más allá y se crean columnas calculadas, funciones y/o stored procedures con multitud de funcionalidades, incluidas las de crear o modificar registros ya existentes.
A veces se crean flujos de datos en cascada utilizando triggers. Por ejemplo, cuando se crea un registro de tipo A, crear automáticamente otro registro de tipo B. Esto está pasando a día de hoy.
De este modo gran cantidad de reglas de negocio quedan resueltas en la base de datos y la aplicación queda liberada de aplicar dichas reglas, o por lo menos algunas de ellas. Las aplicaciones que se crean a partir de este tipo de planteamiento suelen tener una capa de datos de la que dependen el resto de capas de la aplicación. Son una mera representación en código y en pantallas de la estructura de datos.
Una de las principales ventajas de esta aproximación es que se puede cambiar el comportamiento o las reglas de negocio sin la necesidad de cambiar el código, compilar y realizar un despliegue.
Lo mismo sucede con las integraciones y/o comunicaciones con aplicaciones de terceros, se pueden hacer directamente contra la base de datos.
Este tipo de prácticas suele ser habitual en productos del tipo CMS, CRM o ERP en las que el acceso al código no siempre es posible. Migraciones de datos y automatismos de negocio se crean directamente atacando a la base de datos.
La mayor desventaja de este tipo de planteamiento es el mantenimiento de la aplicación. Se crean numerosas duplicidades en el código, las reglas de negocio quedan ofuscadas y distribuidas entre tablas, funciones, stored procedures y triggers, por tanto, encontrar fallos y/o crear nuevas funcionalidades puede convertirse en un auténtico desafío. Además, el uso intensivo de triggers disminuye el rendimiento de la aplicación.
2. Pantallas primero (interfaz de usuario)
Bajo este planteamiento, primero se crean las pantallas de muestra sin una funcionalidad detrás y a partir de ahí se van desarrollando las diferentes capas de la aplicación.
La principal ventaja de esta aproximación es que es posible presentar prototipos al cliente antes incluso de iniciar el desarrollo. El objetivo es centrarse en lo que realmente importa a los usuarios.
Esta aproximación intenta evitar las típicas situaciones en que tras un largo periodo de duro trabajo se hace una presentación al cliente y entonces el cliente dice que eso no es lo que él había pedido. Si primero presentas una muestra de lo que va a hacer la aplicación, el cliente se pone en situación y empieza a entender lo que va a recibir.
Recuerda que para el cliente, la interfaz de usuario es la aplicación, lo que hay por debajo no le importa. Joel Spolsky lo explica perfectamente en su artículo “The iceberg secret, revelated”.
En él compara el software a un iceberg. La interfaz de usuario es la punta del iceberg, lo que se ve, mientras que las funcionalidades desarrolladas, representan la parte sumergida. Es mucho más grande lo que hay por debajo pero al estar oculto, es como si no existiera.
En este mismo artículo describe 5 corolarios que tengo muy presentes cuando muestro aplicaciones a clientes y/o usuarios. Los voy a referenciar tal cual porque son muy interesantes.
- Si le muestras a alguien que no es programador una pantalla que tiene una interfaz de usuario con el 90% de las cosas mal, pensarán que el programa está mal al 90%.
Aprendí esta lección como consultor cuando hice una demostración de un importante proyecto web para el equipo ejecutivo de un cliente. El proyecto tenía casi el 100% de código completado. Todavía estábamos esperando que el diseñador gráfico eligiera las fuentes y los colores, y dibujara las pestañas 3-D. Mientras tanto, solo usamos fuentes planas y en blanco y negro, había un montón de espacio desagradable en la pantalla, básicamente no se veía muy bien. Pero el 100% de la funcionalidad estaba allí y funcionaba perfectamente.
¿Qué pasó durante la demostración? Los clientes pasaron toda la reunión discutiendo sobre la apariencia gráfica de la pantalla. Ni siquiera estaban hablando de la interfaz de usuario, solo la apariencia gráfica. "Es que no se ve bien", se quejó su gerente de proyecto. Eso es todo en lo que podían pensar. No pudimos hacer que pensaran en la funcionalidad real. Obviamente, arreglar el diseño gráfico tomó aproximadamente un día. Era casi como si pensaran que habían contratado pintores.
- Si le muestras a alguien que no es programador una pantalla que tiene un diseño 100% bonito, pensará que el programa está casi listo.
(...)
El gran riesgo aquí es que si presentas la interfaz de usuario primero para poder mantener conversaciones con el cliente, todos pensarán que la aplicación ya está casi terminada. Entonces, cuando pasas todo un año más trabajando en las funcionalidades "ocultas", por así decirlo, nadie verá realmente lo que estás haciendo y parecerá que no avanzas.
- Una web con el diseño más fresco y pulido, aunque tenga pocas funcionalidades, obtendrá una valoración más alta que otra web altamente funcional con 3700 años de artículos y un fondo gris predeterminado.
- Cuando la política de la empresa exige que varios gerentes o clientes no técnicos verifiquen los avances de un proyecto, proporciónales varias versiones del diseño gráfico para elegir. Se pasarán el tiempo pensando si es más conveniente un tipo de letra u otro, y te dejarán centrarte en los aspectos técnicos y funcionales que importan.
- Cuando estás intentando vender una aplicación, lo único que importa es la captura de pantalla. Hazla bonita al 100%.
No pienses ni por un instante que podrás salirte con la tuya pidiéndole a alguien que imagine lo genial que va a ser la aplicación. No pienses que están entendiendo la funcionalidad, no lo hacen. Lo que quieren es ver píxeles bonitos.
Desde luego estos 5 corolarios dan que pensar.
Iniciar un desarrollo por las pantallas también tiene sus contras:
A día de hoy ya sabemos que los modelos que utilizan las pantallas no tienen por qué coincidir con los modelos abstractos de dominio que realmente resuelven el problema. En las pantallas, debemos centrarnos en la usabilidad. Quizá debemos añadir información extra o controles que faciliten la comprensión y manipulación de los datos pero que en realidad no forman parte de la resolución del problema. Por tanto, hay que ir con cuidado de no arrastrar estos conceptos a capas más internas de la aplicación porque no aportan nada.
Otro punto negativo puede ser el acoplamiento a la tecnología. Por ejemplo, si estamos creando una aplicación Windows o una aplicación Web, los controles disponibles serán diferentes y eso puede llevar a crear estructuras de datos diferentes según la tecnología utilizada. Podemos tender a confundir reglas de la interfaz de usuario con reglas de negocio. En consecuencia las capas de servicios y de modelo quedan acopladas a la manera en que trabaja la interfaz de usuario.
3. Código primero (de dominio)
La interfaz de usuario y la base de datos son capas externas que pueden existir o no, por tanto hay que empezar a trabajar con el código que realmente realiza la funcionalidad.
Iniciar los problemas de desarrollo bajo esta premisa ayuda a concentrarse en las funcionalidades importantes de la aplicación. El objetivo es desarrollar un proyecto/capa independiente de la tecnología y la base de datos. Es decir, se podría cambiar la base de datos y/o la interfaz de usuario y aun así el motor de nuestra aplicación seguiría siendo perfectamente válido.
Otra de las consecuencias de iniciar así los desarrollos es que se distingue con mayor facilidad la diferencia entre los modelos del dominio y los de la interfaz de usuario o base de datos.
¿Qué quiero decir con esto? Cada capa de la aplicación tiene sus propios modelos. Que coincidan entre ellos es casualidad, en la práctica son clases diferentes.
Por ejemplo, dentro del domino del contexto de un departamento de calidad podríamos tener un modelo que se llame 'producto'. Este 'producto' podría tener algunas propiedades como 'tipo de material' que sólo tuvieran sentido dentro de dicho departamento. Para los otros departamentos, por ejemplo, el departamento de marketing, el tipo de material no es relevante.
Y al contrario, dentro del dominio de un departamento de marketing, una propiedad como el 'coste' sí que es importante, por tanto el módulo de marketing puede tener otra clase también llamada 'producto' con una propiedad para representar el coste. El tipo de material para marketing no es significativo, así que su clase 'producto' no tiene dicha propiedad.
Como resultado tenemos en la capa de dominio dos clases 'producto' que según el contexto, marketing o calidad, se comportan de modo diferente.
Si nos desplazamos a la capa de presentación resulta que un producto lo podemos mostrar dentro de la fila de una tabla o en una pantalla de detalles. Según en qué pantalla representemos el 'producto' vamos a necesitar más o menos campos. La consecuencia es que en la capa de presentación tendríamos otras dos clases diferentes para representar un producto, por ejemplo: FilaProductoViewModel y DetallesProductoViewModel. Podríamos tener una representación del producto para cada una de las pantallas en que aparezca.
Y por último, la implementación de dicho producto en la base de datos también puede ser diferente. Se podría guardar en una única tabla con todos los campos del producto, incluyendo los campos que utilizan ambos departamentos, marketing y calidad, o se podrían tener dos tablas diferentes relacionadas por un Id. O incluso, se podrían tener en bases de datos diferentes, siguiendo una arquitectura de Microservicios.
Es importante distinguir entre los diferentes tipos de modelos porque eso nos permite diseñar aplicaciones desacopladas con una capa central, la de dominio, capaz de implementar las funcionalidades independientemente de sus capas externas. Y en teoría esto es bueno.
Otra de las ventajas de empezar a programar por el código de dominio es la facilidad con la que se pueden crear tests unitarios. Este proceso se desarrolla de modo natural, de hecho, la mayor parte de los tests unitarios deberían pertenecer a la capa de dominio porque es en ella donde se encuentra la lógica.
Como en las dos anteriores aproximaciones, esta también tiene sus desventajas. No siempre es fácil abstraer y desacoplar todas las reglas de negocio. Juan María lo explica muy bien en su artículo 'Tu modelo puede ignorar la persistencia. Tú no'.
... si quiero un vaso de leche, aunque solo necesite invocar IMilkProvider.GetMilk(), no es lo mismo si la implementación lo saca de mi nevera, que si tiene que ir a un centro comercial, o que si tiene que ordeñar la vaca. El tipo de errores que se pueden producir, el tiempo que tardará en ejecutarse… son factores que pueden invalidar por completo el algoritmo que usamos y que, supuestamente, es independiente de la implementación del interfaz. Pensar que por tener un montón de interfaces estamos a salvo de futuros cambios es demasiado optimista.
El sistema de ORM que utilizamos, por ejemplo Entity Framework, también puede invalidar alguna de las intenciones ideales que queremos implementar en nuestra capa de domino. A veces, intentar ser completamente purista nos llevaría a dejar de utilizar herramientas altamente productivas.
Respecto a la interfaz de usuario podríamos versionar la afirmación de Juan María como: 'Tu modelo puede ignorar la tecnología. Tú no'. J
Conocer la tecnología de la capa de presentación y utilizarla a tu favor seguramente es más productivo que emperrarse en conseguir un modelo de domino puro.
Opinión personal
En mi caso particular, durante muchos años he estado planteando el desarrollo de software desde una perspectiva Datos primeros, debido sobre todo a que las herramientas tecnológicas que tenía a mi disposición me encaminaban en esa dirección.
Con la aparición de ORM’s como Entity Framework o NHibernate esta tencencia ha ido cambiando y cada vez se está utilizando más la aproximación Código primero.
Como en todo cuando se trata de programación, hay que hacer un balance entre las ventajas y las desventajas y adoptar en cada caso particular el enfoque más conveniente.
Es posible combinar estas tres aproximaciones, ¿por qué no? En algunas funcionalidades podríamos empezar por las pantallas y así tener prototipos que mostrar al cliente y en otros casos, cuando la lógica de negocio juegue un papel importante, podríamos empezar por el código.
Si me preguntas a mí, mi preferido es Código primero, pero claro, yo soy programador y barro para casa J.
Referencias
- Tu modelo puede ignorar la tecnología. Tú no.
Juan María Hernández
-
Domain-centric vs data-centric approaches to software development.
Vladimir Khorikov -
UI-First Software Development.
Jeff Atwood -
The Iceberg Secret, Revealed.
Joel Spolsky -
Database centric architecture.
Wikipedia