ES6

He estado leyendo sobre las novedades de ES6 y aquí va un pequeño recopilatorio de algunas de ellas.

Classes

Para comenzar aquello de lo que tanto se hablaba cuando salió. Lo primero que hay que decir es que es solo maquillaje para hacer la vida mas fácil en algunos aspectos y escribir menos. Aunque parezca una clase, se seguirá comportando como una simple función.

Como ventaja, al utilizar la sintaxis de clase, puede que sea mas simple tratar con la herencia de prototipos de Javascript.

class Car {
  constructor(brand, model, year) {
    this.brand = brand;
    this.model = model;
    this.year = year;
  }

  printOut() {
    console.log(`${this.brand} ${this.model} - ${this.year}`);
  }
}

let myCar = new Car('Daewoo', 'Kalos', 2005);
console.log(myCar.printOut()); //Daewoo Kalos - 2005
typeof myCar; //object
typeof Car; //function

Las clases pueden ser anónimas, asignando su definición a una variable, después usaremos esta variable para crear una nueva instancia de la clase.

var Car = class {
  constructor(brand, model, year) {
    this.brand = brand;
    this.model = model;
    this.year = year;
  }

  printOut() {
    console.log(`${this.brand} ${this.model} - ${this.year}`);
  }
};

var myCar = new Car();

En ES6 hay herencia, y puede ser útil para acceder al prototipo cómodamente.

class Amphibious {
  constructor(type = 'river') {
    this.type = type;
  }

  crossRiver() {
    console.log('I am swimming');
  }
}

class Car extends Amphibious {
  constructor(type, brand, model, year) {
    super(type);
    this.brand = brand;
    this.model = model;
    this.year = year;
  }

  printOut() {
    console.log(`${this.brand} ${this.model} - ${this.year}`);
  }
}

var car = new Car('Alfa Romeo', 'Giulia', 1998);
console.log(car.printOut());
console.log(car.crossRiver());

const

Como su nombre sugiere, se trata de una constante, es decir, un valor que no puede cambiar a lo largo de la ejecución de nuestro programa una vez que ha sido definido, al contrario que ocurre con las variables.

A día de hoy esto es así al menos en Chrome y Firefox, sin embargo Safari las trata como simples variables. Son por lo tanto útiles como en cualquier lenguaje, para albergar valores inmutables.

const MAX_CHILD_AGE = 18;
//Si intento redefinirla, obtendré un error
const MAX_CHILD_AGE = 16;
//Uncaught TypeError: Identifier 'MAX_CHILD_AGE' has already been declared(…)

let

Esta novedad me gusta mucho, porque permite controlar el scope de nuestras variables de una forma precisa. Al declarar una variable con let lo que ocurre es que esa variable solo sera modificable o accesible en el scope en el que ha sido definida: bloque, declaración o expresión donde se usa.

Sin embargo con var se puede acceder a la variable en el scope exterior a donde la estemos usando. Con un ejemplo siempre es mas claro.

var color = "red";
console.log(color);
if (true) {
  var color = "blue";
  console.log(color);
}
console.log(color);
let shape = "square";
console.log(shape);
if (true) {
  let shape = "circle";
  console.log(shape);
}
console.log(shape);

Generators

Me parecen bastante divertidos. Son unas funciones especiales, que nos permiten ejecutarlas por partes y parar o hacer continuar la ejecución. Las reconoceremos porque tiene un asterisco junto a la palabra function y contienen el termino yield.

De alguna forma se trata de una factoría de iteradores cuyo estado es controlado por una función. Por lo tanto nos puede ayudar a crear funciones asíncronas.

Un objeto es un iterador cuando sabe como acceder a los elementos de una colección, mientras mantiene un registro de su posición actual dentro de esa secuencia. En JavaScript un iterador es un objeto que proporciona un método next() que devuelve el siguiente elemento en la secuencia. Este método devuelve un objeto con dos propiedades: done y value. Ver mas sobre ello en MDN.

 

//GENERATORS
var numberGenerator = function* numberGenerator() {
  var number = 0;
  yield number++;
  yield number++;
  yield number++;
  yield number++;
  yield number++;
  return;
}();
console.log(numberGenerator.next());
console.log(numberGenerator.next());
console.log(numberGenerator.next());
console.log(numberGenerator.next());
console.log(numberGenerator.next());
console.log(numberGenerator.next());
//{ value: 0, done: false }
//{ value: 1, done: false }
//{ value: 2, done: false }
//{ value: 3, done: false }
//{ value: 4, done: false }
//{ value: undefined, done: true }

next() puede recibir un parámetro, que sera considerado en la función como el resultado del ultimo yield.

Default function parameters

Puede parecer increíble, pero aun no existían. Estamos acostumbrados a verlos en otros lenguajes como PHP. Tan simple como:

function takeCare(takeIt = 'easy') {
  console.log(takeIt);
};
takeCare(); //easy

Template literals

Parece ser que tenemos una forma de decir adiós al infierno de las dobles comillas y las concatenaciones con el símbolo +.

En general, incluir información compleja es mas sencillo. Veamos algunos ejemplos.

//Concatenar con variables
var animal = 'cats';
var sentenceES5 = 'She said ' + animal + ' are independent';
var sentenceES6 = `She said ${animal} are independent`;
console.log(sentenceES5);
console.log(sentenceES6);

//Saltos de linea
var sentenceES5 = 'She said ' + animal + '\nbreak the line';
var sentenceES6 = `She said ${animal}
break the line`;
console.log(sentenceES5);
console.log(sentenceES6);

//Concatenar con operaciones
var sentenceES5 = 'She said ' + animal + ' break the line ' + (1 + 1) + ' times';
var sentenceES6 = `She said ${animal} break the line ${1 + 1} times`;
console.log(sentenceES5);
console.log(sentenceES6);

//Llamar a funciones
var sentenceES5 = animal.toUpperCase() + ' are evil';
var sentenceES6 = `${animal.toUpperCase()} are evil`;
console.log(sentenceES5);
console.log(sentenceES6);

Arrow functions

Lo primero que hay que decir es que las arrow functions vienen a resolver un problema de diseño de javascript: la palabra this en una función sin scope, se refiere al objeto window. Es decir que las arrow functions vinculan contextualmente this a la función, lo que es de esperar o lo que lógicamente un programador podría esperar, como ocurre por ejemplo en un objeto:

var car = {
  color: "red",
  wheels: ['RF', 'LF', 'RR', 'LR'],
  printWheels: function () {
    this.wheels.forEach(function (wheel) {
      console.log(wheel + ' in a ' + this.color + ' car');
    });
  },
};
car.printWheels();
//this.color es undefined. Si anadimos:
this.color = "blue"
car.printWheels()
//RF in a blue car
//LF in a blue car
//RR in a blue car
//LR in a blue car

Si, es una de esas extravagancias de javascript, como tantas otras, que los programadores han resulto de forma imaginativa en versiones anteriores, por ejemplo creando una variable self a la que se asigna this. De forma que el scope en el momento de definir el objeto esté disponible al ejecutarlo.

Veamos ahora como this se refiere al contexto en el que se encuentra, usando arrow functions.

var car = {
  color: "red",
  wheels: ['RF', 'LF', 'RR', 'LR'],
  printWheels() {
    this.wheels.forEach((wheel) => {
      console.log(wheel + ' in a ' + this.color + ' car');
    });
  }
};
car.printWheels();
//RF in a red car
//LF in a red car
//RR in a red car
//LR in a red car

Spread Operator

Este operador, usado antes de un array, sirve para extenderlo, por ejemplo para incluir los elementos de un array dentro de otro sin aumentar las dimensiones, o para usar los elementos de un array como argumentos de una función. Siempre es mejor ver el código:

//crea un array unidimensional
var animals = ['owl', 'camel', 'pinguin', 'otter', 'beaver', 'ox'];
var flowers = ['Water lily', 'Hibiscus', 'Bird of paradise'];
var things = ['car', ...animals, ...flowers, 'computer'];
console.log(things);
//pasar argumentos de funciones
var numbers = [2, 6, 9, 10];
console.log(Math.min(...numbers));
//otro ejemplo
var sumArgs = (a, b, c) => {
  return a + b + c
};
console.log(sumArgs(...numbers));

Destructuring assignment

Nos ayuda a asignar los elementos de un array a variables rápidamente.

//ES5
var myArray = ['pear','apple','strawberry'];
console.log(myArray[0]);

//usando asignación por destructuring:
var [pera, manzana, fresa] = myArray;
console.log(pera);
console.log(manzana);
console.log(fresa);

//otros ejemplos
var[primero, ...elResto] = myArray;
var[primero,,tercero] = myArray;

//Con objetos también funciona:
var post = {
  author : 'Pepe',
  title : 'Ecmascript 6'
};
var {author, title} = post;
console.log(author);
console.log(title);

En este caso hay que tener en cuenta que en lugar de “[” hemos usado “{“.

Ademas como nombre de las variables no podemos poner cualquier cosa, sino el nombre de la propiedad que queremos recuperar, a no ser que lo especifiquemos :

var {author: customName, title} = post;

Aquí, customName es el nombre que queremos darle, y author es el nombre de la propiedad del objeto post que queremos recuperar.

Y hasta aquí he llegado en este post, por supuesto hay muchísimo mas, espero tratar otros asuntos de ES6 en próximamente.

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.

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.

Domain Driven Design

Domain Driven Design book by Eric Evans

 

No sabia que cuando en la oficina he dicho “Al final de lo que se trata es de que el cliente sea feliz y de que nosotros los programadores también lo seamos” lo que estoy haciendo es promover Domain Driven Design.

Microservices, Self contained systems, Domain driven design y otros en el fondo lo que pretenden es que todos los actores de un dominio particular extremen su rendimiento y felicidad. La empresa que necesita una solución de software de calidad obtendrá un beneficio a medio plazo. Los programadores diseñarán paso a paso una aplicación en la que pueden confiar y los managers dejarán de arrancarse el pelo.

Divide y vencerás

Cuando observo un poco mas de cerca estas metodologías tengo la impresión de que todas se parecen demasiado, aun con sus grandes diferencias. Es como si todas ellas emanaran de la misma fuente: La necesidad de separar las responsabilidades a todos los niveles.

El principio SR (Single responsability) impregna todo buen comienzo en los cuentos de software. Al dedicar tiempo a definir:

  • el modelo de tu dominio
  • los objetos que lo habitan
  • sus necesidades
  • los datos que circulan y su forma de transmitirse y relacionarse
  • los servicios y la infraestructura que existe en el mundo representado e inmaterial del software

lo que haces es una vez más dividir.

Cuando la aplicaciones comenzaron a crecer exponencialmente y los pioneros de la profesión notaban que perdían el control y se les ponían de corbata, se hicieron necesarias normas que ayudaran a organizar sistemas complejos.

Hablemos

Al parecer había un abismo entre los equipos que desarrollaban el software y aquellos que llevan a cabo la “principal” actividad empresarial. Vamos, lo que todo el mundo identificaría como la fuente de beneficios. ¿Y el software entonces, qué es?. Lo que promueve el DDD en los equipos de trabajo es llegar a desarrollar un idioma conjunto, usado tanto por los expertos de dominio como por los desarrolladores. Esto quiere decir muchas cosas, por ejemplo tu variable $hash2 que solo tu sabes lo que es se convertirá en $bookings. Como ves la variable $bookings es significativa tanto para los expertos de dominio, como para los desarrolladores de software. Para conseguir eso muchas horas de común conversación habrán tenido lugar en la sala de reuniones. Cuando todo el mundo comparte los mismos conceptos, la misma semántica de dominio: hemos alcanzado el nirvana del DDD y nuestro software debe reflejarlo.

De qué trata

De modelar tu software siguiendo una hermenéutica de dominio. Lo cual quiere decir que una vez conocemos el mundo que debe representar nuestro software, resolverlo usando DDD, nos provee un marco en el que trabajar con garantías de que ese mundo no se convertirá en algo incomprensible, monolítico e imposible de verificar: Spaguetti code.

Crear sistemas complejos basados en software de alta calidad. DDD no es arquitectura. Descubrir poco a poco el dominio, interactuar con los expertos, generar poco a poco el Ubiquitous language común a todos, es aportar valor y conocimiento al negocio.

Show me the money!

 

Colofón

Como introducción de un novato en DDD, no deberías fiarte en absoluto de lo que digo sino leer el famoso libro de Eric Evans. Los sistemas complejos necesitan formas de construirse, de organizarse e integrarse, así como filosofías de trabajo y formas de entender la profesión. Veo un poco de todo eso en el DDD.

DDD provee un análisis sintáctico para la arquitectura de software.

 

Desde un punto de vista más técnico, para aplicar DDD a tu aplicación, además de mejorar la comunicación con el cliente y ser experto del dominio, hay que saber utilizar Domain Services, Value Objects, Modules, Aggregates, Entities, Domain Events, Factories o Repositories.