Servicios: Invertir las dependencias utilizando el sistema de módulos

Hoy quiero mostrar un ejemplo del uso de servicios utilizando módulos. Esta característica está presente desde Java 9 y nos permite invertir la dirección de las dependencias dejando al sistema de módulos obtener las implementaciones que serán utilizadas y nuestro modulo consumidor solo tiene que interactuar con tipos abstractos.

Creando Servicios

Comenzamos con la siguiente figura:



El Consumidor solo tiene conocimiento de la interface Servicio, la cual utiliza en tiempo de ejecución llamando al método identificar en cada una de las implementaciones que recibe, en este caso serán ServicioA y ServicioB. Lo entretenido del sistema de módulos es que nos entrega esta funcionalidad, podemos crear los módulos que provean los servicios, agregarlos al module path y serán provistos a la clase consumidora. La siguiente imagen muestra los módulos que usaremos:
  


Si es necesario agregar mas servicios generamos nuevos módulos que implementan la interface Servicio y los dejamos en el module path.

Carga de los Servicios

El sistema de módulos se encarga de entregar las implementaciones del servicio al consumidor y lo hace a través de la clases ServiceLoader y Provider. Lo que tenemos que hacer para que puedan ser utilizadas nuestras Implementaciones es lo siguiente:

  • Exportar el paquete que contiene el tipo abstracto que representa al servicio (export), en nuestro caso la interface Servicio se encuentra en el paquete com.sebastian.servicios.port y luego indicamos con uses que la utilizaremos como un servicio:


  • En los módulos que implementan el servicio requerimos el módulo que contiene la interface e indicamos lo siguiente:
    provides com.sebastian.servicios.port.Servicio with com.sebastian.productor.a.ServicioA;
    
    • provides está acompañado del servicio que implementamos.
    • with indica cual es la clase que está entregando el servicio.
 




  • Para crear el servicio en los módulos proveedores hay que cumplir uno de los siguientes requisitos:
    • La clase tiene que tener un constructor público sin argumentos e implementar la interface del servicio.
    • Tener una clase factory con un método público, estático, sin parámetros que se llame provider que retorne la implementación del servicio.
  • finalmente la clase consumidora que desconoce los servicios que utilizará tiene el siguiente código:

    • Utiliza el método ServiceLoader::load indicándole cual es el servicio requerido (Servicio.class).
    • Llama al método Provider::get para obtener las implementaciones que encontró, las guarda en una lista y llama al método identificar en cada una de las referencias.
  • Al lanzar la aplicación obtenemos el siguiente resultado: 

compilar con:
mvn clean package
lanzar con:
java --module-path mods --module services.consumidor/com.sebastian.servicios.Consumidor
Conclusiones

  • tenemos disponible la inversión de las dependencias directamente desde el sistema de módulos sin requerir librerías adicionales
    • Sin la inversión de dependencias tenemos la siguiente imagen:

    • Acá el Consumidor conoce explícitamente cuales son sus dependencias (requires en el module-info para cada servicio)
    • Si hay que agregar una nueva implementación del servicio el Consumidor tiene que ser modificado
  • Desacoplamos el servicio de sus implementaciones
  • Se pueden modificar o reemplazar componentes sin interferir entre módulos

Eso es todo por hoy 😁, en GitHub está el código!

NOTA: El código en GitHub incluye interacción con servicios pre modulos ✌ (servicios C y D)

No hay comentarios.:

Publicar un comentario