書接上文???
是什么讓一些東西變得 RESTful?
到目前為止,您擁有一個基于 Web 服務來處理涉及員工數據的核心操作。但這還不足以讓事情變得“RESTful”。
- 漂亮的 URL/employees/3不是 REST。
- 僅使用GET,POST等不是 REST。
- 安排好所有的 CRUD 操作不當 REST。
事實上,到目前為止,我們構建的更好地描述為RPC(遠程過程調用)。那是因為沒有辦法知道如何與這個服務器交互。如果您今天發布了此內容,您還必須編寫文檔或在某個地方托管開發人員的門戶,其中包含所有詳細信息。
Roy Fielding 的這一陳述可能會進一步為REST和RPC之間的區別提供線索:
我對將任何基于 HTTP 的接口稱為 REST API 的人數感到沮喪。今天的例子是 SocialSite REST API。那就是RPC。它尖叫 RPC。展示的耦合太多了,應該給它一個 X 評級。
要做些什么來使用 REST 架構風格清楚地認識到超文本是一種約束?換句話說,應用程序狀態引擎(以及 API)不是由超文本驅動的,那么它就不能是 RESTful 并且不能是 REST API。時期。是否有一些損壞的手冊需要修復?
— 羅伊菲爾丁
https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
在我們的表示中不包括超媒體的副作用是客戶端必須硬編碼 URI 來導航 API。這導致了與網絡電子商務興起之前相同的脆弱性。這表明我們的 JSON 輸出需要一點幫助。
介紹Spring HATEOAS,這是一個 Spring 項目,旨在幫助您編寫超媒體驅動的輸出。要將您的服務升級為 RESTful,請將其添加到您的構建中:
將 Spring HATEOAS 添加dependencies到pom.xml
org.springframework.bootspring-boot-starter-hateoas復制
這個小型庫將為我們提供定義 RESTful 服務的結構,然后以可接受的格式呈現它以供客戶使用。
任何 RESTful 服務的一個關鍵要素是添加指向相關操作的鏈接。要使您的控制器更加 RESTful,請添加如下鏈接:
獲取單個項目的資源
@GetMapping("/employees/{id}")EntityModel one(@PathVariable Long id) { Employee employee = repository.findById(id) // .orElseThrow(() -> new EmployeeNotFoundException(id)); return EntityModel.of(employee, // linkTo(methodOn(EmployeeController.class).one(id)).withSelfRel(), linkTo(methodOn(EmployeeController.class).all()).withRel("employees"));}
本教程基于 Spring MVC 并使用靜態輔助方法WebMvcLinkBuilder來構建這些鏈接。如果您在項目中使用 Spring WebFlux,則必須改用WebFluxLinkBuilder.
這與我們之前的情況非常相似,但有一些變化:
- 該方法的返回類型已從 更改Employee為EntityModel。EntityModel是來自 Spring HATEOAS 的通用容器,它不僅包含數據,還包含鏈接集合。
- linkTo(methodOn(EmployeeController.class).one(id)).withSelfRel()要求 Spring HATEOAS 建立到EmployeeController'one()方法的鏈接,并將其標記為自鏈接。
- linkTo(methodOn(EmployeeController.class).all()).withRel("employees")要求 Spring HATEOAS 建立到聚合根的鏈接all(),并將其稱為“員工”。
“建立鏈接”是什么意思?Spring HATEOAS 的核心類型之一是Link. 它包括一個URI和一個rel(關系)。鏈接是賦予網絡權力的東西。在萬維網之前,其他文檔系統會呈現信息或鏈接,但正是將文檔與這種關系元數據鏈接在一起,才將網絡縫合在一起。
Roy Fielding 鼓勵使用使 Web 成功的相同技術構建 API,鏈接就是其中之一。
如果您重新啟動應用程序并查詢Bilbo的員工記錄,您將得到與之前略有不同的響應:
冰壺更漂亮
當你的 curl 輸出變得更復雜時,它可能變得難以閱讀。使用這個或其他技巧來美化 curl 返回的 json:
# 指示部分將輸出通過管道傳輸到 json_pp 并要求它使您的 JSON 更漂亮。(或者使用任何你喜歡的工具!)
# v------------------v
curl -v localhost:8080/employees/1 | json_pp
單個員工的 RESTful 表示
{
"id": 1,
"name": "Bilbo Baggins",
"role": "burglar",
"_links": {
"self": {
"href": "http://localhost:8080/employees/1"
},
"employees": {
"href": "http://localhost:8080/employees"
}
}
}
這個解壓縮的輸出不僅顯示了您之前看到的數據元素(id和name)role,而且還顯示了一個_links包含兩個 URI 的條目。整個文檔使用HAL進行格式化。
HAL 是一種輕量級媒體類型,它不僅可以編碼數據,還可以編碼超媒體控件,提醒消費者注意他們可以導航的 API 的其他部分。在這種情況下,有一個“自我”鏈接(有點像this代碼中的語句)以及一個返回聚合根的鏈接。
為了使聚合根 ALSO 更加 RESTful,您希望包括頂級鏈接,同時還包括其中的任何 RESTful 組件。
所以我們把這個
獲取聚合根
@GetMapping("/employees")List all() { return repository.findAll();}
進入這個
獲取聚合根源
@GetMapping("/employees")CollectionModel> all() { List> employees = repository.findAll().stream() .map(employee -> EntityModel.of(employee, linkTo(methodOn(EmployeeController.class).one(employee.getId())).withSelfRel(), linkTo(methodOn(EmployeeController.class).all()).withRel("employees"))) .collect(Collectors.toList()); return CollectionModel.of(employees, linkTo(methodOn(EmployeeController.class).all()).withSelfRel());}
哇!曾經的那個方法,repository.findAll()都長大了!不用擔心。讓我們打開它。
CollectionModel<>是另一個 Spring HATEOAS 容器;它旨在封裝資源集合,而不是像EntityModel<>之前那樣封裝單個資源實體。CollectionModel<>,也可以讓您包含鏈接。
不要讓第一個聲明溜走。“封裝集合”是什么意思?員工收藏?
不完全的。
由于我們談論的是 REST,它應該封裝員工資源的集合。
這就是為什么您獲取所有員工,然后將它們轉換為EntityModel對象列表的原因。(感謝 Java 8 流!)
如果您重新啟動應用程序并獲取聚合根,您可以看到它現在的樣子。
員工資源集合的 RESTful 表示
{ "_embedded": { "employeeList": [ { "id": 1, "name": "Bilbo Baggins", "role": "burglar", "_links": { "self": { "href": "http://localhost:8080/employees/1" }, "employees": { "href": "http://localhost:8080/employees" } } }, { "id": 2, "name": "Frodo Baggins", "role": "thief", "_links": { "self": { "href": "http://localhost:8080/employees/2" }, "employees": { "href": "http://localhost:8080/employees" } } } ] }, "_links": { "self": { "href": "http://localhost:8080/employees" } }}復制
對于提供員工資源集合的聚合根,有一個頂級“自我”鏈接。“集合”列在“_embedded”部分下方;這就是 HAL 表示集合的方式。
并且集合的每個單獨成員都有他們的信息以及相關鏈接。
添加所有這些鏈接有什么意義?它使得隨著時間的推移發展 REST 服務成為可能。可以維護現有鏈接,而將來可以添加新鏈接。新客戶可以利用新鏈接,而舊客戶可以在舊鏈接上維持自己的生命。如果服務被重新定位和移動,這將特別有用。只要保持鏈接結構,客戶端仍然可以找到事物并與之交互。
簡化鏈接創建
在前面的代碼中,您是否注意到單個員工鏈接創建中的重復?為員工提供單個鏈接以及創建到聚合根的“員工”鏈接的代碼顯示了兩次。如果這引起了您的關注,很好!有一個解決方案。
簡單地說,你需要定義一個將Employee對象轉換為EntityModel對象的函數。雖然您可以輕松地自己編寫此方法,但在實現 Spring HATEOAS 的
RepresentationModelAssembler接口的道路上也有好處——它將為您完成工作。
進化
/src/main/java/payroll/EmployeeModelAssembler.java
package payroll;import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;import org.springframework.hateoas.EntityModel;import org.springframework.hateoas.server.RepresentationModelAssembler;import org.springframework.stereotype.Component;@Componentclass EmployeeModelAssembler implements RepresentationModelAssembler> { @Override public EntityModel toModel(Employee employee) { return EntityModel.of(employee, // linkTo(methodOn(EmployeeController.class).one(employee.getId())).withSelfRel(), linkTo(methodOn(EmployeeController.class).all()).withRel("employees")); }}復制,>
這個簡單的接口有一個方法:toModel(). 它基于將非模型對象 ( Employee) 轉換為基于模型的對象 ( EntityModel)。
您之前在控制器中看到的所有代碼都可以移到此類中。并且通過應用 Spring Framework 的@Component注解,將在應用程序啟動時自動創建匯編程序。
Spring HATEOAS 的所有模型的抽象基類是RepresentationModel. 但是為了簡單起見,我建議使用EntityModel作為您的機制來輕松地將所有 POJO 包裝為模型。
要利用此匯編器,您只需EmployeeController通過在構造函數中注入匯編器來更改 。
將 EmployeeModelAssembler 注入控制器
@RestControllerclass EmployeeController { private final EmployeeRepository repository; private final EmployeeModelAssembler assembler; EmployeeController(EmployeeRepository repository, EmployeeModelAssembler assembler) { this.repository = repository; this.assembler = assembler; } ...}
從這里,您可以在單項員工方法中使用該匯編程序:
使用匯編程序獲取單項資源
@GetMapping("/employees/{id}")EntityModel one(@PathVariable Long id) { Employee employee = repository.findById(id) // .orElseThrow(() -> new EmployeeNotFoundException(id)); return assembler.toModel(employee);}
這段代碼幾乎是一樣的,除了不是在EntityModel這里創建實例,而是將它委托給匯編器。也許這看起來并不多。
在聚合根控制器方法中應用相同的東西更令人印象深刻:
使用匯編程序獲取聚合根資源
@GetMapping("/employees")CollectionModel> all() { List> employees = repository.findAll().stream() // .map(assembler::toModel) // .collect(Collectors.toList()); return CollectionModel.of(employees, linkTo(methodOn(EmployeeController.class).all()).withSelfRel());}
同樣,代碼幾乎相同,但是您可以將所有EntityModel創建邏輯替換為map(assembler::toModel). 由于 Java 8 方法引用,插入它并簡化您的控制器非常容易。
Spring HATEOAS 的一個關鍵設計目標是讓 The Right Thing? 變得更容易。在這種情況下:將超媒體添加到您的服務中,而無需對事物進行硬編碼。
在這個階段,您已經創建了一個實際生成超媒體驅動內容的 Spring MVC REST 控制器!不講 HAL 的客戶端可以在使用純數據時忽略額外的位。使用 HAL 的客戶可以瀏覽您授權的 API。
但這并不是使用 Spring 構建真正的 RESTful 服務所需的唯一內容。
......未完待續......
審核編輯:湯梓紅
-
spring
+關注
關注
0文章
340瀏覽量
14338 -
REST
+關注
關注
0文章
32瀏覽量
9407
發布評論請先 登錄
相關推薦
評論