Docker Compose + Consul + Spring Boot + FeignClient

Este artigo mostra um exemplo de como elevar o ambiente de desenvolvimento local usando o Docker Compose, Consul, Make para o aplicativo Spring Boot (e não apenas), usando, por exemplo, PostgreSQL e Browserless.


Plano:


  1. Configurando serviços no Docker Compose
  2. Registro de serviços no Consul'e e adição de variáveis ​​ao repositório do Consul'a
  3. Criar Makefile
  4. Configuração do PostgreSQL
  5. Usando FeignClient
  6. Conclusão

O aplicativo é absolutamente inútil: após um link para uma página, ele retorna um link para a maior imagem desta página. A imagem será recuperada pelo Browserless, e no PostgreSQL este caso será salvo.


Link para o projeto: https://bitbucket.org/maximka777/consul-docker-spring-cloud/src/master/ .


1. Configurando serviços no Docker Compose


A primeira coisa a fazer é criar um arquivo de configuração docker-compose.yml para contêineres do docker:


 touch docker-compose.yml 

Este arquivo contém a versão do docker-compose:


 version: '3.4' 

Configuração de rede:


 networks: lan: 

E a configuração dos serviços necessários, neste caso Consul, Browserless e PostgreSQL:


 services: consul: image: consul:1.1.0 hostname: localhost networks: - lan ports: - 8500:8500 postgres: image: postgres:11.0 hostname: localhost networks: - lan ports: - 5432:5432 environment: POSTGRES_PASSWORD: password POSTGRES_DB: example_app browserless: image: browserless/chrome hostname: localhost networks: - lan ports: - 3000:3000 

POSTGRES_PASSWORD - senha para o banco de dados do usuário por padrão postgres , POSTGRES_DB - banco de dados criado automaticamente no contêiner.


Para iniciar os serviços, você precisa executar o comando:


 docker-compose up 

Estamos aguardando o fim do carregamento de imagens de contêineres e do lançamento de contêineres. Para parar a execução de contêineres, use o docker-compose down . Após iniciar todos os contêineres, você pode ir para o endereço no navegador localhost:8500 - o cliente Web Consul deve abrir (Fig. 1).


Figura 1


Figura 1


2. Registro de serviços no Consul'e e adição de variáveis ​​ao armazenamento do Consul'a


Você pode registrar serviços no Consul enviando várias solicitações posteriores ao endereço do localhost:8500/v1/agent/service/register , por exemplo, usando curl.


Coloque todas as chamadas curl no script bash.


 #!/bin/bash curl -s -XPUT -d"{ \"Name\": \"postgres\", \"ID\": \"postgres\", \"Tags\": [ \"postgres\" ], \"Address\": \"localhost\", \"Port\": 5432, \"Check\": { \"Name\": \"PostgreSQL TCP on port 5432\", \"ID\": \"postgres\", \"Interval\": \"10s\", \"TCP\": \"postgres:5432\", \"Timeout\": \"1s\", \"Status\": \"passing\" } }" localhost:8500/v1/agent/service/register curl -s -XPUT -d"{ \"Name\": \"browserless\", \"ID\": \"browserless\", \"Tags\": [ \"browserless\" ], \"Address\": \"localhost\", \"Port\": 3000, \"Check\": { \"Name\": \"Browserless TCP on port 3000\", \"ID\": \"browserless\", \"Interval\": \"10s\", \"TCP\": \"browserless:3000\", \"Timeout\": \"1s\", \"Status\": \"passing\" } }" localhost:8500/v1/agent/service/register curl -s -XPUT -d"{ \"Name\": \"example.app\", \"ID\": \"example.app\", \"Tags\": [ \"example.app\" ], \"Address\": \"localhost\", \"Port\": 8080, \"Check\": { \"Name\": \"example.app HTTP on port 8080\", \"ID\": \"example.app\", \"Interval\": \"10s\", \"HTTP\": \"example.app:8080/actuator/health\", \"Timeout\": \"1s\", \"Status\": \"passing\" } }" localhost:8500/v1/agent/service/register 

chmod +x register-services.sh - para tornar o arquivo executável.


Após a execução do script, nosso PostgreSQSL e o Browserless aparecerão na lista de serviços registrados no Consule'e (Fig. 2).


Figura 2


Figura 2


A figura mostra que a verificação do PostgreSQL falha com um erro - (isso não afeta a essência) .


Adicione a configuração ao armazenamento de chave / valor do Consul. Crie a variável test.property no diretório example.app :


 curl --request PUT --data TEST \ localhost:8500/v1/kv/example.app/test.property 

Se houver muitas variáveis, é melhor usar um script bash.


3. Criando um Makefile


Para simplificar o lançamento de tudo isso, escreva Makefile`:


 docker_up: @docker-compose up -d consul_up: @./register-services.sh && \ ./register-variables.sh compile: @cd example.app && mvn package run: @cd example.app && java -jar target/example.app-1.0-SNAPSHOT.jar up: docker_up consul_up compile run down: @docker-compose down 

Aviso: Makefile usa um tipo especial de recuo!


O comando de make up iniciará todo o ambiente.


4. Configuração do PostgreSQL


Em seguida, um projeto básico do Spring Boot (Maven) foi gerado usando o inicializador do aplicativo Spring Boot https://start.spring.io/ .


As seguintes dependências foram adicionadas ao pom.xml :


  <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-config</artifactId> <version>2.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> <version>2.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> 

A partir dos nomes dos pacotes, fica claro por que eles são necessários.
Vamos escrever a configuração para o DataSource. No arquivo bootstrap.properties , descarte as configurações:


 spring.cloud.consul.host=localhost spring.cloud.consul.port=8500 spring.cloud.consul.config.enabled=true spring.cloud.consul.config.prefix= spring.cloud.consul.config.defaultContext=example.app spring.cloud.consul.discovery.register=false spring.cloud.service-registry.auto-registration.enabled=false spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults = false spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect spring.jpa.show-sql=false spring.jpa.hibernate.ddl-auto=create 

Em application.yml :


 example.app: db: name: 'example_app' feign: client: config: default: connectTimeout: 20000 readTimeout: 20000 loggerLevel: basic management: endpoint: health: show-details: always endpoints: web.exposure: include: '*' 

E a própria classe de configuração:


 @Configuration public class PersistenceConfiguration { @Value("${example.app.db.name}") private String databaseName; @Autowired private DiscoveryClient discoveryClient; @Bean @Primary public DataSource dataSource() { var postgresInstance = getPostgresInstance(); return DataSourceBuilder .create() .username("postgres") .password("password") .url(format("jdbc:postgresql://%s:%s/%s", postgresInstance.getHost(), postgresInstance.getPort(), databaseName)) .driverClassName("org.postgresql.Driver") .build(); } private ServiceInstance getPostgresInstance() { return discoveryClient.getInstances("postgres") .stream() .findFirst() .orElseThrow(() -> new IllegalStateException("Unable to discover a Postgres instance")); } } 

O método getPostgresInstance() obtém a primeira instância de serviço com a tag postgres registrada no Consul. O método dataSource() é o bean do DataSource.


Em seguida, declaramos um repositório com operações básicas na entidade Image , que armazena o endereço da página e o endereço da imagem:


 @Repository public interface ImageRepository extends JpaRepository<Image, Long> { } 

5. Usando FeignClient


Em seguida, soltaremos um script JS nos recursos, que extrairão a maior imagem da página.


 module.exports = async ({page, context}) => { const {url} = context; await page.goto(url); await page.evaluate(_ => { window.scrollBy(0, window.innerHeight); }); const data = await page._client.send('Page.getResourceTree') .then(tree => { return Array.from(tree.frameTree.resources) .filter(resource => resource.type === 'Image' && resource.url && resource.url.indexOf('.svg') == -1) .sort((a, b) => b.contentSize - a.contentSize)[0]; }); return { data, type: 'json' }; }; 

Defina a interface BlowserlessClient:


 @FeignClient("browserless") //   browserless      public interface BrowserlessClient { @PostMapping("/function") ImageInfo findLargestImage(LargestImageRequest request); //       Browserless'     class ImageInfo { private String url; public String getUrl() { return url; } } //  ,    Browserless  ,    class LargestImageRequest { private String code; private BrowserlessContext context; public LargestImageRequest(String code, BrowserlessContext context) { this.code = code; this.context = context; } public String getCode() { return code; } public BrowserlessContext getContext() { return context; } } //        class BrowserlessContext { private String url; public BrowserlessContext(String url) { this.url = url; } public String getUrl() { return url; } } } 

Método de serviço que solicita uma imagem e armazena no banco de dados:


 public Image findLargestImage(String url) { var browserlessContext = new BrowserlessContext(url); var largestImageRequest = new LargestImageRequest(getLargestImageScript, browserlessContext); var imageInfo = browserlessClient.findLargestImage(largestImageRequest); var image = new Image(); image.setSourceUrl(url); image.setImageUrl(imageInfo.getUrl()); return imageRepository.save(image); } 

Controlador de funcionalidade:


 public class MainController { private static Logger log = LoggerFactory.getLogger(MainController.class); @Autowired private ImageService imageService; @Value("${test.property}") private String testProperty; @GetMapping("/largest-image") public ResponseEntity<Image> getTitle(@RequestParam("url") String url) { return ResponseEntity.ok(imageService.findLargestImage(url)); } @GetMapping("/property") public ResponseEntity<String> getProperty() { return ResponseEntity.ok(testProperty); } } 

Aqui, o campo testProperty obtido do armazenamento de chave / valor do testProperty .


6. O fim


Isso é tudo!


Espero ter conseguido mostrar uma possível configuração das ferramentas apresentadas e este artigo será útil para alguém.


Não há muitas explicações neste artigo, pois acredito que, neste caso, é mais fácil entender o código.


Links úteis:
1) https://docs.docker.com/compose/overview/ - documentação do Docker Compose
2) https://www.consul.io/intro/index.html - introdução ao Consul
3) http://matt.might.net/articles/intro-to-make/ - introdução ao make
4) https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html - documentação sobre Feign

Source: https://habr.com/ru/post/pt429472/


All Articles