La S de SOLID

Para poder decir que uno escribe código limpio tiene que comprender y saber aplicar lo que esconde el acrónimo S.O.L.I.D. El programador Robert C. Martin lo acuñó con el objeto de recordar estas cinco normas que todo clean coder debe divulgar y utilizar.

Estos principios se aplican a programación orientada a objetos. El objetivo principal es que nuestros programas sean mas fácilmente mantenibles y comprensibles así como permitir que crezcan y evolucionen de forma limpia e inteligente.

Los cinco principios son :

  1. Single responsibility
  2. Open-closed
  3. Liskov substitution
  4. Interface segregation
  5. Dependency inversion

Si queréis escribir buen código, aplicar estos principios. ¿Perdéis la calma luchando contra vuestro código? ¿Vuestros programas están llenos de wtf’s? ¿Vuestro código es hostil? Tal vez necesitáis S.O.L.I.D inyectado en vena.

En este post escribo sobre el primer principio.

Robert C. Martin definió SRP inspirándose en el concepto de cohesión explicado por Tom DeMarco. Cuando he leído sobre SRP la parte mas difícil de comprender ha sido precisamente “responsabilidad”.

¿Qué es una responsabilidad?

Una razón para cambiar

 

¿Y qué es una razón para cambiar?: Una de las explicaciones que mas me gusta es la que atiende al sujeto de esa responsabilidad, es decir: ¿a quien responde esa responsabilidad?

Una clase solo debería responder a una necesidad

 

Si una clase posee varias responsabilidades, estará también articulando varias relaciones de responsabilidad con varios actores.

No podemos entender responsabilidad como algo concreto a nivel técnico, ni asimilarlo al trabajo de una función o de toda una clase. La responsabilidad es algo mas amplio, es una explicación semántica sobre lo que hace la clase, no una definición de las tareas que ejecuta.

Es un error pensar que este principio acarrea clases de una sola función. Una sola responsabilidad puede ser definida por diversas funciones, o por una sola, seria estúpido establecer un numero. Aquí de lo que se trata es de que nuestra clase responda a una sola exigencia, a una solo necesidad. Si mi clase solo responde a una necesidad, entonces solo hay un motivo por que el que pueda ser modificada: el cambio de la necesidad misma.

Imaginemos un programa que necesita utilizar la API de un servicio web. Podemos pensar en construir una clase que realice una llamada al servicio, trate la respuesta y actualice una base de datos con las informaciones recibidas.

class WebServiceConnector
{
    public function makeCall()
    {
    }

    public function parseResponse()
    {
    }

    public function connectDatabase()
    {
    }

    public function updateInfo()
    {
    }
}

Analizando ligeramente esta clase, vemos que en efecto hay muchas razones para cambiar, es decir, que es muy probable que tengamos que modificar esta clase por diferentes motivos.

Imaginemos que usamos una base de datos mysql pero que cuando nuestro sistema crece hasta los trescientos millones de registros, decidimos usar una base de datos hypertable. Deberemos realizar cambios relativos a bases de datos en una clase en la que por ejemplo, encontramos llamadas a servicios web: dos responsabilidades que definitivamente no tienen nada en común.

Si el servicio web que utilizamos cambia su interfaz, haremos cambios en la función que realiza las llamadas, mientras que nuestra base de datos, nuestro parseo de datos y nuestra modificación en base de datos, no necesitan ser modificados.

Esta clase responde a la necesidad de comunicarse con un servicio web, de analizar los documentos XML recibidos, de gestionar la comunicación con la base de datos y también de actualizar informaciones en una base de datos. Es una clase Frankenstein que pretende contentar a muchos actores: documentos XML, servicios web soap o bases de datos mysql.

Todas estas responsabilidades, agrupadas en una sola clase están acopladas. Es decir, tienen un nivel de acoplamiento máximo cuando en realidad son objetivamente distintas.

Es necesario separarlas:

class SoapClient
{

}

class Parser
{

}

class DatabaseDriver
{

}

class ApiConnector
{

}

Aquí no pretendo ser exhaustivo, solo reflejar la necesidad de SRP conceptual o filosóficamente. Por supuesto, nos debemos preguntar si estas clases que hemos creado tras una primera reflexión, tienen una sola responsabilidad, es decir, si tienen un solo motivo para cambiar, y si no, analizarlas y modificarlas convenientemente.

Este principio es la base de todo lo demás. Si el programador no pone empeño y horas en realizar un buen diseño, separando las responsabilidades y evitando el acoplamiento, muchas otras buenas practicas y principios se verán muy comprometidos.

Dependency Injection

“As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.”

 

A: ¿Detrás de cuantas cosas de programación esta Fowler? y en general los que hicieron el Agile Manifesto?

D: No sé, pero es el padre de muchas cosas.

A: Menuda cabeza (por fin estoy leyendo su articulo), pero habla de tres tipos de DI, dos usan librerías externas así que no le veo mucho interés. A ver la tercera.

D: Yo quiero hacer algo parecido a DI. Tener algo que instancie al comienzo de la ejecución, y decirle get(NombreDeLaInterface::class) y que me la dé. De esa manera no meto todas esas instanciaciones en 1000 sitios. Al fin y al cabo es como tener un factory que me dé las dependencias. No será una librería de inyección de dependencias, pero así tengo todo ese tipo de instanciaciones en un solo sitio. Y como configuré los environments en CI (production, development y testing), pues que dependiendo del environment que se esté ejecutando, poder cambiar.

A: Parece lo que Fowler llama Injector . Define una interfaz para el.

D: Me ayudó mucho a entenderlo usando la librería que se usa en Android para ello “Dagger”, vas creando “Componentes”, que tienen métodos con la anotación @Provides y lo que retorne, luego en tiempo de compilación se resuelven todas las dependencias. En las clases de cliente, añades un @Inject Repository userRepository y ya el inyector hace su trabajo. Si he notado que al principio con pocas cositas, es fácil de llevar, pero cuando se hace mas grande, ya tienes que ir dividiendo esos componentes, para quizás encapsular los conceptos del dominio. UserComponent, ProductComponent etc…

A: me recuerda a ng2 eso que dices, diría que es la misma filosofía.

D: Dagger 1, fue desarrollado por Sqare, y Dagger2 le hizo fork Google, quizás hayan usado un poco esa filosofía en los laboratorios oscuros de google…

A: pues para no variar no entiendo el ejemplo de Fowler…

D: jajaja

A: supongo que es Java verbosity… “Then, as usual, I need some configuration code to wire up the implementations. For simplicity’s sake I’ll do it in code.” No se lo que es el container.

D: mas abajo lo usa en registerComponents() pero es eso, un contenedor. Parece que internamente, en el container registra un nombre y una clase.

A: si, pero no me dice nada eso. Es decir, que lo veo mágico, no se como funciona ni veo el ejemplo.

D: bueno imagino que ahí está esa magia. Es un simple ejemplo.

A: pues no me vale ! xD

D: Si se pone a sacar el container, saldría una librería ahí xD “Martindency Fowler Injection”

A: luego hay gente para la que DI es simplemente pasar en el constructor las dependencias

D: yo diferenciaría entre DI (Dependency Inversion) y DI (Dependency Injection) donde Dependency Inversion es el patrón, y Dependency Injection la herramienta para llevar a cabo las inyecciones.

A: si, pero debe haber confusión entre la gente, porque veo por ejemplo un articulo aquí donde se dice que que dependency injection es pasar por parámetro… asi que no se, un lio.

D: al menos yo personalmente intento separar ambos. Creo que es igual que MVC, para algunos es una arquitectura para todo y para otros es solo una arquitectura de la capa de presentación. Creo que el software es un cúmulo de ideas con doble sentido.

A: veo que en general la gente usa el termino “dependency injection” refiriéndose a “constructor injection” o “setter injection” cosa que no tiene nada de especial. Yo cuando pienso en dependency injection pienso en “interface injection”, cosa que no entiendo.

D: yo el setter injection no lo he usado nunca

UserRepository repo = new SQLUserRepository();
repo.setMapper(new UserMapper());

vs

UserRepository repo = new SQLUserRepository(new UserMapper));

se consigue lo mismo, pero y con interface ? a que te refieres con interfaces? uhum al InterfaceInjection como explica el?

A: si

A: osea lo que hace es crear una interfaz que define por ejemplo un Finder, luego las implementaciones concretas, ok pero porque la clase que usa la implementacion debe implementar tb la interfaz ? no se, ya te digo que no lo entiendo. He encontrado esto: What is dependency injection? a ver si me ayuda.

D: creo que es un ejemplo de como hacer un inyector mas bien no?, al que tu le pasas un algo que necesita inyección (que implemente el InjectFinder)

A: “Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into fields.”
vale, entonces es solo eso…

D: el Interface Injection que expone creo que es solo una forma de hacerlo

A: vale. No se, pensaba que era algo mas sofisticado, yo hago DI desde el principio! antes de saber como se llamaba.

D: seguramente el container, le pides una clase, que en el ejemplo que pone es MovieLister que tiene para inyectar un MovieFinder y un FinderFileName. El container imagino que instancia el MovieLister, y le hará el “injectFinder(loquesea) y injectFileName(loquesea) de manera que para cuando tu tienes el movielister, ya tiene todo inyectado

A: lo que pasa es lo que te decía, que Fowler siempre complica un poco todo mas de lo que es…. es su forma de pensar, es un tío complejo. Lo que no veo ahora es la ventaja de hacer eso frente a usar el constructor o el setter o directamente la propiedad. (Tener mas de una implementacion ?)

D: Ahí ya supongo que es cuestión de necesidad.

A: Injection Container parece que es eso, un contenedor que inyecta las dependencias allí donde se necesite no?

D: le veo la lógica en el caso de que quieras obtener un MovieLister:

public void testIface() {
    configureContainer();
    MovieLister lister =     
       (MovieLister)container.lookup("MovieLister");
    Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
    assertEquals(
       "Once Upon a Time in the West", 
       movies[0].getTitle()
    );
}

Imagina que hace:

MovieLister lister = (MovieLister)  
container.lookup("IMDBMovieLister");

ese container puede usar la misma instancia del movieLister que tenías, pero le inyecta el MovieFinder de IMDB de modo que ahora el movieLister ha cambiado dependencias, sin tener que crear otra instancia, con inyección por constructor, esto no hubieras podido hacerlo es el caso de uso que se me ha ocurrido para este tipo.

A: es decir la ventaja aquí es usar una misma instancia de movieLister. Inyectando diferentes “finders” en una clase aparte llamada “container” . Con constructor no funciona, pero yo puedo hacer un setter cada vez del finder, sin cambiar tampoco la instancia de MovieLister.

D: no sé si en realidad Fowler lo ha hecho por esto, pero podría resolver este caso de uso.

A: la única utilidad que le veo es en aplicaciones enormes donde merezca la pena tener el container.

D: date cuenta, que cuando usas la herramienta como Dependency Injection, vas a depender de un “Container” siempre. Porque te quitas un boilerplate en código cliente y porque es bastante cómodo, y esto no afectaría a tu core si no lo haces “intrusivo”

A: entonces lo que aplica internamente el container es dependency injection de forma automatizada digamos, es un patrón de diseño ? o es un framework ?

D: ahí está la diferencia que te dije antes. El Container es la herramienta, y la inversión de dependencia, el caso de uso, el patrón.

A: el patrón es ultra simple, el framework no.

D: tu puedes desatornillar un tornillo con un destornillador, o con una taladradora a la que le has puesto el cabezal para desatornillar. Ahora bien, desatornillar el tornillo de la tapa del ordenador, no es lo mismo que desatornillar el tornillo de un camión, pero en general, es Desatornillar, sabes que para un lado aprietas y para otro aflojas.

A: Interesante metáfora. El ejemplo de container que pone aquí me queda mas claro. Es simplemente una clase que asocia una instancia a sus dependencias y la entrega al cliente.

D: crees que debería hacerme poeta? siempre suspendía Lengua Castellana y Literatura, y ahora encuentro pequeños resquicios en mis metáforas. xDDD

A: jajaja pues oye, es un campo a explorar, igual encuentras placer en ello.

D: pero lo que decías, es que en general es eso. Algo a lo que le dices “Dame esto”, y te lo dá. No es muy diferente a un Factory como ves.

A: “Last, but not the least, each time I want to get a mailer, I don’t need a new instance of it. So, the container can be changed to always return the same object:” si te fijas, también habla de eso, es también otra de las cosas que controla el container? Puedes evitar “singletons” que tiran de comprobar si existe una instancia con la variable static. Solo necesitarías (en un ejemplo básico) un singleton, el simple Container. A cambio de depender del container.. cosa que tampoco mola no ? pero supongo que no se puede tener todo.

D: Bueno, supongo que depende del lenguaje y la aplicación

A: Como siempre es un compromiso.

D: el container es eso, si le pides algo y no lo tiene, lo crea, por lo que el impacto de construirlo (si no es complejo o no necesita nada externo), es diminuto

A: bueno esta bien, los posts y esta conversación me han aclarado el tema, menos mal que te tengo aquí.

D: a mi me ha enseñado el inyector de android tras pegarme muchos golpes contra la pared. Tiene unas anotaciones, que se llaman @singleton pero en realidad hace que las dependencias no sean singleton, sino que solo habrá 1 en el graph de objetos. Sin embargo, si no lo tiene, te va a dar siempre una nueva pero esto, ya es algo que la herramienta te ofrece. El container, como te lo montes, da igual, el te va a dar las cosas, a tu código cliente le da igual, como se instancien!

A: respecto a los tests…. si haces tdd, nunca tendrás el container en los tests no ? a no ser que lo estés creando… digo que por ejemplo el test instanciara el mailer y el transport y quiza otro test probara el container.

D: pues a mi parecer ya depende si es un end to end, si vas a hacer uso del container pero en test unitarios, a no ser que sean tests del Container, no lo necesitarías

A: si, eso pienso yo tb

D: es decir, yo el container no lo veo en el core o domain

A: es una utilidad no ? como lo situarías ? capa de infraestructura ?

D: Pues no sabría donde situarlo porque lo veo mas externo a la aplicación en sí.

A: entonces, desde el punto de vista de hexagonal seria capa de infraestructura yo creo… metiendo ddd no se donde se quedaría, me anoto la duda.

D: basándome en el rosco de Clean Architecture, quizás lo pondría en la verde. Y no estaría seguro, es algo que te resuelve las dependencias.

A: es como una metaclase que organiza otras clases.

A: como adaptador ?

D: una librería que instancia cosas xD

A: pero si lo pones en la capa exterior, tu core depende de algo externo (prohibido). Tendrás que aplicar DI al container.

D: estaría fuera del application, basándome en el dibujito

A: vale, yo a eso lo llamo infraestructura. El pb es resolver la dependencia del core sobre una clase externa.

D: a que te refieres?, algún ejemplo?

A: tu core depende del container para obtener las clases que sea con sus “injecciones” y eso no es posible. Como dice U Bob, los módulos solo pueden depender sobre módulos mas internos, no al contrario.

D: yo en mis apps con Clean (que no brillan, las cosas como son), no he necesitado usar el framework en el core nunca. Desde el punto de vista de clean architecture, iría en aquello que invoca los use cases.

A: sea… por ejemplo… en un controlador web

D: tu cuando obtienes el “Interactor” del CA (Clean Architecture), ya tiene sus dependencias resueltas. No deberías usar el inyector mas. Otra cosa es cuando se plantea el problema de tener que elegir entre diferentes implementaciones en runtime.

A: en ese caso pensaba antes, al hacerte la pregunta.

D: es algo que inevitablemente no he logrado averiguar, pero me ha pasado. Pero sabes que pienso?, que si por ejemplo guardar algo en un sitio u otro, es una regla de negocio, eso lo convierte en un caso de uso, no sé si me explico.

A: si, que si encuentras ese problema, tal vez tienes un code smell, un problema de diseño. Y debes sacar esa decisión.

D: si los usuarios con nombre Pepe van a guardarse en un sitio, y los que se llaman Paco en otro, esto es una regla de negocio y vas a tener que tener algún tipo de orquestador que decida donde van las cosas. Lo cual puede ser un caso de uso por ejemplo. Suena feo, porque es como si tu lógica de negocio tuviera que saber sobre la infraestructura

A: si, obligas al core a preocuparse por un detalle, cosa que no es factible, pero si tu core usa un container, entonces no hay ese problema no ? Pero el container debería formar parte del core, o bien invertir la dependencia para que el core no dependa en una capa de la cebolla superior, es decir el core solo se preocupa de llamar un método guardaX() y nada sabe de la implementacion, lo cual esta bien. El container facilita eso. Yo creo que desde el punto de vista de hexagonal tendrías un puerto de salida con su adaptador… puede que el adaptador se ocupara de recuperar la implementacion por ejemplo. Tu core solo dice guarda lo que sea! y va al output port con un objeto command. Y el adaptador puede por ejemplo examinar ese objeto command y obtener la implementacion que lo guarda.

D: hombre, tu adapter, puede tener algún mecanismo de decisión. Puedes tener un OrquestorUserRepository que implementa la interface de UserRepository el cual para crearse necesita un UserRepositoryFactory y en el save(user);

if (user.name === "Paco"){
    userRepositoryFactory->getSQLRepository()->save(user))
} else if (user.name === "Manolo") {
    userRepositoryFactory->getCloudRepository()->save(user));
}

es una forma de hacerlo. Es como cuando tienes que obtener de un repo y cachear, está claro que eso es algo que se puede controlar en esa capa.

A: en clean code, el libro, Uncle Bob dice lo mismo sobre DI…. y habla de containers muy someramente (cita spring para java) y como toooodo el mundo, hace link del articulo de Fowler. Fowler is God.

D: Fowler tio, fowler. Joder es que ese tío es un crack en serio. Creo que es uno de los grandes de la historia del IT.

A: sin duda. Le he dado otra vuelta a su articulo y lo veo mas claro.

Value Objects

En DDD (domain driven design) los value objects son objetos simples que cuantifican, miden o describen algo que pertenece a nuestro dominio. Representan un valor y es su valor lo que importa y lo que los diferencia de otros value objects.

Hay dos aspectos a tener en cuenta para entenderlo mejor:

  1. Como dice Ward Cunningham, es una medida o una descripción de algo.
  2. Su identidad se define a través de su estado y no usando un campo identificador único. Así que, como dice Martin Fowler, dos value objects son iguales si sus atributos contienen los mismos valores. Deben ser inmutables.

Algunos ejemplos de value objects pueden ser:

  • Una fecha
  • Dinero
  • Un nombre
  • Una dirección
  • Una cantidad

Un ejemplo concreto

Por ejemplo, en una receta de cocina, podríamos tener un value object que representara la cantidad “250 gramos”. En realidad esa instancia concreta de la cantidad “250 gramos” no importa demasiado en si misma, si no es a través de su valor. En tanto que objeto inmutable, sea esta u otra instancia, lo importante es el valor que tiene. Será igual a otra con el valor “250 gramos”.

Si necesitáramos representar el valor “1 kilogramo”, aunque también se trata de una cantidad, en lugar de modificar la instancia previa, crearemos una nueva.

Una ventaja obvia de conceptualizar estas descripciones bajo la forma de value objects, es que todo el comportamiento relativo a ellos puede ser implementado dentro de esas clases, por ejemplo una validación. De esta forma tenemos otra ventaja: luchamos contra el anti-pattern de los modelos de dominio anémicos.

Show me the code

Me gustaría traducir en código el ejemplo de la cantidad para los ingredientes de una receta. Por ejemplo “250 gramos” podría ser resuelto con una clase que guarde el tipo de medida (gramos, kilos, mililitros…) y otra que une este concepto a una cantidad (1, 250, 3.5).

Ambas son value objects, en este caso miden la cantidad necesaria de un ingrediente en una receta.

class Measure
{

    private $name;

    public function __construct($name)
    {
        $this->setName($name);
    }

    private function setName($name)
    {
        //implements some validation
        $this->name = $name;
    }

    public function name()
    {
        return $this->name;
    }
}
 

class Quantity
{

    private $amount;
    private $measure;

    public function __construct($amount, Measure $measure)
    {
        $this->setAmount($amount);
        $this->setMeasure($measure);
    }

    private function setAmount($amount)
    {
        $this->amount = floatval($amount);
    }

    private function setMeasure(Measure $measure)
    {
        $this->measure = $measure;
    }

    public function amount()
    {
        return $this->amount;
    }

    public function Measure()
    {
        return $this->measure;
    }
}

Arquitectura hexagonal

 Una de las razones por las que una arquitectura de software es necesaria es que los frameworks en los que habitualmente trabajamos como laravel o symfony promueven un estilo de código que no satisface las necesidades de las grandes aplicaciones.

Cuando trabajamos en un proyecto desde el inicio tenemos todo el control. Según avanza el proyecto se generan un montón de reglas no escritas que aplicamos habitualmente. El problema es que cuando la aplicación crece y tal vez nuevos programadores llegan o los que había se marchan, todas esas reglas no serán necesariamente aplicadas. El código será cada vez más un problema.

Se necesitan por lo tanto principios de organización. El más conocido es MVC.

Los gurús del software nos están siempre diciendo lo que está bien y lo que está mal. Finalmente solo tú como programador sabes lo que conviene a tu aplicación según tus medios y el contexto en general en el que te mueves. Cualquier decisión sobre arquitectura y prácticas de desarrollo en general debe ser tomada, bajo mi punto de vista, siempre tomando en cuenta tu contexto. Por ejemplo no tendrá mucho sentido aplicar DDD si nuestra aplicación es un CRUD con 4 controladores. Aunque por supuesto siempre podrás hacerlo, aunque solo sea por diversión.

MVC no es suficiente

Hay polémica sobre si MVC es arquitectura o no. Desde mi punto de vista no lo es. Lo veo como una guía de diseño como lo puede ser DDD, aunque mucho más simple y no apta para resolver problemas complejos. Por supuesto su aplicación genera una topología que produce modelos, vistas y controladores. En el fondo la discusión sobre si es arquitectura o es un principio de diseño es bastante estéril y no tiene importancia. Es habitual que los programadores nos pongamos muy pesados con los detalles sin importancia. El problema principal que yo veo es que la M de MVC tiene normalmente demasiado trabajo, y es responsable de demasiadas cosas. A menudo algunas de sus responsabilidades se trasladan al controlador creando poco a poco clases demasiado grandes y con demasiadas responsabilidades, lo que lleva al título del siguiente punto:

Difícil hacer tests

MVC viene de los frameworks. Y mucho software está íntimamente ligado al framework en el que fue creado. Este podría ser un proceso de trabajo habitual:

  • Elegir un framework
  • Elegir una librería de persistencia
  • Elegir una librería frontend
  • Instalar el skeleton de la aplicación
  • Borrar el código de demo
  • Auto-generar las entidades
  • Auto-generar los controladores CRUD

Aunque todo esto es muy importante, no es el núcleo de nuestro software. Todas estas decisiones, tomadas a priori, harán más lento y complicado hacer tests ya que estaremos obligados a instanciar todos esos elementos para poder probar una clase. Qué duda cabe que si no vas a hacer tests esto no representará un problema.

En mi experiencia personal estoy en un momento en el que tras rechazar los frameworks, quiero volver a experimentar con ellos, ya que tienen indudables ventajas, por ejemplo llevar a cabo miles de tareas tediosas que no queremos programar por nosotros mismos. Eso está bien, pero es necesario saber establecer los límites de nuestra aplicación y dejar claro su alcance y donde termina el framework.

Los frameworks encapsulan muchos detalles para nosotros, como transformar una request en un tipo de dato como XML o JSON. También la comunicación con la base de datos o el uso del protocolo HTTP. Sin embargo hay que encontrar la forma de trabajar limpiamente con ellos.

Abstracción

Un aspecto muy importante del desarrollo de aplicaciones limpias es la abstracción. Otro problema de los frameworks es que no son capaces de generar abstracción por nosotros. Por ejemplo para hacer una consulta y extraer unos datos a través de la capa de persistencia, el framework nos propondrá elementos muy concretos relativos a bases de datos relacionales, métodos como buildQuery o where ademas de ver los nombres de campos y tablas de bases de datos en el código. No es nada abstracto, son más bien implementaciones muy concretas.

Código acoplado

Otro problema con los frameworks es que tu código está muy acoplado con el llamado delivery mechanism. Me cuesta traducir esa expresión al castellano, tal vez “mecanismo de entrega”. En los frameworks veremos que habitualmente ese mecanismo de entrega es de un solo tipo: La web. Normalmente el controlador expresará lenguaje relacionado con la web y la base de datos. ¿Pero qué ocurre si queremos ejecutar código desde una línea de comandos? Será imposible. El código está muy ligado a un controlador web.

Revelar intenciones

Otro problema es que el código MVC normalmente no muestra la intención del código. En inglés habréis oído hablar de “reveals intentions. Leyéndolo no sabemos de qué trata, qué hace, cuál es la necesidad o su significado. Si observas su árbol de directorios, solo ves carpetas como models, views, controllers que en efecto no revelan nada sobre el comportamiento de la aplicación.

Todo esto puede estar muy bien cuando haces un desarrollo de app muy rápido. El código que obtienes usando la documentación de un framework normalmente es útil para hacer CRUDs.

¿Como arreglar esto?

Arquitectura hexagonal

A menudo instalamos el framework y sentimos que nuestra aplicación ya está funcionando. Creo que en realidad aun no tienes nada aparte de un montón de código que no tiene nada que ver con tu dominio.

Tenemos que saber diferenciar entre lo que constituye nuestra aplicación y todo lo demás. A nuestra aplicación nos referimos con el nombre core:

El core es lo que permite solucionar los problemas concretos a los usuarios de nuestra aplicación en un dominio concreto. También forma parte del core las formas de interacción con el mismo, definidas en los casos de uso.

 

A todo lo demás nos referimos con el nombre detalles (de la aplicación). Es aconsejable que nuestra aplicación no sepa nada del exterior y que sea únicamente a través de interfaces bien definidas donde se produce la comunicación. Es decir que nuestro software es permeable solo en la medida que queremos. En contraste con una aplicación MVC común incrustada en un framework, que está completamente infestada por los detalles de implementación de la infraestructura.

Llamamos infraestructura a todo aquello que es exterior al core. Estos son algunos ejemplos:

  • Comunicar con la web
  • Comunicar con la línea de comandos
  • Relación con el sistema de archivos
  • Comunicación con la base de datos
  • Envío de emails

Un diseño demasiado pobre y compacto arruinará la capacidad de mantener, probar y hacer evolucionar correctamente los sistemas complejos. Para aliviar eso tenemos arquitecturas de capas que permiten separar el código, así como establecer reglas para comunicarse entre ellas, establecer los límites o boundaries en inglés, además de ayudar a colocar nuestro código donde corresponda en lugar de depender de esas “reglas no escritas”.

Hablando de sistemas de capas, Uncle Bob dice algo muy importante que debe destacarse (traslado la idea, no traduzco su frase):

Las dependencias sólo pueden ser de fuera hacia adentro, es decir, un modulo solo puede depender de algo que está en una capa inferior y nunca al contrario. Por lo tanto nuestro core nunca debe saber nada de las capas exteriores.

 

Ports

Para cruzar los límites enviamos mensajes en nuestro código. Estos mensajes pueden ser funciones y argumentos. Los límites de la aplicación están en los inputs ports. Un mensaje viene del exterior y si quieres que tu app se comunique con el exterior hay que establecer un input port que lo permita. Quiero experimentar con esta idea y escribir un post mas adelante sobre lo que puede ser un port usando un framework web.

Adapters

Por ejemplo el caso típico en la web sería un puerto que utiliza el protocolo de comunicación HTTP. Siempre que hay un puerto, hay algún tipo de traducción o transformación que es realizada por un adapter, de tal manera que una web request pueda ser procesada. Un adapter sería por lo tanto un grupo de clases que pueden transformar una petición externa, por ejemplo en la web, en algo que pueda ser tratado por la capa de dominio.

“Ports and adapters” es un alias para arquitectura hexagonal.

 

Los puertos permiten que exista la comunicación y los adapters traducen los mensajes del exterior para que puedan ser tratados en el interior. Por ejemplo una request web será convertida en un objeto command. Este objeto revela sus intenciones gracias a su nombre y ya no tiene nada que ver con el mecanismo de entrega (la web), es por lo tanto self-contained y pierde toda relación con el mundo exterior. Este objeto command es únicamente un mensaje y no realiza ninguna acción. A continuación un handler aceptará el command y hará lo necesario para que la acción se lleve a cabo.

Si la acción implica una persistencia, se usará de la misma forma un puerto de persistencia que puede actuar sobre cualquier tipo destino, por ejemplo una base de datos. La particularidad aquí es la siguiente:

Normalmente en un diseño clásico nuestro dominio (core) tendrá una clase que depende de un repositorio situado en la capa de infraestructura. Por ejemplo:

  • Core
  • usersRepository (Class)
  • Infrastructure
  • queryBuilder
  • entityManager

El usersRepository depende del queryBuilder y del entityManager. Como hemos visto anteriormente nuestro core no puede depender de clases situadas fuera del dominio (en capas externas). Para solucionar este dilema tenemos que usar inversión de dependencias:

  • Core
  • usersRepository (Interface) Nuestro dominio define la interfaz, esta no sabe nada de los detalles de implementación de la persistencia.
  • Application layer
  • usersRepositoryHandler
  • Infrastructure
  • usersRepository (Implementa la interfaz)
  • Podemos añadir cualquier implementación que respete la interfaz para conectar con otro tipo de persistencias.

En nuestro dominio definiremos una interfaz de repositorio en la capa de aplicación y tendremos un handler que usará el repositorio, dependerá de la interfaz, no de la implementación concreta. En la capa de infraestructura tendremos un repositorio que implementa la interfaz para crear un tipo de persistencia concreta.

¿Para qué sirve todo esto?

Separation of concerns. Tendremos varias capas en las que repartir nuestro código:

  • Core
  • Domain layer
  • Application layer
  • Infrastructure

Estamos seguros de poner nuestro código en la capa correcta. Es un principio de organización que ayuda en aplicaciones de cierta complejidad.

Tenemos casos de usos con comandos y handlers que realizan la acción que nuestra aplicación debe llevar a cabo. Esto aísla nuestro core del exterior.

La arquitectura hexagonal soporta muy bien diversos métodos de diseño como BDD, DDD, TDD o CQRS. También podría aplicarse en microservices, obteniendo muchos pequeños hexágonos.

Esto permite escribir tu aplicación como si el mundo exterior no existiera, y a continuación conectarla creando ports y adapters. Esto es muy interesante para hacer tests.

Conclusión

Creo firmemente en las ventajas de posponer siempre que sea posible todas las decisiones relativas a la capa de infraestructura, partiendo de la necesidad de crear un dominio sólido y testeable, estableciendo los puertos y los adaptadores que mas tarde nos permitirán conectar nuestra aplicación al mundo exterior.

Al principio podremos mockear esos boundaries y trabajar cómodamente por ejemplo usando TDD.

Por supuesto, el 90% de este post son ideas traducidas y adaptadas por mi durante el estudio y presentadas aquí para quien pueda sacar provecho de ello. No me doy ningún crédito por ello.

Arquitectura hexagonal es un concepto desarrollado por Alistair Cockburn. Como no, hace falta leer y releer los consejos de Robert C. Martin (Uncle Bob) para entender este ecosistema y en general las buenas practicas de programación orientada a objetos.