Docker Compose + Consul + Spring Boot + FeignClient

Cet article montre comment augmenter l'environnement de développement local à l'aide de Docker Compose, Consul, Make pour l'application Spring Boot (et pas seulement), en utilisant, par exemple, PostgreSQL et Browserless.


Plan:


  1. Configuration des services dans Docker Compose
  2. Enregistrement des services dans Consul'e et ajout de variables au référentiel Consul'a
  3. Créer un Makefile
  4. Configuration de PostgreSQL
  5. Utilisation de FeignClient
  6. Conclusion

L'application est absolument inutile: en suivant un lien vers une page, elle renvoie un lien vers la plus grande image de cette page. L'image sera récupérée par Browserless, et dans PostgreSQL ce cas sera enregistré.


Lien vers le projet: https://bitbucket.org/maximka777/consul-docker-spring-cloud/src/master/ .


1. Configuration des services dans Docker Compose


La première chose à faire est de créer un fichier de configuration docker-compose.yml pour les conteneurs docker:


 touch docker-compose.yml 

Ce fichier contient la version de docker-compose:


 version: '3.4' 

Configuration du réseau:


 networks: lan: 

Et la configuration des services nécessaires, dans ce cas Consul, Browserless et 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 - mot de passe de la base de données utilisateur par défaut postgres , POSTGRES_DB - base de données créée automatiquement dans le conteneur.


Pour démarrer les services dont vous avez besoin pour exécuter la commande:


 docker-compose up 

Nous attendons la fin du chargement des images de conteneurs et du lancement des conteneurs. Pour arrêter l'exécution des conteneurs, utilisez la commande docker-compose down . Après avoir démarré tous les conteneurs, vous pouvez vous rendre à l'adresse dans le navigateur localhost:8500 - le client Web Consul devrait s'ouvrir (Fig. 1).


Figure 1


Figure 1


2. Enregistrement des services dans Consul'e et ajout de variables au stockage de Consul'a


Vous pouvez enregistrer des services dans Consul en envoyant plusieurs post-requêtes à l'adresse de l' localhost:8500/v1/agent/service/register , par exemple, en utilisant curl.


Mettez tous les appels curl dans le 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 - pour rendre le fichier exécutable.


Une fois le script exécuté, nos PostgreSQSL et Browserless apparaîtront dans la liste des services enregistrés dans Consule'e (Fig. 2).


Figure 2


Figure 2


La figure montre que la vérification PostgreSQL échoue avec une erreur - (cela n'affectera pas l'essence) .


Ajoutez la configuration au stockage de clé / valeur de Consul. Créez la variable test.property dans le répertoire example.app :


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

S'il y a beaucoup de variables, il est préférable d'utiliser un script bash.


3. Création d'un Makefile


Pour simplifier le lancement de tout cela, écrivez 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 

Attention: Makefile utilise un type d'indentation spécial!


La commande make up démarrera tout l'environnement.


4. Configuration PostgreSQL


Ensuite, un projet Spring Boot de base (Maven) a été généré à l'aide de l'initialiseur de l'application Spring Boot https://start.spring.io/ .


Les dépendances suivantes ont été ajoutées à 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> 

D'après les noms des packages, il est clair pourquoi ils sont nécessaires.
Écrivons la configuration du DataSource. Dans le fichier bootstrap.properties , supprimez les configurations:


 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 

Dans 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: '*' 

Et la classe de configuration elle-même:


 @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")); } } 

La méthode getPostgresInstance() prend la première instance de service avec la balise postgres enregistrée dans Consul. La méthode dataSource() est le bean du DataSource.


Ensuite, nous déclarons un référentiel avec des opérations de base sur l'entité Image , qui stocke l'adresse de la page et l'adresse de l'image:


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

5. Utilisation de FeignClient


Ensuite, nous déposerons un script JS dans les ressources, ce qui extraira la plus grande image de la page.


 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' }; }; 

Définissez l'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éthode de service demandant une image et stockée dans la base de données:


 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); } 

Contrôleur de fonctionnalité:


 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); } } 

Ici, le champ testProperty extrait du magasin de clés / valeurs de testProperty .


6. La fin


Voilà!


J'espère avoir pu montrer une configuration possible des outils présentés et cet article sera utile à quelqu'un.


Il n'y a pas beaucoup d'explications dans cet article, car je pense que dans ce cas, il est plus facile de comprendre le code.


Liens utiles:
1) https://docs.docker.com/compose/overview/ - Documentation Docker Compose
2) https://www.consul.io/intro/index.html - introduction à Consul
3) http://matt.might.net/articles/intro-to-make/ - introduction à faire
4) https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html - documentation sur Feign

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


All Articles