La inversión de dependencias no es (sólo) lo que tú piensas

Ah, SOLID. Los principios SOLID. Esos principios, desconocidos por unos, sobrevalorados por otros, que se supone que deberían servirnos para desarrollar mejor software. Se ha escrito mucho sobre estos principios, pero a veces nos quedamos más con la implementación concreta (el patrón de diseño, la recetilla a aplicar) que con la idea subyacente.

Siempre he dicho que me gustan más los conceptos que los detalles, por eso en este post quiero profundizar sobre uno de los principios SOLID, el principio de inversión de dependencias, desde un punto de vista más general que el típico “cómo implementar inversión de dependencias en el framework X”.

Qué es la inversión de dependencias

Tradicionalmente, a la hora de definir las dependencias entre los módulos de un sistema, y especialmente si era un sistema basado en capas (esos N-Layer de infausto recuerdo), la dependencia entre los distintos componentes de un sistema era algo así:

n-layer-deps

… que en código se convirtía en una cosa como ésta:

// Assembly DataAccessLayer.dll
public class CustomerDAO {}

// Assembly BusinessLogicLayer.dll, que referencia DataAccessLayer.dll
using DataAccessLayer;
public class CustomerBO { 
    private CustomerDAO dao;
}

Avanzando unos años en el tiempo (del 2002 al 2007 más o menos) y aplicando el principio de inversión de dependencias, aunque no tengamos muy claro por qué ni para qué, lo que hacemos es invertir la dependencia entre el paquete que contiene la lógica de negocio y el paquete que contiene la lógica de persistencia.

Usando terminología actualizada, llegamos al siguiente grafo de dependencias:

n-layer-inverted-deps

Para cambiar la dirección de la dependencia entre la lógica de negocio y la lógica de persistencia, los pasos suelen ser siempre los mismos. Se definen una serie de interfaces en el paquete con la lógica de negocio y se implementan con clases declaradas en el paquete de persistencia.

Algo así:

// Assembly Domain.dll
public interface ICustomerRepository {}
public class CustomerService {
    private ICustomerRepository repository;
}

// Assemmbly Persistence.dll, que referencia Domain.dll
using Domain;
public class CustomerRepository : ICustomerRepository {}

Para hacer luego el montaje de todo esto, entra en juego un patrón muy relacionado con la inversión de dependencias: la inyección de dependencias que nos permite componer la aplicación (con contenedor o sin él) manteniendo las dependencias en la dirección adecuada.

Y ya está. Perfecto. Ya hemos aplicado el principio de inversión de dependencias, ya tenemos código SOLID-compliant, podemos sentirnos clean coders, comprarnos una pulsera que lo demuestre (en serio, esas cosas existen) y asunto resuelto.

Puede que suene exagerado, pero os aseguro que más de una vez al preguntar sobre este tema la respuesta que he obtenido es más o menos esa: «sí, hombre, inversión de dependencias… eso es lo de que defines el interfaz en el dominio y luego el repositorio en otro proyecto».

Qué es, en serio, la inversión de dependencias

Si colgarte la medalla de aplicar mecánicamente principios SOLID no es suficiente para ti y quieres saber algo más (enhorabuena, es un enfoque mucho mejor), podemos empezar por ver la definición original de inversión de dependencias que dio Robert C. Martin en el año 2003 en su libro “Agile Software Development: Principles, Patterns, and Practices”, la cual está compuesta por los siguientes dos puntos:

  • Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones.
  • Las abstracciones no deben depender de detalles, son los detalles los que deben depender de abstracciones.

En una traducción (un tanto libre, lo admito) a lenguaje coloquial podríamos resumirlo en:

Que las cosas importantes no dependan de cosas menos importantes.

Qué significa la inversión de dependencias

En lugar de quedarnos en la recetilla de cómo aplicar el principio de inversión de dependencias que veíamos antes, vamos a intentar profundizar en la idea subyacente, las implicaciones que tiene y ver qué más cosas podemos extraer de él.

¿Cuál es el motivo de depender de abstracciones? ¿Por qué no queremos que las abstracciones dependan de los detalles, sino que sea al revés? ¿Qué extraña razón nos impulsa a que el centro de nuestro sistema sean las abstracciones?

Hay varios motivos (y mucha literatura al respecto en internet), pero uno de los más importantes es la estabilidad de cada componente. No me refiero a la estabilidad en el sentido de que no contenga errores, sino la estabilidad en el tiempo. La menor frecuencia de cambio.

Las abstracciones que introducimos y de las que dependemos tienden a ser más estables en el tiempo precisamente porque las abstracciones capturan la esencia del sistema, y no los detalles accidentales. Puede que cambie el API usada para acceder al servicio de validación de tarjetas de crédito, pero el concepto “validar una tarjeta de crédito” es mucho más estable en el tiempo.

Al aplicar el principio de inversión de dependencias estamos haciendo que las cosas que cambian más (las implementaciones concretas) dependan de las cosas que cambian menos (las abstracciones), haciendo nuestro sistema más resistente a los cambios. Evitamos que los cambios en esas partes más inestables se propaguen hacia el resto del sistema, ya que el resto del sistema no depende de ellas.

Esta misma idea de hacer que las dependencias apunten hacia componentes más estables, podemos aplicarla no sólo entre componentes “técnicos”, como pueden ser una base de datos o un servicio web, sino también entre conceptos de nuestro modelo.

Un caso práctico sería a la hora de elegir aggregate roots. Imagina que tenemos una versión extremadamente simplificada del típico modelo de facturas:

invoice-model

Ahora, queremos que cada factura pueda llevar asociado un vale de regalo, para aquellos casos en que queremos fidelizar el cliente ofreciéndole un descuento. Por la forma en que hemos expresado el nuevo requisito, “que cada factura pueda llevar asociado”, parece que la solución lógica sería pasar un modelo así:

invoice-voucher-model

Éste es un modelo válido y que seguramente funcione bien en la mayoría de los casos pero, ¿es mejor o peor que este otro?

invoice-model-inverted

Si pensamos en el principio de inversión de dependencias, deberíamos depender siempre en la dirección de la estabilidad, es decir, las cosas menos estables depender de las más estables. ¿Qué es más probable que cambie en este caso, el concepto de factura o el sistema de fidelización? En general, el departamento de marketing cambiará de idea más rápido que la legislación sobre cómo emitir una factura, por lo que parece razonable utilizar este último modelo.

Al igual que hablamos del vale de regalo, podríamos hablar de los pagos asociados a la factura. ¿Es mejor que la factura tenga una dependencia sobre los pagos o que sean éstos los que dependen de la factura? Nunca hay una respuesta directa a este tipo de preguntas, y hay más factores a tener en cuenta (rendimiento al acceder al sistema de persistencia, transaccionalidad, casos de uso, etc.), pero uno de los factores que debemos (o al menos podemos) tener en cuenta es el principio de inversión de dependencias.

Si hacemos que las partes más centrales y estables del sistema empiecen a depender de toda nueva funcionalidad que va a apareciendo, estamos perdiendo estabilidad en áreas que necesitamos que sean seguras. Es la misma idea que usábamos con las capas, pero llevada a funcionalidades/conceptos/bounded-context/áreas/cómo-lo-quieras-llamar.

Esto presenta una ventaja adicional, y es que si evitamos que todas las dependencias emanen desde dos o tres clases «centrales» de nuestro sistema, podremos conseguir diseños menos acoplados y no caeremos en antipatrones como el God Object.

Resumen

Tener código SOLID-compliant para presumir con tus amigos en twitter es fácil. Sólo necesitas aprenderte unas cuantas técnicas para aplicar los principios SOLID y quedarás bien. Además, no nos engañemos, en general este tipo de técnicas pueden ayudarte a escribir mejor código, así que tampoco es malo que lo hagas.

Lo bueno de los prinpicios SOLID es que, pese al abuso que se hace últimamente de ellos, se derivan de unas ideas muy básicas que podemos aplicar en ámbitos más generales que los ejemplos típicos. Si intentas profundizar en estas ideas subyacentes, podrás comprender mejor los problemas que supone desarrollar software y estarás en condiciones de buscar mejores soluciones para estos problemas.

7 comentarios en “La inversión de dependencias no es (sólo) lo que tú piensas

  1. pregunton dijo:

    Desarrollar buen código de calidad es complicado, muchos conceptos implicados. Hay que tener una mente ágil para aplicarlos adecuadamente inconscientemente, a mi se me escapa.

    Buen artículo !!!

  2. Excelente post, me encantó. El detalle de explicar la evolución de la programación en capas para llegar a la inversión de dependencias me parece muy clarificador del “por qué” de las cosas, hay muchos programadores que hacen “desarrollo guiado por el CV” que tendrían que leer esto.

  3. Pingback: Desarrollando se vive mejor (IV): SoliD, contextos en JavaScript y novedades de .NET | el.abismo = de[null]

  4. Pingback: Los principios SOLID – The talking bit

Comentarios cerrados.