Spring Cloud Series – The Gateway Pattern – Spring的云系列–网关模式

最后修改: 2017年 6月 28日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

So far, in our cloud application, we’ve used the Gateway Pattern to support two main features.

到目前为止,在我们的云计算应用程序中,我们已经使用网关模式来支持两个主要功能。

First, we insulated our clients from each service, eliminating the need for cross-origin support. Next, we implemented locating instances of services using Eureka.

首先,我们将客户与每个服务隔离,消除了对跨源支持的需求。接下来,我们使用Eureka实现了对服务实例的定位。

In this article, we are going to look at how to use the Gateway pattern to retrieve data from multiple services with a single request. To do this, we’re going to introduce Feign into our gateway to help write the API calls to our services.

在这篇文章中,我们将探讨如何使用网关模式来通过一个请求从多个服务中检索数据。为此,我们将把Feign引入我们的网关,以帮助编写对服务的API调用。

To read up on how to use the Feign client check out this article.

要了解如何使用Feign客户端,请查看这篇文章

Spring Cloud now also provides the Spring Cloud Gateway project which implements this pattern.

Spring Cloud现在还提供了Spring Cloud Gateway项目,它实现了这种模式。

2. Setup

2.设置

Let’s open up the pom.xml of our gateway server and add the dependency for Feign:

让我们打开gateway服务器的pom.xml,添加Feign的依赖。

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

For reference – we can find the latest versions on Maven Central (spring-cloud-starter-feign).

作为参考–我们可以在Maven Centralspring-cloud-starter-feign)找到最新版本。

Now that we have the support for building a Feign client, let’s enable it in the GatewayApplication.java:

现在我们已经支持建立一个Feign客户端,让我们在GatewayApplication.java中启用它。

@EnableFeignClients
public class GatewayApplication { ... }

Now let’s set up Feign clients for the book and rating services.

现在让我们为图书和评级服务设置Feign客户。

3. Feign Clients

3.佯装客户

3.1. Book Client

3.1.书籍客户端

Let’s create a new interface called BooksClient.java:

让我们创建一个名为BooksClient.java的新接口。

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

With this interface, we’re instructing Spring to create a Feign client that will access the “/books/{bookId}” endpoint. When called, the getBookById method will make an HTTP call to the endpoint, and make use of the bookId parameter.

通过这个接口,我们指示Spring创建一个Feign客户端,访问”/books/{bookId}”端点。当被调用时,getBookById方法将对该端点进行HTTP调用,并使用bookId参数。

To make this work we need to add a Book.java DTO:

为了使其工作,我们需要添加一个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
}

Let’s move on to the RatingsClient.

让我们继续讨论RatingsClient

3.2. Ratings Client

3.2.评级客户

Let’s create an interface called RatingsClient:

让我们创建一个名为RatingsClient的接口。

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

Like with the BookClient, the method exposed here will make a rest call to our rating service and return the list of ratings for a book.

BookClient一样,这里暴露的方法将对我们的评级服务进行恢复调用,并返回一本书的评级列表。

However, this endpoint is secured. To be able to access this endpoint properly we need to pass the user’s session to the request.

然而,这个端点是安全的。为了能够正常访问这个端点,我们需要在请求中传递用户的会话。

We do this using the @RequestHeader annotation. This will instruct Feign to write the value of that variable to the request’s header. In our case, we are writing to the Cookie header because Spring Session will be looking for our session in a cookie.

我们使用@RequestHeader注解来做到这一点。这将指示Feign将该变量的值写入请求的头。在我们的例子中,我们要写到Cookie头,因为Spring Session将在cookie中寻找我们的会话。

In our case, we are writing to the Cookie header because Spring Session will be looking for our session in a cookie.

在我们的例子中,我们要写到Cookie头,因为Spring Session将在cookie中寻找我们的会话。

Finally, let’s add a Rating.java DTO:

最后,让我们添加一个Rating.java DTO。

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

Now, both clients are complete. Let’s put them to use!

现在,这两个客户已经完成。让我们把它们投入使用吧

4. Combined Request

4.合并请求

One common use case for the Gateway pattern is to have endpoints that encapsulate commonly called services. This can increase performance by reducing the number of client requests.

网关模式的一个常见用例是拥有封装常用服务的端点。这可以通过减少客户端请求的数量来提高性能。

To do this let’s create a controller and call it CombinedController.java:

要做到这一点,让我们创建一个控制器,并将其称为CombinedController.java

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

Next, let’s wire in our newly created feign clients:

接下来,让我们把我们新创建的佯装客户接入。

private BooksClient booksClient;
private RatingsClient ratingsClient;

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

And finally let’s create a GET request that combines these two endpoints and returns a single book with its ratings loaded:

最后,让我们创建一个GET请求,将这两个端点结合起来,返回一本加载了评级的书。

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

Notice that we are setting the session value using the @CookieValue annotation that extracts it from the request.

注意,我们正在使用@CookieValue注解来设置会话值,该注解从请求中提取它。

There it is! We have a combined endpoint in our gateway that reduces network calls between the client and the system!

有了!有了我们的网关中有一个合并的端点,可以减少客户和系统之间的网络调用!

5. Testing

5.测试

Let’s make sure our new endpoint is working.

让我们确定我们的新端点正在工作。

Navigate to LiveTest.java and let’s add a test for our combined endpoint:

导航到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);
}

Start up Redis, and then run each service in our application: config, discovery, zipkin, gateway, book, and the rating service.

启动Redis,然后在我们的应用程序中运行每个服务。config, discovery, zipkin, gateway, book, 和rating服务。

Once everything is up, run the new test to confirm it is working.

一旦一切就绪,运行新的测试以确认其工作。

6. Conclusion

6.结论

We’ve seen how to integrate Feign into our gateway to build a specialized endpoint. We can leverage this information to build any API we need to support. Most importantly we see that we are not trapped by a one-size-fits-all API that only exposes individual resources.

我们已经看到了如何将Feign集成到我们的网关中来构建一个专门的端点。我们可以利用这些信息来建立我们需要支持的任何API。最重要的是,我们看到我们没有被一个只暴露个别资源的一刀切的API所困。

Using the Gateway pattern we can set up our gateway service to each client’s needs uniquely. This creates decoupling giving our services the freedom to evolve as they need, remaining lean and focused on one area of the application.

使用网关模式,我们可以根据每个客户的需求来设置我们的网关服务。这创造了解耦,使我们的服务可以根据需要自由发展,保持精简并专注于应用程序的一个领域。

As always, code snippets can be found over on GitHub.

一如既往,代码片段可以在GitHub上找到