Una mirada de cerca al desarrollo Outside-In

No tiene sentido tener un backend cuando no hay frontend. No tiene sentido tener una API cuando no hay nadie que la consuma. Tampoco hay motivo para tener una clase cuando no hay otra clase (o framework) que la utilice. No hay razón para tener un método cuando no hay nadie que lo llame.

Desarrollo Inside-Out

Muchos desarrolladores se centran en implementar el Domain Model antes de definir cómo va a ser utilizado por el mundo externo. La forma en que los usuarios (mediante la UI) u otros sistemas (mediante APIs, Mensajes) interactúan con el sistema se trata como una preocupación menor. Lo que aprendí en la universidad y durante la primera mitad de mi carrera, hace más de dos décadas, es a hacer análisis de negocio y centrarme primero en modelar el dominio. Con la idea de que el front-end cambia más a menudo que las reglas de negocio, optábamos por crear primero un sistema back-end totalmente desacoplado y robusto, dejando la interfaz de usuario (UI) para una fase posterior. Este enfoque es lo que yo llamo inside-out development.

“inside-out”

 

Ventajas

Repasemos algunas de las ventajas más importantes del desarrollo inside-out:

Validación temprana de los requisitos empresariales

Algunas cuestiones relacionadas con los requisitos empresariales sólo surgen cuando empezamos a trabajar en el código. Empezar a codificar a partir del modelo de dominio nos permite centrarnos directamente en las reglas de negocio, lo que nos da la oportunidad de descubrir incoherencias empresariales, aclarar los requisitos con más detalles y validar las suposiciones. Obtener un feedback rápido sobre áreas importantes del sistema es una estrategia de mitigación de riesgos.

Mitigación del riesgo

Trabajo de investigación y prevención que permite a los desarrolladores descubrir problemas que pueden cambiar significativamente su comprensión del alcance y el coste del proyecto antes de invertir demasiado en la solución. Identificar y mitigar los riesgos antes de construir demasiado código minimiza la repetición del trabajo y ayuda a mantener bajo el coste del proyecto.

Lo más valioso primero

Los desarrolladores deben centrarse primero en las tareas más valiosas, y existe la creencia generalizada de que el modelo de dominio es posiblemente la pieza de software más importante. Un argumento en contra es que el software no tiene valor empresarial si nadie lo utiliza. Desde esta perspectiva, el modelo de dominio sólo tiene valor cuando la funcionalidad completa se implementa y se utiliza en producción.

Interfaz de usuario (UI) volátil para el final

Las interfaces de usuario tienen fama de ser volátiles, mientras que las reglas de negocio tienden a cambiar mucho menos una vez definidas. Empezar primero con las reglas de negocio permite a los desarrolladores del backend avanzar mientras los del frontend iteran sobre el diseño de la UI.

Desventajas

Veamos algunas de las desventajas de este enfoque:

Esfuerzos desperdiciados

Sin la orientación de una necesidad externa bien definida y concreta (siendo una interfaz de usuario o API - mecanismo de entrega), podemos construir fácilmente cosas que no son necesarias. Y, cuando finalmente definimos el mecanismo de entrega e intentamos conectarlo, rápidamente nos damos cuenta de que el backend no satisface exactamente todas las necesidades del mecanismo de entrega. En este punto tenemos que escribir algún código de fontanería para conectar ambos lados, cambiar el backend o peor aún, comprometer la usabilidad definida por el mecanismo de entrega para que podamos reutilizar el código en el backend como está.

“delivery

 

Desarrollo especulativo

Cuando nos centramos en los aspectos internos de un sistema antes de definir cómo se va a utilizar, nuestra imaginación se dispara. Pensamos en todas las posibilidades y en lo que debería hacer nuestro código. Intentamos que nuestro modelo de dominio sea lo más robusto y genérico posible. Queremos explorar y a menudo nos salimos por la tangente sin tomar en cuenta comportamientos asociados al modelo de dominio. Cuanto más especulamos, más complejidad accidental aparece.

Retroalimentación tardía

A los usuarios o sistemas externos que consumen nuestras APIs no les importa nuestro backend. Sólo podemos obtener feedback de ellos si les proporcionamos algo a lo que puedan acceder o utilizar. Si el recorrido del usuario (a través de la interfaz de usuario) o las APIs no son adecuadas, nuestro backend no tiene valor. Si el front-end o las APIs se hacen después del modelo de dominio (backend), sólo obtendremos feedback cuando toda la función esté hecha y eso ya es demasiado tarde.

Desarrollo de granulado grueso

Una gran manera de entregar software de forma incremental es cortar nuestras funcionalidades en finas rodajas verticales. Esto se vuelve muy difícil cuando empezamos desde el modelo de dominio, ya que no estamos escribiendo código para satisfacer una necesidad externa específica. Los cortes verticales deben estar guiados por requisitos externos y no los tendremos si empezamos a construir primero el modelo de dominio.

Si no podemos encontrar un corte vertical fino adecuado, tenemos que esperar a que toda la función esté terminada para obtener comentarios y desplegarla. También es difícil predecir cuándo estará terminada una función si el mecanismo de entrega se considera una ocurrencia tardía.

Mecanismo de entrega urgente

Otro peligro de centrarse primero en el backend es que, cuando éste está terminado, los desarrolladores no suelen tener tiempo o no tienen muchas ganas de trabajar en el front-end. Acaban precipitándose en la implementación del front-end, lo que causa dos problemas principales: a) una mala experiencia de usuario, b) estándares de calidad inferiores en comparación con el back-end, lo que lleva a un mecanismo de entrega desordenado y difícil de mantener.

Colaboración deficiente

Los equipos de móvil, front-end y back-end no deben trabajar en paralelo mientras las APIs no estén definidas. Esto también se aplica a los equipos interfuncionales en los que los desarrolladores de móvil, front-end y back-end forman parte del mismo equipo. Los desarrolladores de backend que definen las APIs provocan muchas fricciones y repeticiones del trabajo cuando los desarrolladores de móvil y front-end intentan integrar su código.

Ventajas del desarrollo Inside-Out

Profundicemos en las ventajas del desarrollo Inside-Out y comprobemos si son ventajas reales y si algunas de ellas pueden aplicarse también al desarrollo Outside-In.

Gestión de riesgo

Una de las mayores ventajas del desarrollo Inside-Out consiste en abordar primero el núcleo del sistema y, con un poco de suerte, descubrir problemas desconocidos, lo que se denomina mitigación de riesgos. Pero, ¿es la mitigación de riesgos algo que sólo debemos hacer en el desarrollo Inside-Out? ¿Están todos los tipos de mitigación de riesgos y de trabajo exploratorio al mismo nivel?

A menudo no estamos seguros de cuál debería ser la solución para un requisito empresarial determinado, cómo deberían diseñarse los requisitos empresariales, qué herramientas deberíamos utilizar o cómo funcionan algunos frameworks. Por ello, es necesario realizar un trabajo exploratorio.

Veamos algunas categorías de trabajo exploratorio:

  • Aspecto técnico: Relacionado con marcos, bibliotecas, bases de datos o herramientas.
  • Arquitectura: Relativa a cómo se comunicarán los sistemas entre sí, cómo se distribuye la lógica entre los sistemas, las APIs, los mensajes entre sistemas, los entornos de producción, el registro, la supervisión, etc.
  • Macrodiseño: Relacionado con la organización del código a alto nivel: capas, hexagonal, contextos acotados, etc.
  • Microdiseño: Relacionado con las reglas del dominio empresarial, colaboraciones de clases, algoritmos, composiciones de funciones, etc.
“architecture,

 

Hay una diferencia entre tratar de desvelar incógnitas lo antes posible y entregar realmente una función. No deberían formar parte de la misma tarea. Siempre que no estemos seguros de lo que hay que hacer o de cómo deberían funcionar las cosas, deberíamos crear un spike (o cualquier otra actividad con límite de tiempo) para investigar el problema. Una vez que tengamos más información, podemos comprometernos a crear una función.

No se debe mezclar el trabajo exploratorio con el desarrollo de características.

Entre las cuatro categorías anteriores, me parece útil crear spikes separados para las investigaciones técnicas, de arquitectura y de macrodiseño, pero no tanto para el microdiseño, ya que es un área de mi código que emerge de forma natural a través de Outside-In TDD. El descubrimiento a nivel de microdiseño suele tener mucho menos impacto en el proyecto que el descubrimiento a niveles superiores. Eso significa que centrarse en el diseño inicial a nivel micro suele ser una pérdida de tiempo, pero no ocurre lo mismo con los demás niveles.

Cambios e impactos en la interfaz de usuario (UI)

Otro argumento para comenzar el desarrollo a partir del modelo Inside-Out es que la interfaz de usuario (UI) es volátil y su implementación debe realizarse una vez finalizado el modelo de dominio. Es cierto que la interfaz de usuario cambia con más frecuencia que las reglas de negocio, pero ¿realmente todos los cambios en la interfaz de usuario afectan al backend? Echemos un vistazo a los tipos más comunes de cambios en la interfaz de usuario y su respectivo impacto en el código backend:

Estética

Los cambios estéticos están relacionados con el aspecto de la interfaz de usuario, y normalmente los realizan diseñadores web, desarrolladores front-end y especialistas en experiencia de usuario, sin ningún impacto en el backend. Los cambios estéticos suelen ser los más habituales en la UI.

Comportamiento

Los cambios de comportamiento están relacionados con la forma en que los usuarios interactúan con el sistema. Afectan al recorrido del usuario, es decir, a los pasos que debe seguir para lograr el resultado deseado.

Los cambios de comportamiento pueden subdividirse en dos tipos: de navegación  y accionables.

Los cambios en la navegación están relacionados con la forma en que un usuario se desplaza por la aplicación o en trayectos de usuario determinados. En una aplicación web, los cambios de navegación en la interfaz de usuario pueden afectar o no al backend. Todo depende del tipo de tecnologías front-end que se utilicen y de cómo estén estructuradas las capas View y Controller (MVC). Si la capa Controller vive en el navegador (normalmente es el caso cuando se usan frameworks como AngularJS y React), no hay cambios en el backend. Si tenemos una aplicación web más tradicional donde el Controller vive en el backend, entonces el backend necesita cambiar cada vez que se añade, elimina o cambia un enlace (o botón). Para más detalles, por favor echa un vistazo a mi post anterior sobre MVC, Mecanismo de Entrega y Modelo de Dominio.

Los cambios accionables están relacionados con las acciones que el usuario puede realizar en el sistema. Son los que aportan valor de negocio al usuario y a la empresa. Algunos ejemplos serían la compra de productos, la lectura de reseñas de restaurantes, la reserva de un billete, por mencionar sólo algunos. La mayoría de las veces, los cambios en la interfaz de usuario son pequeños: añadir un botón, cambiar los parámetros de la URL, el método HTTP o el código de retorno. Los cambios accionables en la interfaz de usuario están directamente relacionados con los cambios en el backend, ya que normalmente afectan a la API y posiblemente al flujo desencadenado por ella.

Datos

Los cambios en los datos están relacionados con los datos que se muestran al usuario o que se le piden. Estos cambios implican una modificación de los datos que las API existentes reciben o devuelven. El impacto en el backend varía en función de los datos y de la complejidad para devolver o almacenar los datos requeridos.

¿Cómo afectan realmente los cambios en la IU al modelo de dominio?

Como ya hemos comentado, la forma en que decidamos estructurar nuestro mecanismo de entrega y nuestro modelo de dominio hará que nuestro backend sea más o menos susceptible a los cambios en la interfaz de usuario. En una aplicación web bien estructurada, los cambios en el backend pueden reducirse significativamente si el mecanismo de entrega es totalmente responsable de la navegación del sistema, mientras que el modelo de dominio sólo expone los flujos de negocio y gestiona los datos. Aunque esté diseñado para satisfacer las necesidades del usuario, el modelo de dominio no debe tener conocimiento de cómo se presentan esos flujos de negocio a los usuarios.

Las interacciones de los usuarios representan sus necesidades

Cada interacción del usuario con la interfaz representa una necesidad. El trabajo de los sistemas es satisfacer las necesidades del usuario cada vez que éste solicita algo. Si el usuario sólo quiere navegar de una parte a otra del sitio web, la lógica pertenece al mecanismo de entrega y no al modelo de dominio. Sin embargo, si el usuario quiere almacenar o recuperar información, la lógica pertenece al modelo de dominio.

Un caso de desarrollo Outside-In

Para ponernos de acuerdo sobre cómo debemos diseñar software, primero debemos estar de acuerdo con algunos principios básicos. Para el resto del artículo, voy a suponer que todos estamos de acuerdo con lo siguiente:

  • Sólo se aporta valor cuando el sistema está en producción y se utiliza satisfactoriamente.
  • Debemos esforzarnos por aportar valor tan pronto y tan a menudo como sea posible.
  • Debemos realizar la diligencia debida antes de construir algo.
  • Debemos obtener feedback lo antes y lo más a menudo posible.
  • Debemos trabajar siempre en pequeños y valiosos incrementos.
  • Debemos hacer las cosas lo más sencillas posible, pero sin simplificaciones.
  • Sólo debemos construir lo que realmente hay que construir.
  • No debemos trabajar en tareas técnicas si no tienen valor empresarial.
  • El trabajo de investigación debe programarse y realizarse como una tarea independiente.

Desarrollo Outside-In

El desarrollo Outside-In es un enfoque que se centra en construir código bien diseñado para satisfacer una necesidad externa, reduciendo la complejidad accidental al eliminar el trabajo especulativo.

“inside-out”

 

El código sólo debe escribirse para satisfacer una necesidad externa, ya sea de un usuario, de un sistema externo o de otro fragmento de código.

Nota: en el resto del artículo daré por sentado que los participantes trabajan en un equipo cross-funcional.

Desarrollo Incremental

Sólo debemos construir lo que realmente hace falta, nada más.

Sin límites, nuestra imaginación se desboca. Y también el exceso de ingeniería. A la hora de crear software, es importante limitarse (o centrarse) en lo mínimo necesario para satisfacer una necesidad empresarial.

En el desarrollo Outside-in, en lugar de trabajar primero en el modelo de dominio y luego en la persistencia y la UI, y sólo pasar a producción cuando toda la funcionalidad está hecha, preferimos trabajar en pequeños incrementos y dirigir el desarrollo desde la UI.

sliced feature

 

Diseñar mock-ups

Lo primero que hay que hacer es dibujar rápidamente unos cuantos mock-ups utilizando una herramienta como Balsamiq. Lo bueno de este tipo de aplicaciones es que podemos crear rápidamente mock-ups ejecutables, es decir, podemos hacer clic en enlaces, movernos a otras páginas, etc. Esta actividad se realiza normalmente en colaboración con los usuarios y/o Product Owners. Mientras dibujamos los mock-ups nos centramos más en el recorrido del usuario y en la información que tendremos en cada página, sin perder tiempo en el aspecto real de la página. A medida que añadimos nombres a los componentes visuales (etiquetas, formularios, botones, enlaces, etc.), hacemos que el lenguaje del dominio (o lenguaje ubicuo) surja de forma natural de esos mock-ups. De los mock-ups podemos extraer todos los nombres y verbos que conformarán el vocabulario utilizado en nuestro modelo de dominio.

Cuando los mock-ups se hacen en colaboración, todo el equipo construye una visión clara de lo que hay que hacer y se implica plenamente. Los mock-ups dan a todo el equipo una idea más precisa de cómo se utilizará el sistema y de cómo se conectan entre sí las distintas funciones. También es muy fácil y rápido probar ideas y jugar con distintos escenarios. Los mock-ups se convierten en un buen punto de partida para debatir las historias de usuario y empezar a dividir las funciones en pequeños entregables sin perder de vista el conjunto.

Uno de los peligros de este enfoque es ir demasiado lejos con las mock-ups, intentando que se parezcan a la interfaz de usuario real o crear mock-ups para demasiadas funciones. Tampoco deberíamos utilizar nunca herramientas de mock-ups para generar código. El objetivo de los mock-ups es acordar los recorridos de los usuarios y los datos con los que van a interactuar en cada uno de ellos.

Dividir una funcionalidad en pequeños incrementos

Al iniciar una nueva funcionalidad, lo primero que hay que hacer es crear su interfaz de usuario. Eso suena muy contra-intuitivo y por lo general hace que los desarrolladores de backend sacudan la cabeza en señal de negación. Pero, la construcción de la interfaz de usuario nos dará retroalimentación muy rápida de nuestros usuarios o Product Owner. Si no les gusta cómo se va a utilizar el sistema o el lenguaje empleado, podemos cambiarlo fácilmente sin ningún impacto en el backend - todavía no hay backend. Una vez que todos estemos de acuerdo sobre la UI, conoceremos el comportamiento exacto que tendrá que proporcionar el backend y tendremos un lenguaje de dominio más definido y estable. Si la interfaz de usuario tiene un comportamiento, toda la interfaz podrá probarse de forma independiente, simulando las llamadas al backend. Esto también ayudará a estabilizar las API que necesita la UI.

Una vez terminada la UI, estamos listos para dividir el backend en incrementos verticales. Cada incremento vertical debe partir de una interacción con el usuario y contener toda la lógica (persistencia, comunicación con sistemas externos, etc.) necesaria para satisfacer esa interacción. Una vez finalizado este incremento, se añadirá un nuevo comportamiento al sistema.

Este enfoque sistemático de trocear nuestras funciones en pequeños incrementos y empezar siempre el desarrollo desde fuera (interfaz de usuario) nos permite definir fácilmente los límites de las pruebas de aceptación y utilizarlos para guiar nuestro desarrollo.

Una vez que un incremento está terminado, probado y aceptado, podemos desplegarlo en producción o en cualquier otro entorno. Ahora estamos listos para iniciar un nuevo incremento.

Disño Outside-In de API 

Pero, ¿y si nuestra aplicación no tiene UI y sólo proporciona APIs?

Las APIs también son un mecanismo de entrega (desde la perspectiva del modelo de dominio) y se aplica el mismo principio de Outside-In. Las APIs siempre deben diseñarse para satisfacer las necesidades de sus clientes. Pero antes de profundizar, echemos un vistazo a los distintos contextos en los que se proporcionan APIs:

  • APIs privadas de empresas: Sólo utilizadas por sistemas controlados por el proveedor de API (internas).
  • APIs públicas: Utilizadas por sistemas no controlados por el proveedor de API (externas).
  • APIs híbridas: Utilizadas por sistemas internos y externos.

APIs privadas de empresas

El propósito principal de las APIs es satisfacer las necesidades de sus clientes, ya sean web, móviles u otros módulos (servicios). Los desarrolladores de front-end, móvil y back-end deben colaborar en el diseño de las API. Esta colaboración también debe producirse cuando un equipo necesite consumir una API que esté desarrollando otro equipo. El diseño debe basarse en las necesidades de los clientes de la API y no en lo que los desarrolladores de backend piensan que necesitarán los clientes.

He visto en muchas empresas cómo los desarrolladores de backend diseñaban APIs y las imponían a los demás. A menudo, los desarrolladores de aplicaciones móviles y front-end tenían que ajustar sus recorridos de usuario o añadir mucha complejidad innecesaria porque las APIs no estaban diseñadas para satisfacer sus necesidades. Esta forma de trabajar se ha vuelto tan habitual que muchos desarrolladores de móviles y front-end creen ahora que el diseño de una API no forma parte de su trabajo y esperan a que los desarrolladores back-end lo hagan.

El propósito principal de las APIs es satisfacer las necesidades de sus clientes, ya sean web, móviles u otros módulos (servicios).

Los proveedores de API (desarrolladores backend) deben hacer todo lo posible por reducir la carga de trabajo de sus clientes de APIs. Tener APIs creadas por desarrolladores backend sin una comprensión clara de las necesidades de sus clientes y sus respectivos flujos conduce a mecanismos de entrega complejos y a fricciones entre los equipos. Los problemas pueden reducirse significativamente con equipos cross-funcionales, donde los desarrolladores pueden trabajar en colaboración en ambos lados de la aplicación.

En caso de que sólo haya un cliente para una API, ésta debe ser específica para ese cliente. Las APIs se vuelven gradualmente más genéricas a medida que añadimos más clientes y diferentes necesidades externas.

APIs públicas

Es difícil predecir todas las formas posibles en que se utilizará una API pública. Sin embargo, incluso las APIs públicas deberían diseñarse teniendo en cuenta el flujo de trabajo externo, empezando por el flujo (o flujos) de trabajo más comunes que se espera que tengan los clientes al utilizar nuestras APIs. Dependiendo del tipo de APIs que se expongan, deberíamos empezar con una implementación de referencia y derivar el diseño de la API a partir de sus necesidades. Las implementaciones de referencia nos permiten validar si los flujos y la granularidad de las APIs tienen sentido.

Deberíamos reconsiderar nuestra decisión de exponer una API si no podemos imaginar un buen caso de uso para ella.

APIs híbridas

Las APIs híbridas pueden ser muy complicadas de mantener, ya que sufren presiones de distintos lados para evolucionar. Al igual que las APIs públicas y privadas, también deben diseñarse en función de las necesidades de sus clientes. Este escenario puede ser bastante complicado y queda fuera del alcance de este post.

Resumen

La mitigación de riesgos es una actividad extremadamente importante en cualquier proyecto de software. No debemos mezclar el trabajo exploratorio con el desarrollo de funcionalidades.

El código sólo debe escribirse para satisfacer una necesidad externa, ya sea de un usuario, de un sistema externo o de otro fragmento de código. Sólo debemos desarrollar lo que realmente necesita ser construido, nada más.

Guiar nuestro diseño y código en función de las necesidades externas nos ayuda a mantenernos centrados en la tarea que tenemos entre manos y evitar el desarrollo especulativo.

La navegación es responsabilidad del mecanismo de entrega y el backend no debe cambiar cuando cambia la navegación. El backend sólo debe proporcionar operaciones (acciones) al front-end.

Para lograr la entrega y el despliegue continuos, es importante trabajar en pequeños incrementos. Dividir las funciones en pequeñas secciones verticales es una buena forma de asegurarse de que cada incremento tiene valor para la empresa.

El desarrollo Outside-In es una forma estructurada e incremental de entregar software, centrada en ofrecer el comportamiento justo para satisfacer una necesidad externa.


Aclaraciones:

  1. Equipo cross-funcional no significa personas interfuncionales.

  2. En aras de la brevedad sólo me refiero al trabajo exploratorio técnico, dejando fuera cosas como tests de usabilidad, impacto en el negocio, etc.

  3. Hay que construir equipos multidisciplinares que tengan todas las habilidades necesarias para construir lo que se les pide. Por ejemplo: front-end, móvil, back-end, operaciones, etc.

  4. Algunas veces podemos querer hacer incrementos por lotes antes de desplegar a producción. Alternativamente, podemos desplegarlos continuamente y utilizar conmutadores de funciones.