Domain-Driven Design "in a Shot"

Como parte de mi trabajo en Zara.com una de las cuestiones sobre la que trabajé con mis equipos fue en introducir conceptos de Domain-Driven Design, acompañado del estilo de arquitectura de Ports & Adapters (o Clean/Hexagonal Architecture). Había algunos productos que estábamos arrancando a los que se les presuponía que iban a tener una complejidad de negocio que iba a crecer, además tenían bastante incertidumbre inicialmente y se esperaba que su evolución perdurase en el tiempo al menos un puñado de años.

Empecé a intentar redactar de forma muy condensada un documento con los conceptos relacionados con DDD, por tratar de aligerar nuestras conversaciones sin necesidad de leerse los típicos libros azul, rojo o verde; que además suelen resultar durillos o incluso confusos.

Y aunque finalmente terminamos trabajando en los equipos practicando DDD y Ports & Adapters, no fui capaz de terminar con este documento. El día a día no me permitió dedicarle el tiempo necesario, así que aprovechando ese borrador he ido dedicando ratos posteriormente para terminar de aterrizarlo y servir como complemento a Diseño incremental de software a partir de las interacciones.

Foto de unos machacados, chupito especialidad de un garito de Zaragoza

Introducción

Domain-Driven Design (en adelante DDD) o diseño dirigido por dominio son un conjunto de prácticas que tratan de facilitar el desarrollo de software con un dominio complejo.

DDD se divide en artefactos a nivel estrátegico y táctico. El estratégico podríamos decir que se enfoca más al largo plazo y a lo funcional/dominio: especificaciones, documentación, algunas cuestiones de arquitectura de alto nivel… Mientras que el táctico se enfoca en el diseño más cercano a la implementación, a través de varios patrones y buenas prácticas.

Hay que tener en cuenta que DDD a nivel táctico consta de bastantes tipos de artefactos y que no es necesario utilizarlos todos de golpe y siempre, pero sí creo que es interesante conocerlos para poder sacarles partido en momentos dados.

En mi experiencia DDD casa muy bien tanto con las ideas de arquitectura de Ports & Adapters (más conocida como Hexagonal o Clean) para separar dominio/infraestructura/mecanismo de entrega, como con la práctica de TDD junto a prácticas de Specification by Example/ATDD.

TDD y Specification by Example/ATDD son prácticas más que recomendables. Nos ayudan a tener foco y a facilitar que emerga el diseño poco a poco a través de los flujos autosimilares de Red, Green, Refactor y Specify, Develop, Deploy.

Está fuera del ámbito de este documento la explicación en detalle tanto de estas prácticas como de Ports & Adapters.

Strategic Design

Dentro de los artefactos de Strategic Design se hace énfasis en dividir un problema grande en diferentes subdominios, con contextos delimitados o Bounded Contexts para hacerlo más manejable. Evidentemente, es habitual que distintos subdominios se relacionen entre sí usando distintos patrones de integración.

En cada contexto se establece un Ubiquitous Language basado en su subdominio para evitar equívocos entre cualquier miembro del equipo o stakeholders que participan en su desarrollo, todas las personas involucradas deberían utilizar las mismas palabras para referirse a los mismos conceptos de negocio.

El significado del concepto Product o Purchase en una gran empresa de retail puede tener docenas según el contexto en el que se usa. Pero dentro de un Bounded Context un concepto debe ser canónico, debe tener un único nombre y ese nombre un único significado.

Así que debemos tratar de utilizar un nombrado uniforme en todos los artefactos (código de producción, documentación, tests…) e incluso no está de más tener un glosario de términos como referencia de ese lenguaje ubicuo.

Para facilitar el entender cómo se relacionan los diferentes subdominos podemos visibilizarlo a través de un Context Map. Los context maps nos ayudan a ver qué subdominios existen, cómo se relacionan entre ellos organizativamente y los patrones de integración que hay.

Para quien quiera profundizar en esto, hay diferentes libros, charlas o artículos que entran en mucho más detalle. Por ejemplo: Strategic Domain Driven Design with Context Mapping.

Tactical Design

Los artefactos de Tactical Design son un conjunto de patrones que nos facilitan implementar en código el modelo de dominio dentro de un Bounded Context. Estos patrones pueden tener diferentes implementaciones, estar más o menos desacopladas de frameworks y librerías, etc.

Los modelos

Al modelar dentro de un subdominio encontramos conceptos que no se deben separar nunca, deben formar parte de la misma transacción siempre, estos conceptos son los Aggregates. Estos a su vez están compuestos de una o varias Entities y posiblemente de algunos Value Objects. Siempre habrá una Entity raíz en el Aggregate por la que se deberá acceder al resto de entities y value objects.

Podemos resumir qué es una Entity como algo que tiene un identificador único que lo distingue, aunque cambien el resto de sus atributos, el identificador jamás cambia. Deberíamos evitar tener entidades anémicas (sólo con getters y setters) tratar de empujar la lógica de negocio en lo posible hacia estos artefactos, siempre que no haya dependencias con colaboradores que no sean otras entities o value objects del aggregate.

Los Value Objects en cambio no tienen identidad y deberían ser inmutables por diseño (crearlo con un new y sólo métodos que consultan estado). Nos sirve para modelar conceptos en clases de dominio evitando tener responsabilidades repartidas por usar siempre tipos primitivos. Cuando hablamos de tipos primitivos incluiríamos también clases tipo String, colecciones estándar, etc.

La aproximación más purista lleva a que los atributos de una entity se modelen como value objets, personalmente prefiero empezar con tipos primitivos y si acaso emergen necesidades ir refactorizando a clases.

Un ejemplo muy simplificado de todo esto podría ser un agregado Invoice, dentro del dominio de una herramienta de facturación como este:

class Invoice {
  InvoiceId id; //Value object para encapsular el tipo primitivo
  Collection<InvoiceLine> lines; //Una lista de entity InvoiceLine
  CustomerId customerId; //Value object encapsulando la referencia a una entidad de otro agregado
  ...
  void addLine(InvoiceLine line){ //Método que modifica el estado de la entidad
  ...
}
class InvoiceId {
  String value; //Valor del tipo primitivo
  ...
  Boolean equals(InvoiceId id){
  ...
}
class InvoiceLine {
  InvoiceLineId id; //Value object para encapsular el tipo primitivo
  String description; //Tipo primitivo
  Money amount; //Value object para encapsular cantidad y divisa
  ...
}
...

La persistencia

Por otro lado, lo normal es que queramos persistencia, para lo que tendremos un Repository por cada Aggregate. Se encargará de esconder los detalles de la tecnología de persistencia y los mapeos que fueran necesarios.

Conceptualmente este patrón debería exponer métodos muy similares a una colección y ocultarnos los detalles de implementación: guardar un elemento, borrarlo, métodos básicos de consulta… aunque también es habitual ver expuestos métodos para queries más complejas tipo findAllByStatus o getByEmail frente al uso de query objects.

Los servicios

Como creo que a otras muchas personas, al ir introduciéndome en DDD me costó ver la distinción entre servicios. El que compartan parte del nombre resulta algo confuso, también ver cómo se refieren a la idea de servicios en documentaciones sobre frameworks y muchos artículos. ¿Qué diferencias hay entre Application Services y Domain Services?

Los Application Services son los puntos de entrada a la aplicación, los que orquestan y exponen el modelo de dominio. Personalmente prefiero referirme a ello como Use Cases para facilitar el explicar y entender su cometido; también los he llamado o visto llamar como Actions o Commands.

Sin entrar en mucho detalle a la hora de implementarlo, ya que hay muchísimas aproximaciones de hacerlo, mi preferencia es una clase por Use Case con un sólo método público expuesto. Siguiendo el ejemplo de una herramienta de facturación podríamos tener unas clases RegisterInvoice, AddLineToInvoice… con un método con nombre tipo run o execute.

Para evitar efectos colaterales, evitaremos tener use cases que se llamen entre ellos. Si aparece lógica común y estamos seguros de que estamos duplicando conceptos, podemos crear un Domain Service para actuar como helper.

Los Domain Services vienen a ser clases que encapsulan reglas de negocio que no tienen cabida para moverlos dentro de entities ni value objects. Suelen emerger porque hay reglas de negocio que involucran dependencias a ports o hay que orquestar varios aggregates diferentes.

Intentaremos que esas clases sean pequeñas y tengan una sola responsabilidad.

Sin ser un concepto original de DDD no quiero dejar mencionar la idea de Infrastructure Services, vi usarlo en algunos sitios y lo incorporé a mi terminología. Son simple y llanamente fachadas o clientes de servicios externos que no forman parte de mi dominio, así que los suelo nombrar como Client estilo SolrClient o GiphyClient.

Los eventos

Un Domain Events es algo significativo a nivel de negocio que ha ocurrido en el dominio que modelamos como una clase, tipo: InvoiceRegistered, ProductPublished, UserConfirmed… que contendrán los detalles de lo que ha ocurrido en el dominio para que sea publicado.

Personalmente suelo utilizarlos sólo al ver que el producto que estoy construyendo, además de complejidad, va a tener muchos side-effects o pueda interesar a otros subdominios: alimentar analítica para un dashboard, actualizar contadores, enviar un email, provocar algún trigger en un sistema externo…

La publicación de los eventos la dejaríamos encapsulada en un publisher, dejando a los subscribers la responsabilidad de comprobar si les interesa o no el evento y qué hacer con ello. Esos artefactos a nivel de infraestructura podrían implementarse con múltiples tecnologías: redis pub/sub, AMQP, kafka… O, mi preferencia para empezar a introducirlo, simplemente un patrón observer manejando todo en memoria.


Quienes conozcan Domain-Driven Design verán que he dejado algunas cosas fuera de este artículo y no he entrado a mucho detalle por tratar de condensar, ya que era algo inicialmente pensado para ayudar a asimilar los conceptos para gente que se inicia y acompañarlo con código de aplicaciones reales.

Con el mismo objetivo, personalmente me parece muy interesante un ejercicio similar que hicieron en video (muuuy rápido) los amigos de CodelyTV: DDD en 20 minutos.

Para profundizar más hay multitud de recursos en forma de charlas, libros, cursos…

Relacionados

Sobre el rediseño

Hace un par de meses publiqué el rediseño de la home de mi web personal y de paso de este blog.

Versión 2013

El anterior diseño, de José Luis Lizano y maquetado por Guillermo Latorre, databa de inicios de 2013 y estaba enfocado a ser un portfolio de algunos de mis trabajos como desarrollador freelance hasta ese momento. Tenía el objetivo de dar a conocer el tipo de trabajos para el que se me podía contratar, objetivo que había perdido sentido hacía tiempo.

A su vez el blog lo maqueté yo basándome en el trabajo que hicieron ellos en la home, aún siendo un resultado medianamente digno, se evidenciaban mis limitaciones.

Pantallazo el diseño previo del blog

Originalmente, tanto la web como el blog estaban sobre Wordpress en un hosting compartido, así que tocaba ir haciendo actualizaciones y mantenimiento de vez en cuando… Normalmente tarde y mal. Así que a mediados de 2017 lo migré a Jekyll en Github Pages, integrando Disqus para los comentarios y así poder olvidarme de eso. En ese momento me tocó andar revisando la parte responsive para que no se viera el diseño totalmente roto accediendo desde móviles, algo que hasta entonces no le había dedicado atención.

Versión 2019

Tras mucho tiempo con ello en la cabeza, finalmente nos pusimos a darle una vuelta con Vanessa Rubio al rediseño. Sin tener objetivos en sí mismos, las ideas principales eran:

  • Cambio en el enfoque de comunicación, quería que la home fuera algo más parecido a una carta de presentación. Romper con el enfoque de portfolio para pasar a contar qué cosas hago, qué he hecho, mis intereses, etc.
  • Ganar en legibilidad, ya que el anterior diseño de la home estaba planteado para textos cortos y la adaptación que hice para el blog arrastraba por ello algunos problemas. Quería volver a darle protagonismo a este blog escribiendo más en él y dejando de publicar en Medium.
  • Tener un diseño pensado para consumir contenido desde dispositivos móviles, por razones evidentes.

Boceto inicial del diseño del blog tanto versión escritorio como móvil

Para ponerlo en común trabajamos sobre bocetos en papel iterando rápido hasta llegar a la imagen de este post, que usó Vanessa como base para diseñar ya la parte visual entrando a detalle junto a contenido que fui evolucionando.

Una vez diseñada la parte visual, empezó a trabajar en la maquetación responsive sobre un fork del repositorio de git. Incorporando los cambios directamente en el Jekyll existente, con el contenido real y sincronizando alguno de mis cambios en cuanto a copys. Además, aprovechando que Jekyll lo soporta de serie, en el camino migró los estilos de Less a Sass.

Para finalizar, estas son algunas explicaciones de decisiones sobre el rediseño de Vanessa:

La idea era rediseñar sin perder la identidad actual. Para eso mantuve los principales elementos identificativos, como el logo, los colores y la tipografía.

Como queríamos facilitar la lectura de textos largos, le di más peso al blanco (sustituyéndolo como color de fondo) y dejé el negro principalmente para las zonas de navegación y la intro de la home. Para hacerla también más cómoda, especialmente en dispositivos móviles, aumenté el tamaño de fuente y del interlineado; añadiendo además espacios entre apartados para facilitar la lectura en diagonal.

Quería que fuera un espacio limpio, sin elementos que pudieran distraer la lectura. Para eso eliminé la barra derecha del blog ampliando así el espacio del cuerpo del post y dejé bastante aire alrededor para relajar la vista y centrar la atención en el contenido.

Por último, también quería destacar los CTAs más importantes del blog (como el de enviar comentarios o navegar entre páginas) convirtiéndolos en botones y unificando al mismo tiempo esos mismos estilos con los de la home para mantener la coherencia entre elementos similares.

Personalmente estoy muy contento con el resultado :)

KPIs para equipos de desarrollo de software

Como parte de mi trabajo en el último año, he intentado empujar la cultura de mejora continua en los diferentes equipos con los que he ido trabajando. Tanto en cuestiones de herramientas y habilidades técnicas, como en las de comunicación y coordinación, con ciertas restricciones y dependencias que caen fuera de nuestro margen de influencia.

Partimos del supuesto de que cuanto mejores sean las prácticas del equipo, mejor capacidad de entrega tendrá. Eso se traduce en una mayor adaptabilidad a los cambios, una mejor mantenibilidad del software con el paso del tiempo y posiblemente una mayor motivación del equipo con su trabajo.

Además de las típicas sensaciones subjetivas de efecto de mejora, teníamos que pensar en indicadores que nos permitieran ir observándolo realmente. En algún momento, también se empezaría a querer tener visibilidad desde fuera de los equipos, así que tocaba darle una vuelta y ordenar ideas.

Estuve preparando una presentación para explicar internamente cómo estábamos trabajando, hacia dónde creía que debíamos ir a través de una mejora continua, los distintos KPIs que podríamos observar y las necesidades que se cubrirían con una buena capacidad de entrega.

Diapositiva de una presentación con la visión de lo que, como equipo deberíamos cubrir: adaptabilidad respecto a negocio, entregar pronto, evitar retrabajo, una buena UX y evitar bugs en lo posible

“Dime cómo me mides y te diré cómo me comporto”

Desconozco el origen, pero con estas cosas siempre me acuerdo de este dicho. Hay que andar con ojo con qué indicadores (que no objetivos) vamos a medir. Además, tendremos que usar varios para compensar el fomento de comportamientos extraños que falsean esas métricas.

Con el uso normal de las herramientas, sólo acordando algunas convenciones, se puede facilitar la explotación de datos posterior para extraer métricas. Los orígenes de datos para los indicadores son:

  • El código
  • El repositorio y servidor de automatización
  • Las herramientas de gestión
  • El propio producto

Indicadores del código

Las herramientas de análisis estático de código nos dan números sobre deuda técnica que existe. No debemos perder de vista estos indicadores y dedicar tiempo en analizar y hacer limpieza específica de vez en cuando. Unas veces se resuelven con soluciones simples y otras esconden problemas de diseño que no resultan tan evidentes.

La cobertura de test es uno de los indicadores más habituales, lo más interesante en este caso es ver qué NO está cubierto. Y como se suele comentar, hay que andar con cuidado porque es un indicador fácil de falsear si se busca como objetivo.

En un momento dado, además, se podría utilizar mutation testing (comprobar que se rompe algún test al modificar código de producción) para tener un indicador de la calidad de los tests unitarios.

Indicadores del repositorio y servidor de automatización

Hay bastantes indicadores interesantes que se pueden sacar de los repositorios y servidores de automatización, aunque en su mayoría son dependientes de las convenciones de uso.

Sin embargo, un indicador siempre válido y que pienso que debería observarse es la frecuencia de integración.

La integración continua es una práctica (que no herramienta) tan popular como malinterpretada, ya que hasta que no se une el trabajo que ha hecho o está haciendo una persona con la rama principal de desarrollo y se completa una build, no la estamos realizando.

Hasta que no ha terminado correctamente la construcción de un artefacto de software, no sabemos si todo está correcto. A mayor frecuencia, feedback más temprano y menor incertidumbre.

En caso de usar ramas, también es interesante ver la duración de vida de las ramas. A más tiempo, mayor riesgo de conflictos u otros problemas al integrar.

Y si se utilizan pull/merge requests, también hay un puñado de indicadores que en un momento dado puedan sacarnos olores relacionados con la capacidad de entrega: cantidad de comentarios, tiempo que quedan abiertas, cantidad de rechazos…

Indicadores de las herramientas de gestión

Las herramientas de gestión, además de servir como radiador de información para saber la situación actual de la construcción del producto y ayudar a coordinar el trabajo, son una buena fuente de información de indicadores del proceso de trabajo.

Para observar la capacidad de todo el equipo en conjunto de hacer vertical slicing es muy útil conocer el tiempo de ciclo. A menor tiempo de ciclo, mayor es nuestra capacidad de entrega. Es el tiempo que se tarda desde que se empieza a trabajar en algo que aporte valor (por ejemplo, una historia de usuario) hasta que pasa a estar hecho.

Es habitual que en algún punto de las herramientas de gestión se pueden observar los distintos despliegues que se han realizado, donde podamos obtener la frecuencia de despliegue. Evidentemente a mayor frecuencia, mejor.

Aunque nuestro tiempo de ciclo fuera corto y la frecuencia de despliegue alta, sería posible que nuestro producto fuera frágil debido a bugs. Por eso el indicador de bugs detectados y resueltos por versión/despliegue es otra métrica a tener siempre en cuenta.

Dependiendo del momento y escenario en el que se encuentre un producto, también me parece muy interesante el indicador del lead time, el tiempo que pasa desde que se pide algo nuevo hasta que está desplegado en producción. Que vendría a ser consecuencia de los 3 anteriores indicadores y del tamaño de la pila de producto.

Como supongo que haya quien pueda echarlo de menos, omito intencionadamente los indicadores al respecto de las estimaciones, tipo story points por iteración. En mi opinión, tienen un componente muy subjetivo y variable para ser utilizado como indicador de cambio en la capacidad de entrega de un equipo.

Indicadores del propio producto

Además de otro tipo de instrumentación mucho más minuciosa que necesitan los miembros del equipo de gestión de producto o diseño, el equipo de desarrollo debería poder observar el número y porcentaje de uso por funcionalidad, que al final define el éxito o no del trabajo de todos.

Lo más interesante de este indicador es que combinado con otras métricas puede ayudar a tomar decisiones sobre la evolución del producto. Como por ejemplo, este escenario:

  1. Detectamos problemas muy graves de rendimiento en una parte del producto.
  2. Observamos que el porcentaje de uso de la funcionalidad afectada por esos problemas es residual.
  3. Decidimos no resolverlo de momento, pero lo reflejamos en la pila del producto como algo poco prioritario.
  4. Configuramos una alerta para detectar cierto aumento en el porcentaje de uso de esa funcionalidad.

Otro indicador importante es el crash rate del producto, cuántas veces se detecta un fallo o error por cantidad de uso, que nos permite saber lo estable que es un producto. Y mezclado con el indicador de números de uso por funcionalidad, nos permite detectar los puntos problemáticos.

Dependiendo del contexto del producto y del negocio, posiblemente podamos sacar también otros indicadores relevantes de operaciones y soporte que sean consecuencia del uso del producto.

Medir sin perder el foco

Estos son muchos KPIs distintos. Es interesante observarlos porque nos pueden servir para detectar olores sobre problemas y oportunidades de mejora, pero son demasiados para tratar de mejorar todo a la vez.

Para evitar diluirnos y no terminar mejorando en nada, deberíamos elegir y enfocarnos en un par de esos indicadores cada vez, dependiendo de la situación de cada equipo.

Y aunque sea tentador hacerlo, evitaría usar alegremente estos indicadores para marcar objetivos, así como para evaluar a equipos distintos.

No olvidemos que el propósito de medir estos indicadores es observar la evolución en el tiempo de un equipo trabajando en un producto y contexto determinado.

Diseño incremental de software a partir de las interacciones (parte 1)

Este post viene a cuento de que me quedé sin hacer la charla de la Bilbostack 2019 por andar pachucho durante varias semanas, teniendo que cancelar el compromiso al no haberme recuperado a los pocos días del evento. Me quedó el contenido a medio preparar y tenía la espinita clavada de compartir mis ideas sobre el tema.

Dibujo representando la autosimilaridad de Lean Startup, ATDD y TDD

¿Qué narices es eso del diseño incremental?

Ahora que “agile” (nótese el entrecomillado ;)) es mainstream, parece que se va asumiendo en muchos sitios que hacer software de forma iterativa ayuda a hacer mejor software. Cada cierto tiempo hay entregas que permiten validar si se va por un camino correcto o no, comprobar malos entendidos, ver problemas que han surgido, adaptabilidad a cambios…

Lo que bajo mi percepción no es tan mainstream es la parte de incremental, cuesta más hacer vertical slicing. En mi opinión esto suele ocurrir por falta de habilidades o conocimiento de prácticas relacionadas con Extreme Programming, como son test first o el propio diseño incremental.

Aunque siempre hay que hacer un esfuerzo inicial para poder sentar algunas bases respecto a la visión del producto, qué problemas o necesidades se quiere resolver, la arquitectura de alto nivel, etc.; muchas veces se peca de querer bajarlo demasiado en detalle desde el inicio, pretendiendo tener una foto de cómo va a ser la solución y adivinar el futuro.

No olvidemos que, cuando desarrollamos un software, hoy sabemos menos que mañana. En cada iteración que pasa, más aprendemos sobre el negocio, conocemos mejor las herramientas y la tecnología, intentamos mejorar como equipo, etc.

Así que una cosa que debemos tener clara es que, si queremos que nuestro software dure, tenemos que enfocarnos en diseñar software que acepte cambios. Aceptar la incertidumbre frente a tratar de adivinar problemas futuros.

¿Y lo de diseñar a partir de las interacciones?

Pues básicamente enfocar el diseño de software desde cómo se va a utilizar, ya sean personas que interactúan a través de una UI u otros sistemas que lo hacen usando algún tipo de API.

Seguramente resulta familiar el término API first, que es una aproximación similar. Empezar definiendo cómo se expone el software partir de un contrato e ir haciendo Outside-In hacia la implementación y modelado más interno.

En el caso del diseño a partir de las interacciones nos abstraemos de los detalles de un API. Anteponemos la definición de la interacción a la del API ya que esta no es nada más que un detalle técnico de cómo se expone la interacción al mundo.

Y cuidado: No podemos caer en la trampa de definir pobremente esas interacciones, sin descubrir el negocio que hay debajo y acabar pensando que sólo son CRUDs. Nunca es un CRUD: reglas de negocio, efectos colaterales, integraciones con terceros…

Descubrimiento de producto

Antes de identificar las interacciones, un trabajo importante es lo que se suele llamar descubrimiento de producto. Algunas cosas que necesitamos saber: objetivos de negocio del producto, quiénes son los usuarios y sus motivaciones, dependencias técnicas u organizativas, posibles restricciones, identificar a los stakeholders…

Es un tiempo dedicado a analizar y entender los problemas para poder enfocar mejor la solución, ya que no hay nada peor que descubrir que hemos contruido algo que nadie quiere.

Hay multitud de actividades que se pueden usar dependiendo del contexto (producto, equipo, negocio…): business model canvas, value proposition canvas, entrevistas, user personas, mapa de stakeholders, mapa de empatía, benchmarking de producto, customer journey map… o usar un paquete de este tipo de actividades como design sprint, agile inception deck o lean inception.

Estas actividades, muy estilo design thinking, se han quedado relegadas habitualmente a la gente de UX, negocio o gestión de producto, ya que a los técnicos típicamente no nos ha preocupado demasiado o no se nos ha incluido.

Participar en estas actividades nos ayuda a entender infinitamente mejor las necesidades durante la evolución del desarrollo del producto, además de facilitarnos el planteamiento inicial de la arquitectura de software a alto nivel. Y es que hacer diseño incremental no quiere decir que no hagamos un trabajo previo.

Formalización del backlog

Para poder organizarnos y priorizar debidamente necesitamos tener disponible algún tipo de backlog de producto, de modo que lo oportuno es hacer un volcado de product backlog items (sin preocuparnos demasiado del tamaño) como complemento a las diferentes actividades de descubrimiento que se hayan podido realizar.

A mi personalmente me gusta el terminar visibilizándolo en un formato estilo user story map. Aunque siempre he omitido la parte de journey, simplemente me gusta verlo organizado en dos dimensiones: funcionalidad/tema y prioridad agrupada en swimlanes.

Me parece muy interesante organizar las swimlanes como must, could y nice to have, al menos como organización incial o de cara a implementar un MVP. Otras veces, teniendo razonablemente claro el alcance final, las he utilizado directamente para definir releases o entregas parciales tentativas.

Refinamiento de historias de usuario

Ya con la prioridad, sabemos por donde hay que ponernos a aterrizar esos product backlog items y formalizarlo en una o varias historias de usuario. Recordemos que una historia de usuario no es sólo completar una plantilla, debería ser siempre un artefacto que sirve como recordatorio de (o excusa para) una conversación sobre ella y que además debería tener unos criterios de aceptación específicos.

Además de en las dailies o conversaciones más informales entre los miembros del equipo, habitualmente estas conversaciones deberían ocurrir más intensamente en algún tipo de sesión de planning, de la que deberíamos extraer los criterios de aceptación de las historias de usuario en las que vayamos a trabajar próximamente. Esas sesiones pueden contener ejercicios de estimación como planning poker o de refinamiento como example mapping.

Personalmente me gusta bastante el example mapping porque ayuda a estructurar las conversaciones, desgranando las historias de usuario en forma de reglas, dudas y ejemplos que ilustran las reglas.

Specification by Example

Las reglas forman parte de los criterios de aceptación de una historia, mientras que los ejemplos nos marcan la especificación y nos sirven como primer paso para hacer Specification by Example (prefiero ese nombre que Behaviour-Driven Development) con el que dirigir el diseño incremental de las iteraciones.

Como herramienta para hacer Specification by Example suelo utilizar cucumber, de modo que las especificaciones terminan en el típico formato gherkin Given/When/Then.

Evidentemente se pueden utilizar otras herramientas para esto, pero ¿por qué cucumber?:

  • Al separar las especificaciones como texto plano, permite colaborar fácilmente con perfiles no técnicos para elaborarlo o validarlo.
  • Ayuda forzarse a utilizar lenguaje ubicuo que deberíamos haber ido desarrollando (quizá incluso mantengamos un glosario de términos).
  • Queda como fuente de verdad y sirve como documentación viva. Si una funcionalidad cambia, quedará reflejado el cambio.
  • Facilita hacer ATDD separando claramente el ciclo de especificación del de desarrollo.
  • Permite implementar tests automáticos a distintos niveles. Pudiendo hacer tests sólo de la lógica de negocio, desde la UI, a través de un API…

A partir de las historias de usuario y de las especificaciones con ejemplos tendremos identificados los casos de uso o interacciones, el alcance bien definido y habremos trabajado el lenguaje ubicuo.

Este tipo de prácticas hacen más eficiente el proceso de desarrollo, aumentando la calidad del producto y evitando retrabajo a causa de malosentendidos.

Próximamente

Espero escribir al menos un post más, tratando la parte más técnica a través de un ejemplo práctico sobre:

  • Implementar tests automáticos desde especificaciones gherkin con cucumber.
  • Artefactos tácticos de Domain Driven Desing.
  • TDD Outside-In, empezando desde las interacciones o use cases.
  • Arquitectura hexagonal/clean, escapando de frameworks.

Otros recursos

En forma de cursos, posts y videos relacionados que tengo repartidos por ahí:

Maven multimodule y attached tests

En mi vuelta al mundo java, una de las cosas que he retomado es el utilizar de forma habitual proyectos maven multimodule, lo que facilita controlar mejor las dependencias y segregar el empaquetado.

Así podemos hacer separación de responsabilidades dependiendo de nuestros intereses. Por ejemplo, algunos escenarios por los que podríamos querer usarlos serían:

  • Partir en vertical una aplicación por temas funcionales.
  • Publicar de forma independiente una librería que sale de nuestro proyecto para reutilización de terceros.
  • Partición horizontal de una aplicación o servicio: core, diferentes APIs, persistencia…

El último escenario es el que he empezado a utilizar de forma habitual, separando el modelo de dominio de los detalles de implementación de infraestructura y mecanismo de entrega. Así evitamos en lo posible tener dependencias en los módulos core a través aproximaciones de arquitectura hexagonal.

El problema que surge frente a tenerlo en un sólo módulo maven es lo respectivo a la duplicidad en cuanto código de test.

Un ejemplo es reutilizar algunos helpers de tests. En nuestro caso, implementaciones de patrones que nos facilitan la mantenibilidad como el builder o el object mother. Tener objetos fake (puede que incluso algún dummy) para usarlos como colaboradores y desacoplarnos de las implementaciones reales. O tener especificaciones en lenguaje gherkin desde el módulo core, para que sean compartidos con los módulos responsables de tener implementados los tests de cucumber.

Gracias a Maven JAR Plugin podemos evitar estas duplicidades, ya que nos permite empaquetar los tests de un módulo en un JAR sin tener que mezclarlo en los paquetes de código de producción.

Por ejemplo, para compartir los tests del módulo core deberíamos configurar la construcción del JAR de test de este modo:

<project>
  ...
  <build>
   <plugins>
     ...
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-jar-plugin</artifactId>
       <version>3.1.1</version>
       <executions>
         <execution>
           <goals>
             <goal>test-jar</goal>
           </goals>
         </execution>
       </executions>
     </plugin>
   </plugins>
  </build>
</project>

Mientras que en los módulos que queramos usar el JAR de los tests deberemos incluir la dependencia así:

<project>
  <dependencies>
  ...
    <dependency>
      <groupId>com.danilat.killerapp</groupId>
      <artifactId>core</artifactId>
      <version>X.Y.Z</version>
      <type>test-jar</type>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Con esto ya podemos reutilizar los helpers de test o especificaciones de cucumber en diferentes módulos de maven.