精通Spring Boot 3 : 13. Spring Cloud 与 Spring Boot (2)

HashiCorp Consul(哈希 icorp 的 Consul)

HashiCorp Consul(https://www.consul.io/)是一个服务网络平台,旨在管理不同环境(包括本地部署、多云和各种运行时)之间的安全连接。它充当服务发现、安全通信和网络自动化的中央控制平面。

以下是一些 Consul 的应用场景:

  • 微服务架构:轻松连接和管理微服务,支持动态服务发现和健康检查。
  • 多云和混合部署:确保不同云服务提供商与本地基础设施之间的服务连接一致。
  • API 网关和服务网格:实现安全的服务间通信,具备自动 TLS 加密和基于身份的授权等功能。
  • 网络自动化:根据服务变更自动配置和更新网络基础设施。

Consul 包含以下功能(以及其他功能):

  • 服务发现:通过 DNS、HTTP 或 gRPC 接口注册和查找服务。
  • 健康检查:监控服务状态并自动注销不健康的实例。
  • 安全通信:启用服务间加密,使用相互 TLS(mTLS)和基于身份的授权。
  • 键值存储:在 Consul 中安全地存储配置和机密信息。
  • 多数据中心支持:可以在多个数据中心或区域之间无缝扩展 Consul。
  • 服务网格集成:与现有的服务网格(如 Linkerd 或 Istio)进行整合。
  • API 网关:在 Consul 服务网格中管理服务的流量和访问控制。
  • 网络自动化:根据服务变更自动化网络基础设施的配置。

使用 Consul 的好处包括

  • 简化服务管理:集中管理服务发现、健康检查和安全。
  • 提高灵活性:借助多平台支持加快服务的部署和扩展。
  • 提升运营效率:自动化任务,获得对服务的全面视图。
  • 加强安全性:实施最小权限访问并启用安全通信。

使用 HashiCorp Consul 工具

在本节中,我们将使用 Docker 启动 Consul 服务器和 Consul 客户端。Consul 需要一种服务器-客户端架构,以提供分布式共识、高可用性、安全性和高效管理等功能,这些功能仅靠客户端无法实现。这种架构确保了在各种场景下服务发现和网络的可靠性、可扩展性和安全性。

要启动 Consul 服务器,请运行以下命令:

docker run \
    -d \
    -p 8500:8500 \
    -p 8600:8600/udp \
    --rm \
    --name=consul-server \
    consul:1.15.4 agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0

注意:撰写本文时,Consul 的版本是 1.15.4。您可以访问 Docker Hub 查看最新版本:
https://hub.docker.com/_/consul。

接下来,让我们找出客户需要用来连接服务器的地址。请执行以下命令:

docker exec consul-server consul members

此命令的输出是默认的 Docker IP 地址(通常是 172.17.0.2)。接下来,您可以启动客户端并使用这个 IP 地址:

docker run \
   --name=consul-client --rm -d \
   consul:1.15.4 agent -node=client-1 -retry-join=172.17.0.2

由于我们将使用 Consul 作为外部配置机制,因此需要添加一些键。您可以通过 Consul 的 REST API 或安装 Consul CLI 工具来与其交互 (
https://developer.hashicorp.com/consul/docs/install)。

以下命令会添加所需的键值对:

curl -X PUT -d 'admin' http://localhost:8500/v1/kv/config/users-service/db/username
curl -X PUT -d 'mysecretpassword' http://localhost:8500/v1/kv/config/users-service/db/password

需要特别注意的是,我们采用了一种非常特定的方式来添加这些键值对。我们遵循 Spring Cloud Consul 的约定,这要求使用这种语法:

config//
config/,/

因此,在这种情况下,我们将 users-service 作为应用程序名称(spring.application.name),db.username 和 db.password 作为属性。这些属性用于我们的数据库。

此外,请添加以下属性(我们在此使用 consul CLI):

consul kv put config/users-service/user/reportFormat PDF
consul kv put config/users-service/user/emailSubject 'Welcome to the Users Service!'
consul kv put config/users-service/user/emailFrom 'users@email.com'
consul kv put config/users-service/user/emailTemplate '
Thanks for choosing Users Service
We have a REST API that you can use to integrate with your Apps.
Thanks from the Users App team.'

此外,您可以通过在浏览器中输入 http://localhost:8500 来使用图 13-2 所示的 Web 界面。

图 13-2 HashiCorp Consul 网页用户界面 (http://localhost:8500)

如果你查看键值部分,你应该能看到如图 13-3 所示的 db.* 和 user.* 属性。

图 13-3 Consul 键值部分(config/users-service/user 属性)

现在,让我们来回顾一下 Spring Cloud Consul 技术及其如何帮助我们使用 HashiCorp Consul。

Spring Cloud Consul

Spring Cloud Consul 是一个库,能够无缝地将 Spring Boot 应用程序与 HashiCorp Consul 集成。它利用 Consul 的功能,简化了基于 Spring 构建的微服务架构中的服务发现、通信和配置管理。

Spring Cloud Consul 的一些主要特点包括

  • 简化的服务发现方式:
    • 使用 Consul 自动注册和发现 Spring Boot 应用。
    • 通过使用注释,可以在无需手动配置的情况下集成服务发现功能。
    • 支持 Spring Cloud LoadBalancer 实现智能路由,并通过 Ribbon 实现客户端负载均衡。
    • 与 Spring Cloud Gateway 集成,实现动态 API 网关路由。
  • 强大的配置管理系统:
    • 利用 Consul 的键值存储实现集中配置管理。
    • 使用 Spring 环境来访问存储在 Consul 中的配置值。
    • 动态更新所有服务的配置,无需单独进行重新部署。
  • 安全服务通信
    • 通过利用 Consul 的内置安全功能来启用强大的相互 TLS(mTLS)加密。
    • 强制实施基于身份的授权,以确保安全的访问控制。
  • 分布式控制总线
    • 利用 Consul 的事件在微服务环境中触发相应的操作。
    • 发送和接收事件以便于协调和通知。
  • 与其他 Spring Cloud 项目的整合:
    • 可以与其他 Spring Cloud 项目无缝集成,例如 Spring Cloud Netflix,以实现 Hystrix 弹性等高级功能。

使用 Spring Cloud Consul 的优势包括:

  • 减少开发时间:通过注解和自动配置,简化了服务发现和配置管理的过程。
  • 提升开发者体验:提供了一种熟悉的 Spring 方式来使用 Consul,从而降低学习曲线。
  • 提升的可靠性和可扩展性:利用 Consul 的高可用性和多数据中心支持,构建强大的微服务。
  • 提高安全性:在您的微服务架构中强制实施安全通信和授权。

本质上,Spring Cloud Consul 连接了 Spring Boot 应用程序与 Consul,使开发者能够轻松构建出弹性、可扩展且安全的微服务。

在用户应用中使用 Spring Cloud Consul

在本节中,我们将为用户应用添加 Spring Cloud Consul,并回顾服务发现功能和外部配置。使用 Consul 和 Spring Cloud Consul 等工具的外部配置为微服务架构带来了显著优势。它提高了灵活性、增强了安全性、改善了可维护性,并简化了扩展,从而促进了微服务生态系统的整体成功。

让我们从用户应用程序开始。本章的源代码位于 13-cloud/users 文件夹中。如果您想使用 Spring Initializr(https://start.spring.io)从头开始,请将 Group 字段设置为 com.apress,将 Artifact 和 Name 字段设置为 users,并将 JPA、PostgreSQL、Web、Validation、Actuator、Consul Configuration、Consul Discovery 和 Lombok 作为依赖项添加。其他所有设置保持默认。然后,生成并下载项目,解压缩后导入到您喜欢的 IDE 中。

让我们先打开 build.gradle 文件,参见列表 13-1。

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.2'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.hibernate.orm' version '6.1.7.Final'
    id 'org.graalvm.buildtools.native' version '0.9.20'
}
group = 'com.apress'
version = '0.0.1-SNAPSHOT'
java {
    sourceCompatibility = '17'
}
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
ext {
    set('springCloudVersion', "2023.0.0")
}
repositories {
    mavenCentral()
}
dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    // Consult
  implementation 'org.springframework.cloud:spring-cloud-starter-consul-config'
     implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
  runtimeOnly 'org.postgresql:postgresql'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    // Web
    implementation 'org.webjars:bootstrap:5.2.3'
    // Test
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
    useJUnitPlatform()
}
hibernate {
    enhancement {
        lazyInitialization true
        dirtyTracking true
        associationManagement true
    }
}

列表 13-1 的 build.gradle 文件

列表 13-1 显示我们正在使用 Maven Bom(构件清单),这使我们能够获取所需的 spring-cloud-dependencies,其中包括
spring-cloud-starter-consul-config(用于外部配置)和
spring-cloud-starter-consul-discovery(用于服务发现)。

接下来,创建或打开 UserProperties 类。请参阅第 13-2 号列表。

package com.apress.users.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "user")
public class UserProperties {
    private String reportFormat;
    private String emailSubject;
    private String emailFrom;
    private String emailTemplate;
}

列表 13-2 源代码:
src/main/java/com/apress/users/config/UsersProperties.java

列表 13-2 显示,我们将 UserProperties 类标记为@ConfigurationProperties,这意味着该值将在启动时进行绑定。这些值存储在外部存储中,在本例中是 Consul 服务器。因此,我们需要创建例如
config/users-service/user/reportFormat 这个键及其值 PDF。

接下来,打开或创建 application.yaml 文件。请参阅列表 13-3。

spring:
  application:
    name: users-service
  config:
    import: consul://
  datasource:
    url: jdbc:postgresql://localhost:5432/users_db?sslmode=disable
    username: ${db.username}
    password: ${db.password}
  jpa:
    generate-ddl: true
    show-sql: true
    hibernate:
      ddl-auto: update
info:
  developer:
    name: Felipe
    email: felipe@email.com
  api:
    version: 1.0
management:
  endpoints:
    web:
     exposure:
       include: health,info,event-config,shutdown,configprops,beans
  endpoint:
    configprops:
      show-values: always
    health:
      show-details: always
      status:
        order: events-down, fatal, down, out-of-service, unknown, up
    shutdown:
      enabled: true
  info:
    env:
      enabled: true
server:
  port: ${PORT:8080}

列表 13-3 源自
main/resources/application.yaml 文件。

应用程序的 YAML 文件包含以下内容:

  • 这个属性是必需的,因为配置基于命名约定,因此必须在此处设置名称。在这种情况下,我们将名称设置为 users-service。
  • spring.config.import:该属性告诉 Spring Boot 部分配置位于 Consul 服务器,因此其值设置为 consul://。
  • spring.datasource.username 和 spring.datasource.password:这两个属性分别对应 ${db.username} 和 ${db.password}。它们的值将分别从 config/users-service/db/username 和 config/users-service/db/password 中获取。
  • 默认情况下,/configpros 端点会隐藏所有属性的值,但我们将此键设置为始终显示属性,因为我们将要展示一个非常酷的配置属性功能。

我们之前已经介绍了列表 13-3 中的其他属性,因此在这里不再重复。

使用 Spring Consul 启动用户应用程序

要运行用户应用程序,我们需要先启动 PostgreSQL 数据库。请执行以下命令来启动 PostgreSQL 数据库:

docker run --name postgres --rm \
 -p 5432:5432 \
 --platform linux/amd64 \
 -e POSTGRES_PASSWORD=mysecretpassword \
 -e POSTGRES_USER=admin \
 -e POSTGRES_DB=users_db \
 -d postgres

数据库启动并运行后,您可以运行应用程序。将端口设置为 8091,可以通过您的 IDE(每个 IDE 都有设置环境变量的方式)或执行以下命令来完成:

PORT=8091 ./gradlew bootRun

一切应该正常运行!如果你查看 Consul UI 的服务部分(
http://localhost:8500/ui/dc1/services),你应该能看到列出的 users-service(下方显示“1 个实例”)。请参见图 13-4。


图 13-4 Consul UI 服务部分 (
http://localhost:8500/ui/dc1/services)

那么,发生了什么呢?因为我们添加了
spring-cloud-starter-consul-discovery 依赖,Spring Boot 和 Spring Cloud Consul 的自动配置会将这个服务自动注册到 HashiCorp Consul,从而使其他服务能够发现它。而且由于我们添加了
spring-cloud-starter-consul-config 依赖,并将 spring.config.import 键设置为 consul://,它会根据命名约定从 Consul Config 存储中获取所有属性,即 config/users-service/* 属性。因此,当它看到 db.username 时,值会从
consul://config/users-service/db/username 中获取;而当它看到 user.reportFormat(来自 UserProperties 类)时,值会从
consul://config/users-service/user/reportFormat 中获取。非常不错,对吧?

现在回到浏览器,如果你点击用户服务,你会看到类似于图 13-5 的内容。

图 13-5 Consul 用户界面显示 users-service-8091

Consul 通过 spring-boot-actuator 的端点(/actuator/health)来检查服务是否正常运行。

那么,如果你有多个用户应用实例,会发生什么呢?你可以打开另一个终端,使用以下命令来运行另一个实例:

PORT=8092 ./gradlew bootRun

如果你再次查看 Consul UI 的服务部分,你会看到它更新为如图 13-6 所示,用户服务下显示“2 个实例”。点击用户服务,你将看到列出的两个服务,如图 13-7 所示。


你在审查用户属性吗?

接下来,让我们通过 actuator 来查看我们的 UserProperties 类。请将浏览器指向
http://localhost:8091/actuator/configprops 这个端点。如图 13-8 所示,您应该能看到我们之前输入的值(在 Consul 中)。

图 13-8
http://localhost:8091/actuator/configprops

如果您需要在应用程序运行时更改任何属性,您需要重新启动应用程序。不过,使用 Spring Boot 和 Spring Cloud Consul 组合的一个优点是,Spring Boot 提供了 @RefreshScope 注解,允许您更改属性的值,Spring Boot 会自动重新创建带有此标记的 bean,并更新为新值。因此,例如,假设您有如下内容:

@Configuration
public class UserConfigurationExample {
   @Value("${message}")
   private String message;
   ...
}

此配置会查找
config/users-service/message,即使您在 Consul 中进行了更改,它也不会改变。这是因为它在应用程序启动时就已设置并使用。因此,如果您需要更改此行为并使用新的值集,则需要使用 @RefreshScope 注解:

@RefreshScope
@Configuration
public class UserConfigurationExample {
   @Value("${message}")
   private String message;
   ...
}

使用 YAML 格式代替键值对作为配置方式

如果你再次查看 application.yaml 文件(见列表 13-3),你可能会想知道是否可以在 Consul 中将所有内容格式化为 YAML。答案是可以的,你可以添加整个 YAML 块,而不是键值对。要启用此功能,你需要在 application.yaml 文件中将格式属性设置为 YAML:

spring:
  cloud:
      config:
        format: YAML

然后你需要添加一个数据键并设置所有的 YAML 配置。因此,你需要在 config/users-service 中添加 data 键,并将以下内容作为其值:

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/users_db?sslmode=disable
    username: admin
    password: mysecretpassword
  jpa:
    generate-ddl: true
    show-sql: true
    hibernate:
      ddl-auto: update
info:
  developer:
    name: Felipe
    email: felipe@email.com
  api:
    version: 1.0
management:
  endpoints:
    web:
     exposure:
       include: health,info,event-config,shutdown,configprops,beans
  endpoint:
    configprops:
      show-values: always
    health:
      show-details: always
      status:
        order: events-down, fatal, down, out-of-service, unknown, up
    shutdown:
      enabled: true
  info:
    env:
      enabled: true
user:
  reportFormat: PDF
  emailSubject: 'Welcome to the User Services'
  emailFrom: 'user@email.com'
  emailTemplate: 'Thanks for choosing Users Service'

现在我们正在将数据库的用户名和密码添加到 yaml(config/users-service/data 值)以及 user.*属性中。请参见图 13-9。

图 13-9 Consul UI 的键值部分显示了 config/users-service/data 键

您的 application.yaml 文件将包含列表 13-4 中所示的内容。

spring:
  application:
    name: users-service
  config:
    import: consul://
  cloud:
    config:
      format: YAML
server:
  port: ${PORT:8080}

列表 13-4 源文件:
src/main/resources/application.yaml

正如你所看到的,使用最小配置后,一切都已外部化。当然,你可以使用 Spring Profiles(例如测试、开发、生产等)进行调整,使其更加灵活可配置。

在我的复古应用程序中使用 Spring Cloud Consul

现在我们要为我的复古应用程序添加 Spring Cloud Consul 功能。源代码位于 13-cloud/myretro 文件夹中。我们将重用第 11 章的 Spring Boot Actuator 代码,并进行一些修改。如果您想从头开始使用 Spring Initializr(https://start.spring.io),请将 Group 字段设置为 com.apress,将 Artifact 和 Name 字段设置为 myretro,并添加 JPA、H2、Web、Validation、Actuator、Consul Configuration、Consul Discovery、OpenFeign 和 Lombok 作为依赖项。其他设置保持默认。然后,生成并下载项目,解压缩后导入到您喜欢的 IDE 中。

首先打开 build.gradle 文件,参见列表 13-5。

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.2'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.hibernate.orm' version '6.1.7.Final'
    id 'org.graalvm.buildtools.native' version '0.9.20'
}
group = 'com.apress'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
ext {
    set('springCloudVersion', "2023.0.0")
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    // Consul
    implementation 'org.springframework.cloud:spring-cloud-starter-consul-config'
    implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
    // OpenFeign
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    // Kubernetes
    implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-client-all'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}
tasks.named('test') {
    useJUnitPlatform()
}

列表 13-5 的 build.gradle 文件

列表 13-5 显示,我们在 build.gradle 中不仅添加了 spring-cloud-start-consul 依赖项,还添加了
spring-cloud-starter-openfeign 和
spring-cloud-starter-kubernetes-client-all 依赖项。我们将在本章最后一节讨论 Kubernetes 客户端,这将帮助我们理解为什么需要在这里包含它。

如果你还记得,My Retro App 有一种与 Users App 进行通信的方式。在第 12 章中,我们使用了 @HttpExchange 和 @GetExchange 注解,这些注解简化了与其他服务 API 连接的方式。这次我们将采用不同的方法。

在我的复古应用程序中使用 OpenFeign

Spring Cloud OpenFeign 是一个库,它通过声明性地定义带有 Spring MVC 注解的客户端接口,简化了对 RESTful 网络服务的调用。它会根据这些接口自动生成客户端,并处理诸如以下任务:

  • 基于如@GetMapping 和@PostMapping 注解的 HTTP 请求映射
  • 使用 Spring 的 HttpMessageConverters 进行参数的解码和编码
  • Feign 拦截器集成用于自定义请求和响应处理
  • Hystrix 故障容忍机制用于增强服务调用的弹性

以下是 Spring Cloud OpenFeign 的主要特点:

  • 基于声明式接口的客户端:编写接口,而非底层 HTTP 代码,以实现更清晰、更简洁的方式。
  • Spring MVC 注解:利用熟悉的 Spring MVC 注解,提供直观的语法和便捷的集成。
  • 默认的编码器和解码器支持:使用 Spring 的默认消息转换器,实现数据处理的无缝对接。
  • 支持 Feign 拦截器:集成自定义拦截器以实现额外的处理和日志记录。
  • Hystrix 集成:自动为可靠的服务调用启用 Hystrix 故障容错功能。
  • 负载均衡支持:与 Spring Cloud LoadBalancer 集成,提供智能的服务发现和路由功能。
  • 服务发现集成:可以与多种服务发现工具(如 Eureka 和 Consul)协同工作。
  • OAuth2 支持:简化与 OAuth2 保护服务的安全通信方式。
  • Micrometer 支持:通过 Micrometer 监控和收集 Feign 客户端的指标。
  • Spring Data 支持:可以直接使用 Spring Data 仓库与 Feign 客户端,方便地访问数据。
  • Spring @RefreshScope 支持:在应用程序运行时动态更新 Feign 客户端的配置。

Spring Cloud OpenFeign 的一些优势包括

  • 减少开发时间:声明式方法和注解使客户端开发变得更加简单。
  • 提高可维护性:代码更简洁,关注点分离更加清晰。
  • 提高可重用性:接口有助于代码的重用和更便捷的共享。
  • 自动化 Hystrix 弹性:内置的故障容错机制,确保服务通信的可靠性。
  • 无缝的 Spring 集成:充分利用熟悉的 Spring 概念和工具。

Spring Cloud OpenFeign 帮助开发者构建具有弹性、可维护性和可扩展性的微服务架构的 RESTful 客户端。如果您想了解更多信息,请访问
https://spring.io/projects/spring-cloud-openfeign。

我们也将使用 Consul 来支持 My Retro App,并且需要一种连接到 Users App 的方式。为此,我们将利用 OpenFeign 和 Consul 提供的服务发现和负载均衡功能。请注意,我们有两个正在运行的 Users Service App 实例;My Retro App 将同时连接这两个实例。

接下来,打开或创建 UserClient 接口。请参阅第 13-6 号列表。

package com.apress.myretro.client;
import com.apress.myretro.client.model.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "users-service")
public interface UserClient {
    @GetMapping("/users")
           ResponseEntity> getAllUsers();
           @GetMapping("/users/{email}")
           ResponseEntity getById(@PathVariable String email);
}

示例 13-6
src/main/java/com/apress/myretro/client/UserClient.java

让我们来回顾一下这些注释:

  • @FeignClient:这个注解用于传递服务名称,在这里是 users-service,这是在 Consul 中注册的服务。
  • 你已经了解这个注解,正如你所看到的,我们在 UsersController 中定义了与之前相同的内容,这里有两个方法,一个用于获取所有用户,另一个用于通过电子邮件查找用户。

接下来,打开或创建 RetroBoardConfig 类。请参阅第 13-7 号列表。

package com.apress.myretro.config;
import com.apress.myretro.board.Card;
import com.apress.myretro.board.CardType;
import com.apress.myretro.board.RetroBoard;
import com.apress.myretro.service.RetroBoardAndCardService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
@EnableFeignClients(basePackages = "com.apress.myretro.client")
@Configuration
public class RetroBoardConfig {
    @Bean
    CommandLineRunner init(RetroBoardAndCardService service){
        return args -> {
            //... some code here
        };
    }
}

列表 13-7 源代码:
src/main/java/com/apress/myretro/config/RetroBoardConfig.java

列表 13-7 显示,RetroBoardConfig 类有一个新的注解:@EnableFeignClients(basePackages = "com.apress.myretro.client")。这是一种告诉自动配置所有客户端位置的方法。尽管只有一个客户端,这就是设置它的方式,或者你可以使用 clients 参数,将实际类 UserClient.class 作为值添加。

接下来,我们需要配置 UserClient 接口,以便 My Retro App 能够与 Users App 进行通信。请打开或创建 RetroBoardAndCardService 类,详见列表 13-8。

package com.apress.myretro.service;
import com.apress.myretro.board.RetroBoard;
import com.apress.myretro.client.UserClient;
import com.apress.myretro.events.RetroBoardEvent;
import com.apress.myretro.events.RetroBoardEventAction;
import com.apress.myretro.exceptions.RetroBoardNotFoundException;
import com.apress.myretro.persistence.RetroBoardRepository;
import io.micrometer.core.instrument.Counter;
import lombok.AllArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.UUID;
@AllArgsConstructor
@Service
public class RetroBoardAndCardService {
    private RetroBoardRepository retroBoardRepository;
    private ApplicationEventPublisher eventPublisher;
    private Counter retroBoardCounter;
    private UserClient userClient;
    // more code here...
    // External Call
    public void getAllUsers(){
                   userClient.getAllUsers().getBody().forEach(System.out::println);
          }
}

列表 13-8 源代码:
src/main/java/com/apress/myretro/service/RetroBoardAndCardService.java

在列表 13-8 中,重要的一点是我们注入了 UserClient 接口,并且仅在 getAllUsers 方法中使用它(我们只是打印这些值)。

接下来,打开或创建 RetroBoardController 类。这是一个用于暴露 /retros/users 端点的方法,以下是相关代码(无需展示全部内容):

@GetMapping("/users")
    public ResponseEntity getAllUsers(){
        this.retroBoardAndCardService.getAllUsers();
        return ResponseEntity.ok(Map.of("status",200,"message","Users retrieved. Should be in the console","time",LocalDateTime.now()));
    }

这段代码会调用服务,并在控制台中显示用户信息。

接下来,打开 application.properties 文件,参见列表 13-9。

## DataSource
spring.h2.console.enabled=true
spring.datasource.generate-unique-name=false
spring.datasource.name=test-db
spring.jpa.show-sql=true
## Server
server.port=${PORT:8080}
## Consul
spring.config.import=consul://
## Application
spring.application.name=my-retro-app
logging.pattern.correlation=[${spring.application.name:},%X{traceId:-},%X{spanId:-}]
## Actuator Info
info.developer.name=Felipe
info.developer.email=felipe@email.com
info.api.version=1.0
management.endpoint.env.enabled=true
## Actuator
management.endpoints.web.exposure.include=health,info,metrics,prometheus
## Actuator Observations
management.observations.key-values.application=${spring.application.name}
## Actuator Metrics
management.metrics.distribution.percentiles-histogram.http.server.requests=true
## Actuator Tracing
management.tracing.sampling.probability=1.0
## Actuator Prometheus
management.prometheus.metrics.export.enabled=true
management.metrics.use-global-registry=true

列表 13-9 源文件:
src/main/resources/application.properties

在 application.properties 中,重要的是要指定应用程序的名称,即 spring.application.name=my-retro-app,以及 spring.config.import=consul://,这将使用 Consul 作为外部配置(尽管我们并没有指定任何内容)。