1. 概述

在之前的云应用开发中,我们使用网关模式实现了两个核心功能:

服务隔离:将客户端与各个微服务解耦,避免跨域问题
服务发现:通过Eureka实现服务实例定位

本文将深入探讨如何利用网关模式通过单次请求聚合多个服务数据。为此,我们将在网关中集成Feign,简化服务间API调用。

关于OpenFeign的详细用法可参考这篇文章。Spring Cloud官方也提供了Spring Cloud Gateway项目实现该模式。

2. 环境搭建

打开网关服务的pom.xml,添加OpenFeign依赖:

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

最新版本可在Maven Central获取

GatewayApplication.java中启用Feign客户端:

@EnableFeignClients
public class GatewayApplication { ... }

接下来为图书和评分服务创建Feign客户端。

3. OpenFeign客户端

3.1. 图书客户端

创建BooksClient.java接口:

@FeignClient("book-service")
public interface BooksClient {
 
    @RequestMapping(value = "/books/{bookId}", method = RequestMethod.GET)
    Book getBookById(@PathVariable("bookId") Long bookId);
}

该接口指示Spring创建访问/books/{bookId}接口的Feign客户端。调用getBookById方法时,会自动发起HTTP请求,并传递bookId参数。

需配套创建Book.java DTO:

@JsonIgnoreProperties(ignoreUnknown = true)
public class Book {
 
    private Long id;
    private String author;
    private String title;
    private List<Rating> ratings;
    
    // getters and setters
}

3.2. 评分客户端

创建RatingsClient.java接口:

@FeignClient("rating-service")
public interface RatingsClient {
 
    @RequestMapping(value = "/ratings", method = RequestMethod.GET)
    List<Rating> getRatingsByBookId(
      @RequestParam("bookId") Long bookId, 
      @RequestHeader("Cookie") String session);
}

⚠️ 关键点:该接口受安全保护,必须传递用户会话信息。我们通过@RequestHeader注解将session写入请求头(Cookie字段),因为Spring Session会从cookie中读取会话。

配套创建Rating.java DTO:

@JsonIgnoreProperties(ignoreUnknown = true)
public class Rating {
    private Long id;
    private Long bookId;
    private int stars;
}

4. 聚合请求

网关模式的典型场景是创建聚合接口,减少客户端请求次数。创建CombinedController.java

@RestController
@RequestMapping("/combined")
public class CombinedController { ... }

注入Feign客户端:

private BooksClient booksClient;
private RatingsClient ratingsClient;

@Autowired
public CombinedController(
  BooksClient booksClient, 
  RatingsClient ratingsClient) {
 
    this.booksClient = booksClient;
    this.ratingsClient = ratingsClient;
}

实现聚合接口:

@GetMapping
public Book getCombinedResponse(
  @RequestParam Long bookId,
  @CookieValue("SESSION") String session) {
 
    Book book = booksClient.getBookById(bookId);
    List<Rating> ratings = ratingsClient.getRatingsByBookId(bookId, "SESSION="+session);
    book.setRatings(ratings);
    return book;
}

通过@CookieValue提取session并传递给评分服务,实现安全访问

5. 测试验证

LiveTest.java添加测试用例:

@Test
public void accessCombinedEndpoint() {
    Response response = RestAssured.given()
      .auth()
      .form("user", "password", formConfig)
      .get(ROOT_URI + "/combined?bookId=1");
 
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());
    assertNotNull(response.getBody());
 
    Book result = response.as(Book.class);
 
    assertEquals(new Long(1), result.getId());
    assertNotNull(result.getRatings());
    assertTrue(result.getRatings().size() > 0);
}

启动Redis及所有服务(config, discovery, zipkin, gateway, book, rating),运行测试验证功能。

6. 总结

我们成功将OpenFeign集成到网关中,实现了定制化聚合接口。这种模式带来三大优势:

按需定制API:避免"一刀切"的REST设计
减少网络调用:客户端单次请求获取聚合数据
服务解耦:微服务可独立演进,保持专注

通过网关模式,我们能灵活适配不同客户端需求,同时保持服务架构的简洁性和可维护性。

完整代码示例请访问GitHub仓库


原始标题:Spring Cloud Series - The Gateway Pattern