Tutorial SPI (Service Provider Interface)

Para esse artigo escolhi um assunto que é um mistério para muita gente: SPI’s. Sim, eu falei SPI e não API. SPI’s são maneiras bastante eficazes de garantir baixo acoplamento entre consumidor/serviços. Por exemplo, qualquer um que já escreveu um código usando JDBC na vida já deve ter escrito aquela infame instrução: Class.forName(“um.nome.de.driver.jdbc”);.
Nos drivers JDBC mais recentes, os chamados “type 4”, nao é mais necessário escrever a linha acima para inicializar o driver, pois os mesmos já foram implementados usando o “service provider mechanism”. Traduzindo, os drivers atuais são uma implementação de SPI.

Na intenção de ser mais didático e convencer qualquer cético que venha a ler esse artigo, vou usar um exemplo fictício mas não muito longe da realidade.

O Problema

Cansado de ser pobre e almejando um lugar nas colunas sociais, vamos fazer de conta que você decidiu criar um site de vender medicamentos pela internet. Uma farmácia online. Aqui nasce então a “Drogaria Tabajara”.

Como toda farmácia nos teremos que comprar produtos de fornecedores, os laboratórios. São eles que investem milhões em pesquisa para conseguir fabricar as pílulas mágicas que nos dão alento em um momento de dor.

Com o contexto acima em mente, o desafio que vamos precisar resolver implementando uma SPI é o seguinte: Cada laborátorio se compromete a nos enviar uma lista de medicamentos do seu portfólio. Com pelo menos o preço e o nome de cada um. Seria ótimo então se pudéssemos por meio do nosso próprio sistema, o site da “Drogaria Tabajara”, ler essa lista de remédios que os fornecedores enviam e já lista-los para venda no site. Afinal de contas, se você for cadastrar todos os produtos “na mão”, você vai perder um precioso tempo que poderia estar usando para jogar GTA V.

A pedra no meio do caminho dessa solução é que cada fornecedor, cada laboratório, envia os dados em um formato diferente: Os mais esclarecidos usam um arquivo XML, outros enviam um simplesmente arquivo texto posicional, outros um arquivo CSV com os registros separados por ponto-e-vírgula, etc etc etc.
Resumindo, cada laboratório tem a sua lista de produtos em arquivos de texto, mas em formatos diferentes. E se você é esperto, já deve estar pensando na dor de cabeça que vai ser para o nosso sistema interpretar todos esses formatos que sabemos que existe, e outros de futuros fornecedores que ainda estão por vir e não temos idéia de como serão.

Antes de prosseguir, a imagem abaixo resume o que foi dito até aqui:

Overview

Por enquanto não vamos pensar em nenhum tipo de web service ou qualquer outra coisa high tech. A idéia é simplesmente receber por email o arquivo com os produtos de cada laboratório, e depois nós mesmos acessamos uma funcionalidade de administração do site Farmácia Tabajara para fazermos upload dos arquivos por lá:

overview3

A mágica tem que acontecer ao clicar o botão “processar” exibido acima. Um arquivo em qualquer um dos possíveis formatos pode ter sido escolhido para upload.

E Agora?

Independente das tecnologias que estão sendo utilizadas, JSF, Struts, REST ou seja o que for, algumas das soluções mais diretas para esse cenário poderiam ser:

  • Criar uma tela diferente, uma funcionalidade mesmo, para o envio de cada arquivo de cada fornecedor.
  • Usar uma única funcionalidade e na lógica de processamento fazer algum if-else from hell para identificar os diferentes formatos e poder processar de acordo.

A lógica de processar cada tipo de arquivo realmente vai ter que ser escrita. Disso não temos como fugir. Mas as soluções acima vão exigir que o nosso código estável e maravilhoso do Farmácia Tabajara seja alterado sempre que houver um novo fornecedor ou que os antigos decidam fazer qualquer tipo de alteração nos arquivos que já estão nos enviando! E se é fama e dinheiro que você busca com esse site, pode esperar que muitos novos fornecedores iriam aparecer, e de brinde novas alterações no sistema já em produção.

Não seria ótimo então se qualquer mudança de estrutura nos arquivos existentes, ou o surgimento de novos laboratórios não nos forçasse a alterar o código do nosso sistema? Sim, seria. E é por isso que nós, os iluminados, vamos usar uma SPI.

SPI To The Rescue

O que queremos é não ter mais que mexer no precioso código em produção do Farmácia Tabajara sempre que surgir um novo fornecedor. Como vou mostrar a seguir, usar uma SPI vai permitir que a lógica utilizada para interpretar os diferentes formatos de arquivo seja completamente desacoplada do nosso sistema. Quase que como mágica, o sistema vai saber exatamente qual “implementação” usar para cada tipo de arquivo recebido. Novas implementações serão adicionadas sem qualquer intervenção no código já existente.

Para ilustrar melhor, o que vamos ter é uma estrutura de código como mostra a imagem abaixo:

projetos

Temos quatro módulos distintos. Um deles é o próprio sistema da nossa farmácia, e os outros três fazem parte da solução que estamos implementando. A medida que novos fornecedores forem surgindo, uma nova implementação de “parser” terá que ser criada o que representaria na prática um módulo a mais nessa imagem acima. Observem que no módulo pfizer-parser existe um diretório META-INF/services. Essa é basicamente a única diferença entre um módulo utilitário normal e uma SPI. Mais detalhes a seguir. (obs: caso você esteja se perguntando o que é aquele arquivo pom.xml, é porque usei Maven para criar esses projetos. Sem ele a estrutura do código fonte pode ser um pouco diferente, mas não altera o foco desse artigo).

Sem mais blá-blá-blá, vamos entrar nos detalhes da implementação de cada módulo. Se achar mais conveniente, procure link no fim do artigo para fazer download do código fonte para que possa acompanhar.

Começando pelo módulo tabajara-api, que representaria somente “o contrato” que qualquer implementação de parser teria que usar. Aqui temos 2 classes e uma interface. Por enquanto vamos ignorar a classe ParserFactory e voltar nossas atenções para as outras duas:

tabajara-api

A classe Produto poderia muito bem ser uma entidade JPA. É somente um objeto com atributos e gets/sets, sem muito mistério. A interface Parser é o contrato que todas as implementações diferentes de parser vão ter que cumprir. O método parse() recebe o conteúdo do arquivo sendo “processado” e retorna uma lista de produtos, já que cada arquivo pode conter vários registros de medicamentos.

No geral o módulo tabajara-api se resume a isso. Os outros módulos que vou falar a seguir dependem dele, mas ele próprio não depende de ninguém.

As implementações concretas do serviço que queremos prover, os módulos roche-parser e pfizer-parser, são praticamente idênticos e por isso vou mostrar somente um aqui. Ambos possuem somente uma classe que implementa a interface Parser. Obviamente que a implementação que você vê abaixo é somente “faz de conta”, pois não faz parte da nossa discussão como ler um arquivo XML e transformar em objetos Produto. (dica: JAXB)

pfizer-parser

A novidade ai fica por conta do diretório META-INF/services que eu já havia mencionado anteriormente. Para que esse módulo seja de fato uma SPI é preciso criar um simples arquivo de texto dentro de META-INF/services cujo nome seja o nome completamente qualificado da interface que define o serviço que queremos prover, o nome da interface Parser. O conteúdo desse arquivo precisa ter somente o nome completamente qualificado da implementação que o módulo representa, ou seja, o nome da classe PfizerParser.

Quando devidamente compilado e empacotado em um arquivo .jar, a estrutura desse módulo ficaria assim:

pfizer-parser2

Por fim, como usaríamos isso tudo na ação do botão “Processar” lá no site da Farmácia Tabajara? É bom enfatizar mais uma vez que a lógica exibida aqui seria a mesma para qualquer tipo de aplicação, JSF, Swing, Struts, etc. Inclusive, tudo que fizemos até aqui foi exatamente para que essa lógica de processamento nunca tenha que ser alterada a medida que novos parsers sejam acrescentados.

processar

Só falta agora explicar o que é aquela chamada ParserFactory.createParsers(), que foi uma classe criada lá no módulo tabajara-api. Esse é o factory que por meio da classe ServiceLoader da API Java SE consegue descobrir quais são os serviços que estão disponíveis no classpath:

parserfactory

Pronto. Com isso conseguimos nosso objetivo, acomodar novos parsers sempre que surgir um novo fornecedor para nossa farmácia. Os módulos dos parsers podem ser implementados de maneira independente, e depois precisam somente ser adicionados ao classpath da aplicação principal, sem qualquer intervenção no seu código.

Uma SPI atende os casos mais simples de baixo acoplamento entre consumidor e provedor. Para soluções mais complexas a resposta talvez seja usar OSGI.

Baixe o código fonte mavenizado que foi utilizado nesse post, aqui.

Happy coding 🙂

2 thoughts on “Tutorial SPI (Service Provider Interface)

  1. Fantástico artigo!

    Estava lendo o tutorial da Oracle e o cara que escreveu aquele artigo deve ter raiva de leitor 🙂

    O seu artigo foi direto, simples e abrangente.

    Parabéns e obrigado!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s