[PT-BR] Desacoplando módulos JPMS
Published: 2020-09-04 13:27:39

Quando estamos desenvolvendo um software, temos sempre que trabalhar para ter um menor acoplamento entre nossos componentes. Alguns padrões *(patterns)* foram pensados para nos auxiliar nessa missão. Neste post será apresentado uma aplicação desenvolvida com **JPMS (Java Platform Module System)**, onde aplicou-se o *Service Pattern* para desacoplamento dos módulos. A ideia é explicar com detalhes sobre o que foi feito.

#A aplicação
A ideia do projeto desenvolvido é que o usuário possa selecionar uma fonte de dados para gravar algum objeto. Essas fontes podem ser bancos de dados, arquivos ou qualquer outra que cumpra o requisito. Deve-se inserir novas fontes de dados sempre que necessário. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lgh2dsu9qiekd5zk5ij3.png)

Na imagem acima, observa-se um módulo `br.com.fontededados.application` que consome (*consumer*) informações dos módulos `br.com.fontededados.mysql` e `br.com.fontededados.arquivo` (*producers*). Porém, desta maneira o consumidor irá conhecer detalhes da implementação dos produtores e quando for necessário incluir um novo produtor, será necessário alterar o consumidor. Para tratar o alto acoplamento entre os módulos, foi utilizado o *Service Pattern*. Este padrão utiliza uma api entre *consumer* e *producers*, fazendo com que um não saiba da existência do outro, ocasionando desacoplamento dos módulos e maior flexibilidade na inserção de novas fontes de dados.  

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ezas9b3ipfc88kdbfg6v.png)

Na próxima seção será mostrado o código desenvolvido para obter este resultado. 

#Vamos ao código
A aplicação possui o módulo `br.com.fontededados.api` que tem uma interface `FonteDeDados` e o `module-info`. O conteúdo da interface pode ser visto abaixo:
```java
public interface FonteDeDados {

    public void gravar();
    
    public String getNome();
}
```
E o `module-info`: 
```
module br.com.fontededados.api {
    exports br.com.fontededados.api;
}
```
A interface faz parte do pacote `br.com.fontededados.api` e é exportada para outros módulos. 
 
Para o exemplo do post, será utilizado um *consumer CLI (Command Line Interface)*. Ele faz parte do módulo `br.com.fontededados.application`, possui um arquivo `module-info` e uma classe `FonteDeDadosCLI`. A seguir o conteúdo da classe:
```java
// Pacote e imports ocultos
public class FonteDeDadosCLI {

    public static void main(String[] args) {
        System.out.println(listarNomes());
    }

    public static List<String> listarNomes() {
        List<String> nomes = new ArrayList<>();
        ServiceLoader<FonteDeDados> fontes = carregar();
        if(fontes != null) { 
            fontes.forEach(fonte -> nomes.add(fonte.getNome()));
        return Collections.unmodifiableList(nomes);
        } return nomes;
    }

    public static ServiceLoader<FonteDeDados> carregar() {
        return ServiceLoader.load(FonteDeDados.class);
    }
}
```
Para entender o que o código faz, é necessário entender o que é `ServiceLoader`.

>O Service Loader é um facilitador no carregamento de provedores de serviço. Um provedor de serviço é composto por interface ou classe abstrata (*Service Provider Interface - SPI*) e implementação (*Service Provider*). A classe está na plataforma desde o Java 6, porém foram realizadas mudanças para facilitar o trabalho com *JPMS*. 

Ao chamar o método `carregar()`, cabe o método `load` retornar uma instância do `ServiceLoader`. Caso a instância não esteja nula, é porque existe pelo menos uma classe concreta (*Service Provider*) que implementa a interface (*SPI*) `FonteDeDados`.

O método `listarNomes()` é responsável por iterar sobre a instância de `ServiceLoader` e criar instâncias das classes que possuem implementação de `FonteDeDados`. Como são objetos Java, pode-se chamar o método `getNome()` de cada uma das instâncias, com o objetivo de popular uma lista de *Strings*, que representarão as fontes de dados disponíveis para gravação do objeto.

A seguir será explorado o conteúdo do `module-info`:
```
module br.com.fontededados.application {
    requires br.com.fontededados.api;

    uses br.com.fontededados.api.FonteDeDados;
}
```
A palavra reservada `uses` instrui o `ServiceLoader` que o módulo `br.com.fontededados.application` deseja usar implementações de `FonteDeDados`.

Para compilar os módulos acima, pode-se usar o comando 

*javac -d mods --module-source-path src -m br.com.fontededados.application*

Um diretório *mods* será criado e possuirá os bytecodes de `br.com.fontededados.application` e `br.com.fontededados.api`.

Pode-se executar a aplicação com o comando abaixo:

*java --module-path mods -m br.com.fontededados.application/br.com.fontededados.application.FonteDeDadosCLI*

O resultado será um `[]`, pois não há implementações de `FonteDeDados`.

O próximo passo será mostrar o *producer*. O módulo `br.com.fontededados.mysql` possui uma classe chamada `FonteDeDadosMySQL` e um `module-info`. A classe possui o seguinte conteúdo: 
```java
// Pacote e imports ocultos
public class FonteDeDadosMySQL implements FonteDeDados {

    public void gravar() {
        System.out.println("Implementação do método gravar com MySQL");
    }

    public String getNome() {
        return "mysql";
    }
}
```

*O objetivo não é mostrar possíveis implementações do método gravar(..)* 

A grande questão é que apenas implementando a interface `FonteDeDados` não é o suficiente para o `ServiceLoader` carregá-la. É necessário utilizar um recurso no `module-info` que será mostrado a seguir:
```
module br.com.fontededados.mysql {
    requires br.com.fontededados.api;

    provides br.com.fontededados.api.FonteDeDados
        with br.com.fontededados.mysql.FonteDeDadosMySQL;
}
```
O `provides/with` é justamente o responsável por declarar que o módulo em questão provê uma implementação da interface `FonteDeDados` **(SPI)** com a classe `FonteDeDadosMySQL` **(Service Provider)** e assim poderá ser carregado pelo `ServiceLoader`. 

*É importante destacar que só será possível usar o `provides` se a interface em questão estiver no mesmo módulo que a está declarando ou em um módulo que seja possível usar o `requires`.*

Para finalizar,  o módulo será compilado e colocado no mesmo `module-path` dos módulos anteriores.

*javac -d mods --module-source-path src -m br.com.fontededados.mysql* 

Pode-se executar o projeto com o comando abaixo: 

*java --module-path mods -m br.com.fontededados.application/br.com.fontededados.application.FonteDeDadosCLI*

E agora o resultado será:

*[mysql]*

Percebe-se que para o correto funcionamento não foi necessário recompilar todos módulos, compilando apenas o *provider*, o projeto funcionou como esperado.  

É importante reforçar que os detalhes das implementações dos *providers* não estão sendo expostos. Quando as instâncias forem criadas em *application*, o módulo saberá o que a instância faz, mas não como faz.   

Revisando o diagrama, tem-se este resultado: 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wbbg76r1rl7o72k786r3.png) 
        
#Concluindo...
O Service Pattern trabalha com *producers* e *consumers*. O *Service Provider* são classes que provêm implementações para Service Provider Interface (*SPI's*). Um ou mais *consumers* poderão usar essas implementações, graças ao ServiceLoader, que as instanciam. É importante destacar que o consumidor sabe o que a instância faz, mas não como ela faz e assim consegue-se o desacoplamento tão desejado. No exemplo foi visto a aplicação do pattern de uma forma simples e espero que a ideia tenha sido compreendida. Se ficou alguma dúvida, estarei aberto a esclarece-las.