Criando proxy de APIs com Spring cloud, Zuul e Eureka

A partir do momento que passamos a usar uma abordagem de microserviços ou da separação do código monolito, começamos a enfrentar alguns desafios por lidarmos com diversas aplicações, um deles é trabalhar com as diferentes URLs das aplicações distribuídas, onde conforme a necessidade deve ser direcionado para aplicação correta.

Uma das formas de minimizar esse desafio é utilizando um proxy, onde todas chamadas são centralizadas em um único local e a solução de proxy faz o direcionamento para aplicação correta, com isso, utilizando Spring Cloud em conjunto tecnologias da Netflix (Zuul e Eureka), é possível configurar de forma simples toda essa infraestrutura para aplicações Spring Boot.

Spring Cloud: é um conjunto de ferramentas que prove umas série de padrões e frameworks para a acelerar a construções de aplicações distribuídas.

Zuul: é uma solução de roteamento dinâmico que possibilita monitoramento, resiliência e segurança para aplicações, que também pode ser encontrada no sub-projeto spring-cloud-netflix.

Eureka: é uma solução de service discovery, que em conjunto com outras ferramentas possibilita gerenciamento dinâmico e escalabilidade para as aplicações, o Eureka também faz pode ser encontrado no sub-projeto spring-cloud-netflix.

Como comentado anteriormente, o Zuul e Eureka fazem parte do projeto spring-cloud-netflix, com isso, já uma grande integração nativa das soluções em conjunto com o Spring Cloud e Spring Boot.

Funcionamento

O funcionamento é dividido em três papéis:

  • Spring Cloud: Responsável por integrar todas as soluções em aplicações Spring Boot, onde toda configuração é feita através de anotações e propriedades do application.properties;
  • Eureka:Rresponsável por registar as aplicações através de apelidos, o que torna todo roteamento dinâmico;
  • Zuul: Responsável por ser a porta de entrada das chamadas e direcionar as chamadas para as aplicações registradas no Eureka.

Dessa forma, as chamadas podem ser centralizadas em um único local (no Zuul) através do endereço /api, o qual traça a rota para as APIs internas (/api1 e /ap2), encapsulando os reais endereços das aplicações, o que possibilita maior segurança por não ter acesso direto aos serviços, centralização de URLs, onde a aplicações tem uma única porta de entrada e a estrutura pronta para balanciamento de carga, onde do Zuul e o Eureka podem suportar esse papel em conjunto com outras soluções, como Ribbon.

Exemplo

Colocando em prática o que foi comentado anteriormente, vamos implementar o cenário ilustrador anteriormente.

Como estamos utilizando Spring Cloud Netflix, suas dependências disponibilizam anotações para que aplicações Spring Boot se tornem servidores das soluções da Netflix, assim vamos configurar o Eureka e Zuul em projetos Spring Boot para facilitar a construção do ambiente.

Eureka

O Eureka é executado a partir de uma simples aplicação Spring Boot, que quando executado, sobe a instância do Eureka server.

A única dependência a ser adicionada para configurar o Eureka é a spring-cloud-netflix-eureka-server, que já carrega outras dependências necessárias para inicializar o Spring Boot no projeto (spring-boot-starter).
pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-eureka-server</artifactId>
</dependency>

Para tornar a aplicação um Eureka Server basta adicionar a anotação @EnableEurekaServer em conjunto com a anotação do Spring Boot (SpringBootApplication) que no startup da aplicação será inicializado o Eureka.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaAppConfig {

    public static void main(String[] args) {
        SpringApplication.run(EurekaAppConfig.class, args);
    }

}

E por fim, algumas configurações precisam ser feitas nos arquivos de configuração do Spring Boot (application.properties).

server.port=8761

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
  • server.port: Define a porta que o Eureka vai estar disponível, por padrão o Eureka usa a porta 8761;
  • eureka.client.register-with-eureka: Indica que a instância do Eureka não precisa ser registrada, até mesmo porque ela é a responsável pelos registros, assim não precisa registrar ela mesmo;
  • eureka.client.fetch-registry: Indica que essa aplicação não precisa buscar informações de registro no Eureka, pelo mesmo motivo anterior.

Aplicações

São criadas duas aplicações de back-end para simular um cenário com contexto de APIs diferentes, onde uma aplicação será responsável por listar os customers e outra para listar os products.

Primeira aplicação: Customers

As dependências adicionadas são: spring-boot-starter-web para fazer a disponibilização da API Rest e spring-cloud-starter-eureka para registrar a aplicação no Eureka.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

A classe de configuração é igual qualquer aplicação Spring Boot, apenas adicionando a anotação @EnableDiscoveryClient para fazer o registro da aplicação no Eureka.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class CustomersAppConfig {

    public static void main(String[] args) {
        SpringApplication.run(CustomersAppConfig.class, args);
    }

}

Observação: O registro no Eureka é realizado no startup da aplicação, onde a aplicação se registra no service discovery.

Para fins de demonstração, apenas é criado um serviço /customer no método GET, que retorna uma lista de customers.

import com.google.common.collect.ImmutableMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/customers")
public class CustomerController {

    @GetMapping
    public List findAll() {
        return ImmutableMap.of(
            1L, "Customer 1",
            2L, "Customer 2",
            3L, "Customer 3",
            4L, "Customer 4");
    }
}

E por fim, as configurações a serem feitas no application.properties, são: o host do Eureka e a definição de nome da aplicação para registro no Eureka.

server.port=8060

spring.application.name=customers
eureka.instance.hostname=localhost
    • server.port: A porta que a aplicação estará disponível;
    • spring.application.name: A identificação da aplicação que será registrada no Eureka;
    • eureka.instance.hostname: O hostname do servidor do Eureka.

Segundo aplicação: Products

A aplicação products segue o mesmo padrão da anterior, apenas mudando o controller que disponibiliza na API /products e as informações para registro no Eureka feitas no application.properties.

import com.google.common.collect.ImmutableMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/products")
public class ProductController {

    @GetMapping
    public List findAll() {
        return ImmutableMap.of(
            1L, "Product 1",
            2L, "Product 2",
            3L, "Product 3",
            4L, "Product 4");
    }
}
spring.application.name=products
eureka.instance.hostname=localhost
server.port=8070

Zuul

Por fim, a configuração do Zuul, que também é uma aplicação Spring boot.

As dependências do são: spring-cloud-starter-zuul para inicializar o Zuul na aplicação e a do Eureka para registrar a aplicação no service discovery.

    
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

A ativação do Zuul segue os padrões anteriores, apenas anotando com @EnableZuulProxy na classe de configuração do Spring Boot, e assim o Zuul é inicializado no startup da aplicação.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class ZuulAppConfig {

    public static void main(String[] args) {
        SpringApplication.run(ZuulAppConfig.class, args);
    }
}

As configurações também são as propriedades para definir porta e registar no Eureka, mas também as configurações do Zuul para mapear os caminho das APIs e qual seu destino.

A configuração das rotas do Zuul no application.properties segue o seguinte padrão: zuul.routes. + nome do serviço + . propriedade a ser configurada para aquele serviço. Essa configuração apenas é valida quando utilizado no Zuul no Spring Boot.

spring.application.name=zuul
eureka.instance.hostname=localhost
server.port=8080

zuul.prefix=/api
zuul.ignored-services=*
zuul.routes.customers.path=/customers/**
zuul.routes.customers.serviceId=customers
zuul.routes.customers.strip-prefix=false

zuul.routes.products.path=/products/**
zuul.routes.products.serviceId=products
zuul.routes.products.strip-prefix=false
  • zuul.prefix: Configura para que o contexto de entrada seja no /api, assim todos os serviços vão ser acessados pela URI /api;
  • zuul.ignored-services: Quando configurado como ‘*’, todos os serviços são ignorados por padrão, assim nenhum serviço vai ser acessado pelo Zuul, apenas vão estar disponível os que estiverem mapeados explícitamente como os de customers e products;
  • zuul.routes.customers.path: Define a URI para acessar os dados de customer, no caso /api/customers;
  • zuul.routes.customers.serviceId: Informa o ID da aplicação registrada no Eureka, que é o valor atribuído em cada aplicação na propriedade spring.application.name do application.properties;
  • zuul.routes.products.strip-prefix: Configurado com false, o prefixo do serviço configurado no path não tera nenhum efeito no path original da sua chamada, assim quando requisitado /api/customers será redirecionado para /customers.

Executando o cenário

Fazer o build do projeto rodando um mvn clean package na raiz do projeto.

mvn clean package

[INFO] Reactor Summary:
[INFO]
[INFO] proxy-apis ......................................... SUCCESS [  0.153 s]
[INFO] eureka ............................................. SUCCESS [  3.102 s]
[INFO] zuul ............................................... SUCCESS [  0.683 s]
[INFO] customers .......................................... SUCCESS [  0.431 s]
[INFO] products ........................................... SUCCESS [  0.560 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

Inicializar o Eureka, executando o mvn spring-boot:run dentro da pasta /eureka.

cd eureka
mvn spring-boot:run

s.b.c.e.t.TomcatEmbeddedServletContainer: Tomcat started on port(s): 8761 (http)
s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761
br.com.emmanuelneri.EurekaAppConfig     : Started EurekaAppConfig in 6.465 seconds (JVM running for 10.401)

Após o startup, o Eureka está disponível no endereço: http://localhost:8761/eureka, onde é sua tela administrativa.

Inicializar a API de customers, executando o mvn spring-boot:run dentro da pasta /customers.

cd customers
mvn spring-boot:run

com.netflix.discovery.DiscoveryClient    : DiscoveryClient_CUSTOMERS/192.168.11.247:customers:8060: registering service...
s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8060 (http)
s.c.n.e.s.EurekaAutoServiceRegistration  : Updating port to 8060
br.com.emmanuelneri.CustomersAppConfig   : Started CustomersAppConfig in 6.736 seconds (JVM running for 10.549)
com.netflix.discovery.DiscoveryClient    : DiscoveryClient_CUSTOMERS/192.168.11.247:customers:8060 - registration status: 204

Após o startup, o serviço de listar customers está disponível no endereço http://localhost:8060/customers e a aplicação está registrada no Eureka como demonstrado no log acima.

Inicializar a API de products, executando o mvn spring-boot:run dentro da pasta /products.

cd products
mvn spring-boot:run

com.netflix.discovery.DiscoveryClient    : DiscoveryClient_PRODUCTS/192.168.11.247:products:8070: registering service...
s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8070 (http)
s.c.n.e.s.EurekaAutoServiceRegistration  : Updating port to 8070
br.com.emmanuelneri.ProductsAppConfig    : Started ProductsAppConfig in 5.503 seconds (JVM running for 8.769)
com.netflix.discovery.DiscoveryClient    : DiscoveryClient_PRODUCTS/192.168.11.247:products:8070 - registration status: 204

Após o startup, o serviço de listar products está no endereço http://localhost:8070/products e a aplicação está registrada no Eureka.

Por fim, inicializar o Zuul para fazer o proxy dos serviços.

cd zuul
mvn spring-boot:run

com.netflix.discovery.DiscoveryClient    : DiscoveryClient_ZUUL/192.168.11.247:zuul:8080: registering service...
s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
s.c.n.e.s.EurekaAutoServiceRegistration  : Updating port to 8080
br.com.emmanuelneri.ZuulAppConfig        : Started ZuulAppConfig in 7.765 seconds (JVM running for 11.081)
com.netflix.discovery.DiscoveryClient    : DiscoveryClient_ZUUL/192.168.11.247:zuul:8080 - registration status: 204

Após o startup, as APIs de customers e products estão disponíveis nas URLs http://localhost:8080/api/customers e http://localhost:8080/api/products, assim as chamadas estão centralizadas em um único endereço, do Zuul, deixando transparente o acesso as APIs dos back-ends.

Conclusão

Com a adesão de microserviços ou de outras abordagens arquiteturais que tem como tendência a utilização de diversos serviços distribuídos, o Zuul é uma boa alternativa e de fácil implantação para realizar o proxy de APIs, e quando utilizado em conjunto com o Eureka oferecem o roteamento dos serviços de forma dinâmica, através do nome dos serviços registros no service discovery(Eureka), assim possibilitando maior flexibilidade na gestão dos serviços, além de preparar a estrutura para outras solução como balanciamento de carga (com o Ribbon) e circuit breaker (com hystrix).

O código fonte dos testes está disponível no github.

Anúncios

Um comentário sobre “Criando proxy de APIs com Spring cloud, Zuul e Eureka

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.