Problème posé
Dans le chapitre précédent, nous avons utilisé Nacos pour la gestion des services et RestTemplate pour les appels inter-services. Cependant, le code d'appel distant écrit dans la section 6.5.3 Découverte et appel de services était trop complexe. De plus, cette méthode d'appel différait considérablement des appels de méthodes locales, entraînant une expérience de programmation incohérente, alternant entre appels distants et locaux.
Il est donc nécessaire de trouver une solution pour simplifier le développement des appels inter-services, afin que les appels distants soient aussi simples que les appels de méthodes locales. C'est là qu'intervient le composant OpenFeign.
Les points clés d'un appel inter-services sont au nombre de quatre :
- Méthode de requête
- Chemin de requête
- Paramètres de requête
- Type de retour
OpenFeign utilise les annotations de SpringMVC pour déclarer ces quatre paramètres. Ensuite, il utilise le proxy dynamique pour générer le code d'appel distant, nous évitant ainsi de l'écrire manuellement, ce qui est très pratique.
Démarrage rapide
Ajout des dépendances
Dans le fichier pom.xml du service cart-service, ajoutez les dépendances OpenFeign et le composant d'équilibrage de charge :
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Équilibreur de charge -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
Activation d'OpenFeign
Ensuite, ajoutez l'annotation @EnableFeignClients à la classe de démarrage CartApplication du service cart-service pour activer la fonctionnalité OpenFeign :
@EnableFeignClients
Création du client OpenFeign
Dans le service cart-service, définissez une interface ItemClient pour créer le client Feign :
package com.hmall.cart.client;
import com.hmall.cart.domain.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Collection;
import java.util.List;
@FeignClient("item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
@FeignClient("item-service"): Déclare le nom du service.@GetMapping: Déclare la méthode de requête.@GetMapping("/items"): Déclare le chemin de requête.@RequestParam("ids") Collection<Long> ids: Déclare les paramètres de requête.List<ItemDTO>: Type de retour.
Avec ces informations, OpenFeign utilise un proxy dynamique pour implémenter cette méthode. Il envoie une requête GET à http://item-service/items avec les ids comme paramètres, et convertit automatiquement le résultat en List<ItemDTO>.
Il suffit d'appeler cette méthode pour réaliser l'appel distant.
Utilisation du FeignClient
Dans la classe CartServiceImpl du service cart-service, modifiez le code pour appeler directement la méthode de ItemClient :
private final ItemClient itemClient;
private void handleCartItems(List<CartVO> vos) {
// 1. Obtenir les IDs des articles
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
// 2. Interroger les articles
List<ItemDTO> items = itemClient.queryItemByIds(itemIds); // Appel direct à la méthode de l'interface ItemClient
if (CollUtils.isEmpty(items)) {
return;
}
// 3. Convertir en map ID -> Article
Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
// 4. Mettre à jour les VOs
for (CartVO v : vos) {
ItemDTO item = itemMap.get(v.getItemId());
if (item == null) {
continue;
}
v.setNewPrice(item.getPrice());
v.setStatus(item.getStatus());
v.setStock(item.getStock());
}
}
OpenFeign s'occupe de la découverte de service, de l'équilibrage de charge et de l'envoi des requêtes HTTP. N'est-ce pas plus élégant ? De plus, nous n'avons plus besoin de RestTemplate, ce qui évite également son enregistrement.
Pool de connexions
OpenFeign utilise d'autres frameworks pour effectuer les requêtes HTTP sous-jacentes. Les implémentations de clients HTTP prises en charge incluent :
HttpURLConnection: Implémentation par défaut, ne supporte pas le pool de connexions.Apache HttpClient: Supporte le pool de connexions.OKHttp: Supporte le pool de connexions.
Par conséquent, nous utilisons généralement un client avec un pool de connexions pour remplacer le HttpURLConnection par défaut, par exemple OKHttp.
Ajout de la dépendance OKHttp
Dans le fichier pom.xml du service cart-service, ajoutez la dépendance OKHttp :
<!-- Dépendance OK http -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
Activation du pool de connexions
Dans le fichier de configuration application.yml du service cart-service, activez la fonctionnalité de pool de connexions de Feign :
# OKHttp
feign:
okhttp:
enabled: true
Redémarrez le service pour que le pool de connexions soit effectif.
Bonnes pratiques
À l'aveinr, nous souhaitons extraire la logique métier liée à la création de commandes dans un microservice indépendant : trade-service. Examinons d'abord la logique métier d'origine liée à la création de commandes dans le projet hm-service :
- L'entrée se trouve dans la méthode
createOrderdu contrôleurOrderController, qui appelle ensuite la méthodecreateOrderdeIOrderService. - Comme le frontend soumet les IDs des articles lors de la création de la commande, il est nécessaire de consulter les informations des articles pour calculer le prix total de la commande.
- Cela signifie que si le microservice de transaction (
trade-service) est séparé, il devra également appeler à distance la fonctionnalité de requête groupée d'articles par ID du serviceitem-service. Cette exigence est identique à celle du servicecart-service. - Par conséquent, nous devrons redéfinir l'interface
ItemClientdanstrade-service. N'est-ce pas dupliquer le code ? Existe-t-il un moyen d'éviter cette duplication ?
Analyse des solutions
La méthode pour éviter la duplication de code est l'extraction. Il existe deux approches d'extraction :
- Approche 1 : Extraire dans un module commun en dehors des microservices.
- Approche 2 : Chaque microservice extrait son propre module.
L'approche 1 est plus simple et la structure du projet est plus claire, mais elle présente l'inconvénient d'un couplage élevé dans l'ensemble du projet.
L'approche 2 est plus complexe à mettre en œuvre et la structure du projet est plus élaborée, mais le couplage entre les services est réduit.
Comme le service item-service est déjà créé et ne peut pas être divisé davantage, l'approche 1 sera utilisée ici.
Extraction du client Feign
Définissez un nouveau module nommé hm-api sous le projet parent :
Dépendances :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.heima</groupId>
<artifactId>hmall</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>hm-api</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Open Feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Load balancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Dépendance annotation Swagger -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.6</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
- Dans le
pom.xmlducart-service, les dépendancesopenFeignetload balancerpeuvent être commentées, car les autres modules référenceront le modulehm-api, ce qui équivaut à importer ces deux dépendances.
Copiez ItemDTO et ItemClient dans ce module. La structure finale sera la suivante :
Désormais, tout microservice souhaitant appeler une API du service item-service n'a qu'à importer la dépendance du module hm-api, sans avoir à écrire lui-même le client Feign.
Scan des packages
Dans le pom.xml du service cart-service, ajoutez le module hm-api :
<!-- Module Feign -->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-api</artifactId>
<version>1.0.0</version>
</dependency>
Supprimez l'ancien ItemDTO et ItemClient du cart-service, puis modifiez les références à ItemClient et ItemDTO dans CartServiceImpl :
import com.hmaall.api.client.ItemClient;
import com.hmaall.api.domain.dto.ItemDTO;
Après avoir redémarré CartApplication, vous rencontrerez une erreur :
Cela est dû au fait que ItemClient est maintenant défini dans le package com.hmall.api.client, tandis que la classe de démarrage de cart-service est définie dans le package com.hmall.cart. Par conséquent, ItemClient ne peut pas être trouvé, d'où l'erreur.
Ajoutez une déclaration à la classe de démarrage du service cart-service en utilisant l'une des deux méthodes suivantes :
- Méthode 1 : Déclarer les packages à scanner :
- Méthode 2 : Déclarer les FeignClients à utiliser :
Configuration des logs
Présentation
OpenFeign ne génère des logs que lorsque le niveau de journalisation du package contenant le FeignClient est défini sur DEBUG. Il existe 4 niveaux de journalisation :
NONE: Aucune information de journalisation n'est enregistrée. C'est la valeur par défaut.BASIC: Enregistre uniquement la méthode de requête, l'URL, le code d'état de la réponse et le temps d'exécution.HEADERS: En plus deBASIC, enregister également les informations d'en-tête de la requête et de la réponse.FULL: Enregistre tous les détails de la requête et de la réponse, y compris les en-têtes, le corps de la requête et les métadonnées.
Le niveau de journalisation par défaut de Feign est NONE, nous ne verrons donc pas les journaux de requête par défaut.
Définition du niveau de journalisation
Créez une nouvelle classe de configuration dans le module hm-api pour définir le niveau de journalisation de Feign :
Configuration
Pour que le niveau de journalisation soit effectif, deux méthodes d'utilisation existent :
-
Local : Configuré dans un
FeignClientspécifique, ne s'applique qu'à ceFeignClient. ```java@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
-
Global : Configuré dans
@EnableFeignClients, s'applique à tous lesFeignClient. ```java@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
Modifiez la classe de démarrage du service cart-service :
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
Utilisez l'interface du document pour appeler l'interface de requête de la liste des paniers, et les journaux suivants s'affichent :