jueves, 31 de mayo de 2018

Principio de inversión de dependencia (DIP) - Clean Architecture

El principio de inversión de dependencia o The Dependency Inversion Principle (DIP) (en inglés) establece que los sistemas más flexibles son aquellos cuyas dependencias de código son aquellas que dependen de abstracciones y no de implementaciones concretas.

Tanto en los lenguajes compilados como en los interpretados se debería hacer uso de las abstracciones de manera que se importasen sólo interfaces y clases abstractas, no se debería depender de ninguna abstracción. Esto es claramente algo imposible ya que, en algún momento, hay que importar la implementación de esa abstracción.


Abstracciones estables

Las interface abstractas son más estables que las implementaciones ya que un cambio en una interface implica un cambio en la implementación pero al revés no se cumple. Se puede cambiar una implementación pero esto no debería afectar a la interface. Por esta razón las interfaces son menos volátiles.

Los buenos arquitectos y diseñadores de software se centran en reducir la volatilidad de las interfaces. Tratan de encontrar la manera de añadir funcionalidad sin hacer cambios en las interfaces. Esto es el «Software Design 101».

La estrategia es utilizar interfaces abstractas estables en vez de implementaciones concretas que pueden cambiar fácilmente.

Hay una serie de prácticas para llevar a cabo este principio:

    • No hacer referencia a las implementaciones: hacer referencia a las interfaces abstractas. Generalmente se usan el patrón Abstract Factory para la creación de objetos.
    • No heredar de implementaciones volátiles: la herencia es un dependencia por lo que debería usarse con cuidado.
    • No sobrescribir funciones concretas: La implementación de funciones contienen dependencias a veces. Cuando se sobrescribe una función no se elimina estas dependencias si no que se heredan. Para administrar estas dependencias se debería hacer una función abstracta y crear varias implementaciones.
    • Nunca mencionar el nombre de nada concreto y volátil: esto es básicamente el principio.

Factorias

En cualquier lenguaje de programación la creación de un objeto crea un dependencia de código de la definición de ese objeto.

En la mayoría de los lenguajes orientados a objetos se debería usar el el patrón factoría abstracta o «Abstract Factory» para gestionar estas dependencias no deseadas.

El siguiente diagrama muestra cómo «Application» usa «ServiceImp» a través de la interfaz «Service». Sin embargo, «Application» debe crear una instancia de «ServiceImp» de alguna manera. Para lograr esto sin crear una dependencia de código «Application» hace una llamada al método «create» de la interfaz «ServiceFactory». Este método está implementado en la clase «ServiceFactoryImp» el cual implementa la interfaz «ServiceFactory». Esta implementación crea la instancia de «OrderImp» y la devuelve como un servicio.



Este diseño tiene un límite de arquitectura definido separando las abstracciones de sus implementaciones. Las dependencias, representadas por el sentido de las flechas, siempre van en la misma dirección, hacia el lado de la abstracción.

 «Application» utiliza las abstracciones (interfaces) que contiene todas las reglas de negocio. Las implementaciones contienen el código para las reglas de negocio.

El flujo de control pasa por «inyectar», o sea, pasar como parámetro una instancia de «ServiceFactoryImp» al constructor o a un método de «Application» el cual tenga definido en su implementación un parámetro de tipo «ServiceFactory». De esta manera «Application» opera de manera abstracta.

Implementación de componentes

Es imposible cumplir DIP ya que en algún momento hay que incluir la dependencia pero sí que se pueden reducir al mínimo estas y guardarlas separadas del resto del sistema, por ejemplo, en factorías.

Conclusión

Este principio será el más visible en la organización de la arquitectura.

Principio de segregación de la interfaz (ISP) - Clean Architecture

El Principio de segregación de interfaz o The Interface Segregation Principle (ISP) establece que los módulos deberían conocer sólo aquellas partes del sistema que necesitan y no lo que no usan.

Este principio aplica en aquellos casos donde hay un módulo con gran cantidad de funcionalidad donde se puede dividir, a través de la creación de nuevas interfaces más pequeñas, de manera que los distintos clientes sólo usen aquello que necesitan.

Por ejemplo, si tenemos varios usuarios y estos utilizan distintas operaciones de la clase «Ops» de manera que el «User1» utiliza sólo «op1», «User2» utiliza sólo «op2» y «User3» utiliza sólo «op3».



En este caso el código fuente de «User1» dependerá de los métodos «op2» y «op3» incluso si no los utiliza. Esta dependencia significa que un cambio en «op2» fuerce a «User1» a recompilar y redesplegar incluso sin llegar a utilizarlo.

Este problema se puede resolver si se segregan las operaciones por interfaz.

Si imaginamos que se utiliza un lenguaje estáticamente tipado, entonces el código de «User1» depende de una interfaz «U1Ops» y no de «Ops». De esta manera un cambio en «Ops» no afecta a «User1».

ISP y el lenguaje

Los lenguajes compilados dependen más de este tipo de principio ya que las dependencias de código pueden forzar a compilar y desplegar la aplicación.

En los lenguajes interpretados no existen estas dependencias de código que obliguen a un redespliegue.

Esta es una de las razones por las que los lenguajes dinámicos crean sistemas más flexibles y menos acoplados que los lenguajes compilados.

ISP y la arquitectura

En general no es una buena práctica depender de módulos que contienen más de los necesarios. Es más obvio cuando se fuerzan una nueva compilación y despliegue innecesarios.

En el nivel de arquitectura ocurre lo mismo. Si un sistema S depende un framework F y este a su vez depende de una base de datos DB, un fallo en DB podría causar que F y S dejen de funcionar.




Conclusión

La mejor estrategia es utilizar sólo aquello que se necesita para reducir el númer de errores inesperados.

miércoles, 30 de mayo de 2018

El principio de substitución de Liskov (LSP) - Clean Architecture


En 1988 Barbara Liskov estableció el Principio de Substitución de Liskov o LSP para la definición de subtipos

«What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.1»

Esto hace referencia a la herencia donde una clase A tiene dos subclases B y C y en cualquier parte del programa se puede usar instancias de las subclases B y C como si fueran una instancia de tipo A. Estos dos tipos son sustituibles por una instancia de clase A.

Por ejemplo, si tenemos una clase «Shape» con un método «getArea()» que devuelve el área y un componente «Draw» cuya funcionalidad es dibujar una figura y este hace una llamada a «Shape» para dibujar la figura. Si tenemos dos subclases «OvalShape» y «TriangleShape», que tienen dos maneras distintas de calcular el área, entonces cualquier instancia de estas subclases podría sustituir una instancia del tipo «Shape».




El problema de cuadrado rectángulo

Este problema es un ejemplo de una vulneración del principio ya que un cuadrado no es propiamente un subtipo de un rectángulo ya que los valores de altura y ancho cambian juntos en un cuadrado mientras que en el rectángulo se pueden cambiar por separado.

Como el comportamiento es distinto los tipos no son sustituibles.


LSP y la arquitectura

El principio LSP se ha convertido en un concepto más amplio del diseño de software que pertenece a interfaces e implementaciones.

El LSP es aplicable a cualquier situación donde haya usuarios que requieran de interfaces bien definidas y que la sustituibilidad de las implementaciones de estas interfaces.

La mejor manera de entender el principio desde el punto de vista de la arquitectura es ver qué ocurre cuando se vulnera el principio.


Vulneración del LSP

En el caso de tener una aplicación android que use una «API Restful» la cual tenga definido en su especificación el siguiente punto de entrada («endpoint»):

/setting/account/%s

Este «endpoint» tiene la información de la configuración de la cuenta de usuario.

Si más adelante se saca la versión iOs y esta tiene algunas opciones de configuración distinta por las necesidades propias de cada sistema operativo, se podría dar el caso de abordar la resolución con un «if» en el código del «endpoint»:

if (isAndroid()) { …

Esta estrategia puede llevar a un código oscuro y albergar errores. También se puede dar la situación de tener que contemplar la posibilidad de que surja otra nueva aplicación ya sea web o de escritorio que se comunique con la primera con lo que habría que añadir otro «if».

Un arquitecto podría evitar este tipo de estrategia creando un nuevo «endpoint» específico para esta u otras aplicaciones:

/setting/account/ios/%s
/setting/account/web/%s

La arquitectura ha tenido que añadir una solución más compleja ya que las «interfaces» de los servicios «restful» no son siempre sustituibles.


Conclusión

El LSP puede y debe añadir nuevos requisitos a nivel de arquitectura. La vulneración del principio de sustituibilidad puede introducir una cantidad de mecanismos significativa en la arquitectura del sistema.