Integrações
Para fazermos as requisições necessárias para integrações com outras APIs, utilizamos o pacote Feign, um cliente HTTP que nos permite fazer requisições a outros serviços de forma mais fácil que em outros pacotes.
Documentação oficial do Feign (em inglês)
Instalando
...
<!-- Core do pacote -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>11.8</version>
</dependency>
<!-- Módulo para utilizar o processador de JSON da Google -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>11.0</version>
</dependency>
<!-- Módulo para adicionar suporte para formulários do tipo
application/x-www-form-urlencoded e multipart/form-data -->
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.8.0</version>
</dependency>
...
Definindo requisições a uma API
Para definirmos uma Instância de API, definimos primeiro uma interface
contendo endpoints, body da requisição, se necessário, e retorno.
public interface ExemploAPI {
@RequestLine("GET /exemplos")
ResponseExemplos getExemplos();
@RequestLine("GET /exemplos/{id}")
ResponseExemplo getExemplo(@Param("id") Integer id);
@RequestLine("POST /exemplos")
@Headers("Content-Type: application/json")
ResponseExemplo sendExemplo(ObjetoEnvio corpo);
}
No código acima:
Linha 3 - É definido na anotação RequestLine
o método (GET) e o endpoint sem o domínio (/exemplos);
Linha 4 - Nome do método a ser chamado para executar a requisição (getExemplos) e o tipo de retorno da requisição (ResponseExemplos);
Linha 6 - É definido um endpoint com variável no path (/{id}). Deve ser passado entre chaves ({});
Linha 7 - Para ser passada a variável ao path, devemos inserir nos parâmetros do método com a anotação Param(nome definido na RequestLine)
.
Linha 10 - Podemos passar Headers necessários na anotação Headers
. Neste caso, estamos definindo o header Content-Type com o valor application/json.
Linha 11 - Definimos uma requisição como as outras, mas com método POST e enviando um objeto no body (corpo) da requisição. Para enviar um body, não é necessário passar nenhuma anotação.
Definindo uma instância
Uma instância é um objeto utilizado para realizar requisições com configurações já definidas, como headers ou encoders e decoders, por exemplo. Para construir uma, utilizamos Feign.builder()
, e inserimos configurações desejadas em forma de métodos:
- FeignService
@Service
public class FeignService {
// ...
private static final String EXEMPLO_API = "https://linkapi.com.br";
// ...
public ExemploAPI instanceExemploApi (String token) {
ExemploAPI exemploApi = Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.errorDecoder(new ExemploDecoder(log))
.requestInterceptor(new ForwardedForInterceptor.InterceptorBuilder()
.authorizationHeader(token)
.contentTypeJsonHeader()
.build())
.target(ExemploAPI.class, EXEMPLO_API);
}
}
Linha 4 - Definimos em uma constante a URL da api que desejamos integrar;
Linha 7 - Método que retorna a instância configurada;
Linha 9 e 10 - Definimos que o encoder e decoder (processadores de objetos json para requisição/resposta) será o GSON, processador
Linha 11 - Definimos um decoder customizado para erros, passando o service de Logs para o registro dos erros.
Linha 12 - Definimos um Interceptor (ver mais) para cada requisição. Neste caso, definimos um token de autorização, Content-Type como JSON, e finalmente, finalizamos a contrução com build().
Linha 16 - Por fim, definimos, no método target, a classe do objeto que contém as requisições e a URL definida na constante.
Utilizando
Para utilizarmos uma instância, criamos um service no caminho /services/api
, que conterá todos os métodos relacionados á integração:
@Service
public class ExemploAPIService {
@Autowired
private FeignService feignService;
public ExampleReturn enviarRequisicao() {
String token = "token"
try {
ResponseExemplo response = feignService.instanceExemploApi(token).getExemplo(3);
return response;
} catch (Exception e) {
// ... //
}
}
}
No exemplo acima, é passado um token de autorização à instância, e em seguida é chamado o método getExemplo
com o parâmetro ID, que buscará na API fictícia o exemplo com ID passado.
Obtendo o token de acesso
Para obter o token de acesso e passá-lo á instância, podemos chamar o método getToken
, localizado em OAuthService
. Devemos passar como parâmetro o tipo do token que desejamos pegar (ver na classe OAuthType).
Interceptor
O Interceptor customizado utilizado é construído utilizando métodos. Se precisarmos, por exemplo, criar um método que necessite de autorização e tenha o Content-Type como application/json
, construíriamos da seguinte forma:
new ForwardedForInterceptor.InterceptorBuilder()
.authorizationHeader(token)
.contentTypeJsonHeader()
.build() // Deve-se sempre terminar com build() para finalizar a construção.
Caso tenha mais algum header que deve ser passado frequentemente, pode-se adicionar à classe InterceptorBuilder
.
Adicionando métodos ao interceptor
Para adicionar novos métodos ao construtor do interceptor, deve-se seguir a seguinte estrutura:
// ...
public static class InterceptorBuilder() {
// ...
// Método deve retornar InterceptorBuilder
public InterceptorBuilder headerExemplo() {
/*
- "template" é o responsável por juntar os headers atribuídos para a
construção.
- No método 'header' passamos primeiro o nome do Header a ser atribuído, e
em seguida o valor, que pode ser passado como atributo se necessário.
*/
template.header("Custom-header", "exemplo");
return this;
}
// ...
}
// ...
Decoder de erros
Para cada instância, é possível inserir um errorDecoder
, uma classe responsável por padronizar e registrar os erros ocorridos em requisições. A estrutura básica é:
@Component
public class CelcoinAPIErrorDecoder implements ErrorDecoder {
// Service usado para registrar os erros
private LogService log;
public ExemploAPIErrorDecoder(LogService log) {
super();
this.log = log;
}
@Override
public Exception decode(String methodKey, Response response) {
HttpStatus responseStatus = HttpStatus.valueOf(response.status());
InputStream stream = null;
String message = null;
try {
// Tratamento e padronização do erro. Varia para cada API
} catch (IOException | JSONException e) {
message = "ERRO API_EXEMPLO, Status: " + responseStatus + " mensagem: Erro ao salvar mensagem";
}
log.insert(new Log(null, message, new Date()));
return new DataIntegrityException(message);
}
}
Seguindo esta seção, é possível estruturar uma nova integração. Cada API, porém, possui uma implementação e tratamento de erros diferente. É recomendado estudar a API e montar as requisições em uma plataforma de requisições, como o Postman antes de partir para a implementação.