Clean Architecture - Code Smells. Parte 1

Empecé a utilizar Clean Architecture a finales del 2015 en la última empresa con la que trabajé por cuenta ajena, al principio cometia errores como con todo lo que he empezado nuevo. Seguro que ahora cometo otros y todavía no lo sé :)

Llevo trabajando como freelance desde el verano del 2016 y he seguido utilizando esta arquitectura en proyectos para clientes, proyectos personales y también impartiendo formaciones o en consultorías ayudando a otros equipos de desarrollo.

A día de hoy me sigo replanteando mejoras en mi forma de implementar Clean Architecture y espero seguir replanteándomelo constantemente.

A lo largo de este tiempo gracias al contacto con otros desarrolladores y a mi propia experiancia, he ido identificando problemas comunes que se suelen cometer al utilizar Clean Architecture.

Me gustaría compartir algunos de estos code smells y aportar mi opinión al respecto.

No aislar bien el dominio

En cualquier arquitectura orientada al dominio como Hexagonal o Clean es básico que el dominio este aislado porque esto nos aporta muchas ventajas.

Uno de los principales code smells que me he encontrado es no aislar bien el dominio que acaba haciendo referencias a capas externas, librerias o frameworks.

Según se aprecia en el diagrama original, cuando hablamos de dominio en Clean Architecture se corresponde con la capa donde se encuentran los casos de uso y entidades.

Pero dentro del dominio también se ubican, y no queda tan claro en el diagrama, las interfaces de los adaptadores que se implementan en capas más externas y que el dominio invocará utilizando el Principio de Inversión de dependencia.

Un ejemplo claro de interfaces de adaptadores son los repositorios que se encuentran definidas dentro del dominio y la implementación en la capa externa de datos o de persistencia.

Se necesita cierta disciplina para no caer en utilizar directamente un libreria externa o framework desde el dominio y en ocasiones las prisas, pereza o simplemente si no te das cuenta puede ocurrir.

Normalmente suelo recomendar dos cosas para la gente que empieza con Clean Architecture:

Si es un problema de descuido, muchas veces revisar los imports nos ayuda a darnos cuenta de si estamos referenciando una capa externa.

Pero si tenemos el dominio y las capas externas dentro del mismo proyecto físicamente es más fácil que acabemos contaminando el dominio si no estamos acostumbrados a implementar una arquitectura orientada al dominio.

Lo que más suele ayudar a mantener el dominio aislado es crear una librería o módulo, según la técnología con la que desarolles, separado y dedicado exclusivamente al dominio.

Cuando tienes una librería dedicada al dominio si quieres referenciar inconscientemente una clase de una capa externa no vas a poder a no ser que añadas las dependencia con la librería a la que pertenece, y en ese momento te haces consciente.

Mala definición de casos de uso

Esta es la definición original los casos de uso:

The software in this layer contains application specific business rules. It encapsulates and implements all of the use cases of the system. These use cases orchestrate the flow of data to and from the entities, and direct those entities to use their enterprise wide business rules to achieve the goals of the use case.

Se pueden sacar varias conclusiones, la primera es que se corresponden con los casos de uso del sistema y la segunda es que orquestan el flujo de datos desde y hacia entidades. Por lo tanto trabajan con entidades.

En ocasiones me he encontrado casos de uso donde no se trabaja con entidades sino con datos primitivos.

Cuando se empieza a trabajar con una arquitectura orientada al dominio cuesta empezar a ver las información y las reglas de negocio encapsuladas en entidades porque seguimos teniendo una obsesión por los datos primitivos.

Imaginemos una aplicación donde en función del plan contratado por el usuario puede grabar un video desde la aplicación o no.

En lugar de crear un caso de uso que me devuelve un valor primitivo:

public class getAllowRecordVideoUseCase{  
   public bool execute(){...}
}

Tiene más sentido crear un caso de uso que devuelve la entidad de dominio:

public class getAccountPlan{  
   public AccountPlan execute(){...}
}

Y desde el código que invoca el caso de uso se consulta AllowRecordVideo de la entidad de dominio.

De esta forma este caso de uso si esta trabajando con entidades y además es totalmente reutilizable para obtener cualquier otro dato del plan contratado donde se necesite.

Si trabajamos con valores primitivos estaremos duplicando reglas de negocio empresarial en varios sitios. Estas reglas deben permanecer encapsuladas en las entidades de dominio porque esto nos permite reutilizar ese código de negocio tan importante.

Devolver valores primitivos desde los repositorios

Antes comantabamos que un motivo para cometer el code smell de no definir un caso de uso correctamente orientado a entidades puede ser porque que nos cuesta empezar a ver datos y reglas de negocio encapsulado dentro de entidades de dominio sobre todo al principio.

Comentábamos también que otro motivo es debido a la obsesión por los datos primitivos.

Estos dos motivos nos llevan también a crear situaciones similares con los repositorios en la capa de datos o de persistencia.

Hay muchos artículos que me gustan sobre Repository Pattern, pero voy a mencionar dos de ellos:

Puedes estar de acuerdo en mayor o menor medida en lo que dicen pero lo que esta claro es que los repositorios deben trabajar con entidades de domonio.

Por ejemplo en una app como Spotify existe una preferencia de usuario "Offline mode" que si esta activada no se puede escuchar ni buscar musica online sin wifi, en esas circunstancias solo funciona con datos locales.

Cuando realizamos una búsqueda, tendremos un caso de uso SearchUseCase, este caso de uso necesita saber si la preferencia "Offline mode" esta activada y si hay conexión para realizar la busqueda llamando a una API o a la base de datos local.

En lugar de definir un método en el repositorio que devuelve un valor primitivo y que es invocado desde el SearchUseCase:

public class UserPreferencesRepository ... {  
   public bool isOfflineMode(){...}
}

Tiene más sentido crear un repositorio que devuelve la entidad de dominio UserPreferences:

public class UserPreferencesRepository ...{  
   public UserPreferences get(){...}
}

Este método del repositorio nos esta devolviendo una entidad de dominio, se puede consultar la preferencia que necesitamos para nuestro ejemplo y podrá ser reutilizado desde cualquier otro caso de uso de nuestra aplicación.

Este caso es sencillo pero imaginaros que el dato a consultar para tomar la decisión desde el caso de uso debe ser calculado teniendo en cuenta otros 3 datos distintos, en ese escenario la entidad de dominio debe ser la encargada de devolverme esa información, de lo contrario estaria duplicando esa lógica en el repositorio.

Por estos motivos siempre debemos trabajar con entidades de dominio.

Conclusiones

Empezar a utilizar Clean Architecture como otras arquitecturas orientadas al dominio no es fácil y se cometen errores.

Por eso he querido compartir esta primera parte de code smells que yo mismo he cometido o que suelo ver que se cometen.