1. 概述
迄今為止,在我們的雲應用程序中,我們使用了網關模式來支持兩個主要功能。
首先,我們隔離了客户端,消除了跨域支持的需求。 其次,我們使用 Eureka 定位服務實例。
在本文中,我們將探討如何使用網關模式來通過單個請求檢索來自多個服務的數據。 為此,我們將向我們的網關引入 Feign,以幫助我們編寫對服務的 API 調用。
要了解如何使用 OpenFeign 客户端,請查看這篇文章。
Spring Cloud 現在還提供了 Spring Cloud Gateway 項目,該項目實現了該模式。
2. 設置
打開我們的 gateway 服務器的 pom.xml 文件,並添加 OpenFeign 的依賴項:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>作為參考 – 我們可以在 Maven Central (spring-cloud-starter-feign) 上找到最新版本。
現在我們已經支持構建 OpenFeign 客户端,請在 GatewayApplication.java 中啓用它:
@EnableFeignClients
public class GatewayApplication { ... }現在我們來為圖書和評分服務設置 OpenFeign 客户端。
3. OpenFeign Clients
OpenFeign Clients 提供了一種方便的方式來使用 OpenFeign 與遠程服務交互。它們允許你將 OpenFeign 配置映射到 Java 類,從而可以像使用傳統的 Java 客户端一樣使用 OpenFeign。
為什麼要使用 OpenFeign Clients?
- 代碼可讀性: 將 OpenFeign 配置映射到 Java 類可以顯著提高代碼的可讀性和可維護性。
- 測試友好: 你可以輕鬆地使用 Mockito 或其他測試框架來模擬 OpenFeign Clients,從而進行單元測試。
- 代碼重用: 你可以將 OpenFeign Clients 註冊到 Spring 容器中,以便在整個應用程序中使用。
如何創建 OpenFeign Clients
你可以使用 @FeignClient 註解來定義 OpenFeign Clients。 以下是一個示例:
@FeignClient(name = "my-service", url = "http://localhost:8080")
public interface MyServiceClient {
@GetMapping("/users")
List<User> getUsers();
@PostMapping("/users")
User createUser(@RequestBody User user);
}
解釋:
@FeignClient(name = "my-service", url = "http://localhost:8080"): 定義了 OpenFeign Client 的名稱為 "my-service",並指定了其 URL。@GetMapping("/users"): 定義了客户端的一個方法,用於調用遠程服務的 GET 請求。@PostMapping("/users"): 定義了客户端的一個方法,用於調用遠程服務的 POST 請求。@RequestBody User user: 將請求體映射到User對象。
總結
OpenFeign Clients 是 OpenFeign 的一個強大功能,可以幫助你更輕鬆地與遠程服務交互。 通過將 OpenFeign 配置映射到 Java 類,你可以提高代碼的可讀性、可維護性和測試友好性。
3.1. Book Client
讓我們創建一個名為 BooksClient.java 的新接口:
@FeignClient("book-service")
public interface BooksClient {
@RequestMapping(value = "/books/{bookId}", method = RequestMethod.GET)
Book getBookById(@PathVariable("bookId") Long bookId);
}通過這個接口,我們正在指示 Spring 創建一個 OpenFeign 客户端,該客户端將訪問 “/books/{bookId}” 端點。當調用時,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
}讓我們繼續討論 RatingsClient。
3.2. 評分客户端
讓我們創建一個名為 RatingsClient 的接口:
@FeignClient("rating-service")
public interface RatingsClient {
@RequestMapping(value = "/ratings", method = RequestMethod.GET)
List<Rating> getRatingsByBookId(
@RequestParam("bookId") Long bookId,
@RequestHeader("Cookie") String session);
}與 BookClient 類似,此處提供的該方法將向我們的評分服務發起一個 REST 調用,並返回書籍的評分列表。
但是,此端點是受保護的。 為了正確訪問此端點,我們需要將用户的會話傳遞到請求中。
我們使用 @RequestHeader 註解來實現這一點。這將指示 OpenFeign 將該變量的值寫入請求的標頭中。在本例中,我們寫入 Cookie 標頭,因為 Spring Session 將在 Cookie 中查找我們的會話。
在本例中,我們寫入 Cookie 標頭,因為 Spring Session 將在 Cookie 中查找我們的會話。
最後,讓我們添加一個 Rating.java DTO:
@JsonIgnoreProperties(ignoreUnknown = true)
public class Rating {
private Long id;
private Long bookId;
private int stars;
}現在,這兩個客户端都已完成。讓我們來使用它們!
4. 合併請求
使用 Gateway 模式的常見用例是將經常調用的服務封裝到端點中。這可以提高性能,減少客户端請求的數量。
為了實現這一點,我們創建一個控制器,並將其命名為 CombinedController.java:
@RestController
@RequestMapping("/combined")
public class CombinedController { ... }接下來,讓我們將我們新創建的 OpenFeign 客户端連接起來:
private BooksClient booksClient;
private RatingsClient ratingsClient;
@Autowired
public CombinedController(
BooksClient booksClient,
RatingsClient ratingsClient) {
this.booksClient = booksClient;
this.ratingsClient = ratingsClient;
}最後,讓我們創建一個 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;
}請注意,我們使用 @CookieValue 註解從請求中提取值來設置會話值。
找到了!我們網關中有一個合併的端點,從而減少了客户端與系統之間的網絡調用!
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。最重要的是,我們意識到我們沒有被一個“一刀切”的 API 所限制,它只會暴露單個資源。
通過使用網關模式,我們可以為每個客户端設置網關服務,以滿足其獨特需求。這實現瞭解耦,使我們的服務能夠根據需要自由發展,保持簡潔和專注於應用程序的一個領域。