restful和rest_HATEOAS的RESTful服务:JVM上的REST API和超媒体
restful和rest
1.簡介
到目前為止,我們已經花了很多時間談論了相當數量的關于角色的的超媒體和HATEOAS在REST風格的 Web服務和API,掃視不同規格和可用性方面。 聽起來好像支持超媒體和HATEOAS并不難,只需選擇您的收藏夾就可以了! 您可能會猜到,現實是“好,取決于情況”,在本教程的這一部分中,我們將理解“為什么”。 困難的學習方法是從頭開始設計和實現它們。 這正是我們將要忙的事情,主要在Java中將RESTful Web API和客戶端引入JVM平臺。
我們將為其設計RESTful Web API的應用程序是我們之前涉及的案例研究的擴展版本。 從業務角度來看,我們將實施一個可以讓客戶租車的租車平臺。 我們的目標是根據REST架構風格的原則和約束來構建該平臺。
目錄
1.簡介 2.從CRUD到工作流程 3.在服務器上2.從CRUD到工作流程
超媒體作為驅動力的存在極大地改變了設計過程。 不幸的事實是,目前大多數基于HTTP的Web服務和API基本上都是CRUD代理,用于其后面的數據存儲。 僅僅公開數據模型并不能幫助客戶理解他們可以做什么,他們注定要在許多地方復制服務器的業務邏輯(至少是業務邏輯的大部分)。 這不是經過深思熟慮的REST應用程序應該做的。
REST API應該花費幾乎所有的描述性精力來定義用于表示資源和驅動應用程序狀態的媒體類型,或者為現有標準媒體類型定義擴展關系名稱和/或啟用超文本的標記。 花費所有精力描述應該在媒體類型的處理規則范圍內(并且在大多數情況下已由現有媒體類型定義)完全定義對感興趣的URI使用哪種方法。
https://roy.gbiv.com/untangled/2008/rest-apis-must-be-超文本驅動代替CRUD ,我們應該將RESTful Web API視為工作流。 通過就適當的媒體類型達成共識,服務器和客戶端可以就如何解釋它們達成共識。 鏈接,關系和操作(支付能力)的存在會引導客戶采取可能的后續步驟。 換句話說,服務定義并共享供客戶遵循的工作流。
3.在服務器上
在深入研究庫和框架之前,最好先了解我們正在嘗試作為應用程序一部分構建的RESTful Web API的工作流程。 下圖是這樣做的嘗試。
租車API工作流程誠然,工作流遠非詳盡無遺,但足以說明現實世界中RESTful Web API的復雜性,挑戰和好處。
最后但并非最不重要的一點是,我們尚未決定要使用哪種超媒體規范 。 HAL的簡單性,輕量級結構和廣泛采用(以及HAL-FORMS )使其成為大多數情況下的不錯選擇,這就是我們將用于RESTful Web API的方式。
制定戰略決策后,就該討論技術細節了。 首先,我們正在尋找對我們有幫助的庫或框架。
JAX-RS
Jakarta RESTful Web服務規范(更好地稱為JAX-RS 2.1 ( JSR-370 ))是在JVM平臺上實現RESTful Web服務和API的最受歡迎選擇之一。
JAX-RS : RESTful Web服務的Java API ( JAX-RS )是一種Java編程語言API規范,它提供了根據代表性狀態轉移(REST)架構模式創建Web服務的支持。
https://projects.eclipse.org/projects/ee4j.jaxrs盡管它包括全面的服務器端和客戶端支持,但除了引入非常基本的Link表示之外,它幾乎沒有解決任何超媒體功能。
一些JAX-RS參考實現(例如Jersey )進行了努力,并捆綁了自己的專有擴展以促進對超媒體的支持,但是正如您所期望的那樣,這些都不是規范的一部分。 這當然是有幫助的,但仍然需要開發人員完成大量工作。
Quarkus,Micronaut,Helidon……
向微服務架構和云計算的加速轉變導致了新一代的云原生框架的泛濫。 對于JVM平臺尤其如此,在JVM平臺上,著名的領導者正受到Quarkus , Micronaut和Helidon等人的挑戰。
實際上,對于大多數媒體 而言 , 超媒體和HATEOAS并不是首要任務。 Micronaut是異常值的一個示例,該異常值至少包含基本的超媒體元素 ,但總的來說,您是一個人。
Crnk
如果您碰巧選擇了JSON:API規范來支持RESTful Web服務和API,那么Crnk框架當然值得一看。
Crnk是JSON API規范和Java中建議的實現,以促進構建RESTful應用程序。 它提供了許多可以使應用程序受益的約定和構造塊。 這包括諸如排序,過濾,分頁,請求復雜對象圖,稀疏字段集,將鏈接附加到數據或自動執行多項操作等功能。 與框架和庫(例如Spring,CDI,JPA,Bean驗證,Dropwizard,Servlet API,Zipkin等)的進一步集成可確保JSON API與Jav??a生態系統完美地結合在一起。
https://github.com/crnk-project/crnk-frameworkCrnk采用以資源為中心的API建模方法,基本上可以實現非常干凈且可維護的實現。 引用文檔, 資源,關系和存儲庫是Crnk的核心構建塊。 下面的代碼段很好地說明了這些概念。
@JsonApiResource(type = "customer", resourcePath = "customers") public class Customer {@JsonApiId private String id;@NotNull @NotBlank private String firstName;@NotNull @NotBlank private String lastName;@JsonApiRelation(mappedBy = "customer")private Collection<Reservation> reservations;// Getters and setters here... }@JsonApiResource(type = "reservation", resourcePath = "reservations") public class Reservation {@JsonApiId private String id;private String vehicle;@NotNull @FutureOrPresent private LocalDate from;@NotNull @FutureOrPresent private LocalDate to;@JsonApiRelation private Customer customer;// Getters and setters here... }@Repository public class CustomerRepository implements ResourceRepository<Customer, String> {// Implementation of the repository methods here... }@Repository public class ReservationRepository implements ResourceRepository<Reservation, String> {// Implementation of the repository methods here... }基本上就是這樣,根據集成( Vert.X , JAX-RS和Spring Boot ), Crnk框架將為您完成其余工作。 不幸的是, Crnk不支持ALPS或JSON Hyper-Schema (至少是開箱即用的)。
但是由于我們決定使用HAL而不是JSON:API ,所以我們必須繼續搜索。
Spring帽子
構成Spring產品組合的項目數量確實令人印象深刻。 我們特別感興趣的是Spring HATEOAS ,它是一個庫,用于支持實現超媒體驅動的RESTful Web服務和API的表示形式。 它實現了HAL , HAL-FORMS , Collection + JSON和UBER規范,并且加分了一點,它帶有ALPS支持,非常適合我們完成已設定的目標。
如人們所料, Spring HATEOAS自然地與典型的Spring Boot Web應用程序集成,包括傳統的Spring MVC和響應式Spring WebFlux堆棧。 @EnableHypermediaSupport批注以及Spring Boot自動配置功能會根據您選擇的一個(或多個)規范來激活超媒體支持。
@SpringBootConfiguration @EnableHypermediaSupport(type = HypermediaType.HAL_FORMS) public class ReservationServiceConfig {@BeanHalFormsConfiguration halFormsConfiguration() {final HalFormsConfiguration configuration = new HalFormsConfiguration();configuration.registerPattern(LocalDate.class, "yyyy-MM-dd");return configuration;} }遵循非正式的命名約定, RootController我們的RESTful Web API提供RootController 。
@RestController public class RootController {@GetMapping("/")public ResponseEntity<RepresentationModel<?>> root() {final RepresentationModel<?> model = new RepresentationModel<>();model.add(linkTo(methodOn(RootController.class).root()).withSelfRel());model.add(templated(linkTo(methodOn(ReservationController.class).findAll(null)), "reservations").withProfile(linkTo(methodOn(RootController.class).reservations()).withSelfRel().getHref()));model.add(linkTo(methodOn(CustomerController.class).findAll()).withRel("customers").withProfile(linkTo(methodOn(RootController.class).customers()).withSelfRel().getHref()));return ResponseEntity.ok(model);} }控制器返回的HAL文檔提示了下一個可用的導航方向。
{ "_links": { "self": { "href": "https://rentals.jcg.com" }, "reservations": { "href": "https://rentals.jcg.com/reservations{?page,size,sort}", "profile": "https://rentals.jcg.com/alps/reservations", "templated" : true }, "customers": { "href": "https://rentals.jcg.com/customers", "profile": "https://rentals.jcg.com/alps/customers" } } }有些事情可能引起您的注意。 第一個是reservations關系的鏈接,該鏈接作為模板返回。 第二個是每個鏈接關系的profile屬性的存在,指向各自的ALPS配置文件。 下面的代碼段說明了用于構建reservations收集資源的ALPS配置文件的Spring HATEOAS API。
@GetMapping(value = "/alps/reservations", produces = MediaTypes.ALPS_JSON_VALUE) public ResponseEntity<Alps> reservations() {return ResponseEntity.ok(Alps.alps().doc(doc().href("https://rentals.jcg.com/documentation.html").build()).descriptor(List.of(descriptor().id("reservations").type(Type.SEMANTIC).rt("#reservation").descriptor(Arrays.asList(descriptor().id("book").name("reservations").type(Type.UNSAFE).rt("#reservation").build(),descriptor().id("list").name("reservations").type(Type.SAFE).rt("#reservation").build())).build(),descriptor().id("reservation").type(Type.SEMANTIC).descriptor(Stream.concat(PropertyUtils.getExposedProperties(Reservation.class).stream().map(property -> descriptor().id(property.getName()).href(href(property)) .type(Type.SEMANTIC).build()),Stream.of(descriptor().id("customer").type(Type.SAFE).rt("#customer").build(),descriptor().id("update").name("reservation").type(Type.IDEMPOTENT).rt("#reservation").build(),descriptor().id("cancel").name("reservation").type(Type.IDEMPOTENT).rt("#reservation").build())).collect(Collectors.toList())).build())).build()); }分別是ALPS reservations集合資源概要文件的JSON表示,這基本上是客戶端要處理的內容。
{"version": "1.0","doc": {"href": "https://rentals.jcg.com/documentation.html"},"descriptor": [ {"id": "reservations","type": "SEMANTIC","descriptor": [ {"id": "book","name": "reservations","type": "UNSAFE","rt": "#reservation"}, {"id": "list","name": "reservations","type": "SAFE","rt": "#reservation"} ],"rt": "#reservation"}, {"id": "reservation","type": "SEMANTIC","descriptor": [ {"id": "from","href" : "https://schema.org/Date","type": "SEMANTIC"}, {"id": "id","href" : "https://schema.org/Thing#identifier","type": "SEMANTIC"}, {"id": "to","href" : "https://schema.org/Date","type": "SEMANTIC"}, {"id": "vehicle","href" : "https://schema.org/Vehicle#name","type": "SEMANTIC"}, {"id": "customer","type": "SAFE","rt": "#customer"}, {"id": "update","name": "reservation","type": "IDEMPOTENT","rt": "#reservation"}, {"id": "cancel","name": "reservation","type": "IDEMPOTENT","rt": "#reservation"} ]} ] }本著超媒體和HATEOAS的精神, Spring HATEOAS方法也是面向資源的(或者更確切地說 ,面向資源表示的)。 基本上,您必須實現許多RepresentationModelAssembler (例如ReservationResourceAssembler )和控制器端點,這些端點依賴于各自的匯編器來構造單個資源表示或資源集合表示。
@Component public class ReservationResourceAssembler implements SimpleRepresentationModelAssembler<Reservation> {@Overridepublic void addLinks(EntityModel<Reservation> resource) {resource.add(linkTo(methodOn(CustomerController.class).findOne(resource.getContent().getCustomerId())).withRel("customer").withType(linkTo(methodOn(RootController.class).customers()).slash("#customer").toString()));resource.add(linkTo(methodOn(ReservationController.class).findOne(resource.getContent().getId())).withSelfRel().withType(linkTo(methodOn(RootController.class).reservations()).slash("#reservation").toString()).andAffordance(afford(methodOn(ReservationController.class).modify(resource.getContent().getId(), null))).andAffordance(afford(methodOn(ReservationController.class).cancel(resource.getContent().getId()))));} }除了與customer的鏈接關系外,還有許多用于更改預留資源狀態( modify或cancel預留狀態)的優惠(動作)。 另外,由于reservations收集資源正在使用分頁(和排序),因此其表示的構造稍微復雜一點,并且涉及兩個匯編程序,因此讓我們看一下該示例。
@RestController @RequestMapping(path = "/reservations") public class ReservationController {@Autowired private ReservationRepository repository;@Autowired private ReservationResourceAssembler reservationResourceAssembler;@Autowired private PagedResourcesAssembler>Reservation< assembler;@GetMappingpublic ResponseEntity>PagedModel>EntityModel>Reservation<<< findAll(@PageableDefault Pageable pageable) {return ResponseEntity.ok(assembler.toModel(repository.findAll(pageable), reservationResourceAssembler));} }為了演示分頁的效果,僅用說2個元素的頁面大小來獲取reservations集合就足夠了。
{ "_embedded": { "reservations": [ { "id": "13e1892765c5", "vehicle": "Honda Civic 2020", "from": "2020-01-01", "to": "2020-01-05", "_links": { "customer": { "href": "https://rentals.jcg.com/customers/fed195a03e9d", "type": "https://rentals.jcg.com/alps/customers#customer" }, "self": { "href": "https://rentals.jcg.com/reservations/13e1892765c5", "type": "https://rentals.jcg.com/alps/reservations#reservation" } }, "_templates": { "cancel": { "method": "delete", "properties": [ ] }, "default": { "method": "put", "properties": [ { "name": "from", "regex": "yyyy-MM-dd", "required": true }, { "name": "to", "regex": "yyyy-MM-dd", "required": true }, { "name": "vehicle", "required": true } ] } } }, { "id": "fc14e8ef90f5", "vehicle": "BMW 325i", "from": "2020-01-10", "to": "2020-01-12", "_links": { "customer": { "href": "https://rentals.jcg.com/customers/fed195a03e9d", "type": "https://rentals.jcg.com/alps/customers#customer" }, "self": { "href": "https://rentals.jcg.com/reservations/fc14e8ef90f5", "type": "https://rentals.jcg.com/alps/reservations#reservation" } }, "_templates": { "cancel": { "method": "delete", "properties": [ ] }, "default": { "method": "put", "properties": [ { "name": "from", "regex": "yyyy-MM-dd", "required": true }, { "name": "to", "regex": "yyyy-MM-dd", "required": true }, { "name": "vehicle", "required": true } ] } } } ] }, "_links": { "first": { "href": "https://rentals.jcg.com/reservations?page=0&size=2" }, "self": { "href": "https://rentals.jcg.com/reservations?page=0&size=2" }, "next": { "href": "https://rentals.jcg.com/reservations?page=1&size=2" }, "last": { "href": "https://rentals.jcg.com/reservations?page=1&size=2" } }, "page": { "size": 2, "totalElements": 3, "totalPages": 2, "number": 0 } }很容易發現附加的導航鏈接first , next和last ,它們實際上是上下文相關的(例如,由于我們要求第一頁,因此不存在prev鏈接關系)。
可以肯定地說Spring HATEOAS為JVM平臺提供了最全面的超媒體和HATEOAS支持。 盡管它沒有立即實現某些規范,但它允許通過一組SPI 插入自定義媒體類型 。
九頭蛇
決定采用JSON-LD和Hydra的RESTful Web服務和API可能會受益于使用hydra-java庫。 Spring HATEAOS擴展的出現非常令人鼓舞,但不幸的是,由于它不能與最新的Spring HATEOAS版本一起使用而被陰影掩蓋 。
有了這個,我們對RESTful Web服務和API的服務器端實現有了一個很好的主意,現在該切換主題并討論客戶端。
4.在客戶端上
從客戶端角度,區分超媒體 API客戶端的兩個角度或類是有意義的:
- (Web)用戶界面(前端)上下文中的超媒體 API客戶端
- 業務任務實施(后端)環境中的超媒體 API客戶端
JavaScript是Web前端開發的第一選擇,而Java(通常是JVM)已經在后端方面占據了主導地位。 盡管我們將進一步討論后者,但許多概念同樣適用于兩者。
那么, 超媒體 API客戶端的設計和實現背后的原理是什么? 如果要強調的是一件事,那就是專注于針對超媒體規范進行編程,而不是檢查服務器的響應。 媒體類型應向客戶提供所有必要的詳細信息,并作為實施指南。 而且,客戶可能只使用一些特定的流程,而無需實現服務必須提供的所有功能。
JAX-RS
JAX-RS 2.1規范包括客戶端部分,不幸的是,該客戶端部分僅提供了從Link頭中提取鏈接的方法。
final Client client = ClientBuilder.newClient();try (final Response response = client.target("https://rentals.jcg.com/").request().accept("application/prs.hal-forms+json").get()) {final Link customers = response.getLink("customers");if (customers != null) {// follow the link here } } finally {client.close(); }基本上,與服務器端一樣 ,如果您需要完成某些工作,請準備好卷起袖子。
Crnk
Crnk框架提供了很好的客戶端支持,并通過熟悉的構建塊實現了這些構建塊:資源,關系和存儲庫。
final CrnkClient client = new CrnkClient("https://rentals.jcg.com/"); client.setHttpAdapter(new OkHttpAdapter());final ResourceRepository>Customer, String< repository = client.getRepositoryForType(Customer.class);final ResourceRepository>Customer, String< repository = client.getRepositoryForType(Customer.class); final List>Customer< customers = repository.findAll(new QuerySpec(Customer.class).setPaging(new OffsetLimitPagingSpec(0L, 10L)));if (!customers.isEmpty()) {// navigate through customers }如果您的RESTful Web服務和API遵循JSON:API規范,則Crnk客戶端可以為您節省大量時間和精力。
Spring帽子
令人驚訝的是,直到最近SpringHATEOAS對超媒體客戶端的支持還有些不完整,但是最新版本帶來了許多改進 。 Traverson是Spring HATEOAS支持的最古老的機制,用于在鏈接和關系之間導航。
final RestTemplate template = ...;final Map>String, Object< paging = Map.of("page", 0L,"size", 2L);final CollectionModelType>Reservation< resourceType =new TypeReferences.CollectionModelType>Reservation<() {};final Traverson traverson = new Traverson(URI.create("https://rentals.jcg.com/"), MediaTypes.HAL_FORMS_JSON).setLinkDiscoverers(List.of(new HalFormsLinkDiscoverer())).setRestOperations(template);final CollectionModel>Reservation< reservations = traverson.follow(rel("reservations").withParameters(paging)).toObject(resourceType);;if (!reservations.getContent().isEmpty()) {// navigate through reservations }像WebClient和RestTemplate這樣的更傳統的通用API客戶端(我們稱它們為REST客戶端)也已經得到了超媒體支持。
final WebClient client = builder.build();final CollectionModelType>Reservation< resourceType =new TypeReferences.CollectionModelType>Reservation<() {}; final LinkDiscoverer discoverer = new HalFormsLinkDiscoverer();final Optional>Link< link = client.get().uri("https://rentals.jcg.com/").accept(MediaTypes.HAL_FORMS_JSON).retrieve().bodyToMono(String.class).map(r -< discoverer.findLinkWithRel("reservations", r)).block();if (link.isPresent()) {final Map>String, Object< paging = Map.of("page", 0L,"size", 2L);final URI uri = link.get().getTemplate().expand(paging);final CollectionModel>Reservation< reservations = client.get().uri(uri).accept(MediaTypes.HAL_FORMS_JSON).retrieve().bodyToMono(resourceType).block();if (!reservations.getContent().isEmpty()) {// navigate through reservations} }您甚至可以選擇Traverson , RestTemplate或WebClient ,當然可以實現功能強大的超媒體 API客戶端,以完全自動化服務工作流程。
5。結論
在本教程的這一部分中,我們討論了可幫助您在JVM平臺上設計和實現超媒體驅動的RESTful Web API及其客戶端的庫和框架。 盡管數量不多,但您可以選擇。
由于它主要是使用JavaScript在瀏覽器端完成的,因此我們跳過了在Web前端上下文中有關超媒體 API客戶端的討論。 在這方面,仍然值得一提的是Traverson ,它是Node.js和瀏覽器的超媒體 API / HATEOAS客戶端(是的,這是Spring HATEAOS的靈感來源)。
6.接下來
在接下來的本教程的最后一部分,我們將總結REST體系結構樣式中最被遺忘和神秘的約束( 超媒體作為應用程序狀態引擎( HATEOAS ))背后的理論和實踐。
7.下載源代碼
下載您可以在此處下載此示例的完整源代碼: 具有HATEOAS的RESTful服務:JVM上的REST API和超媒體
翻譯自: https://www.javacodegeeks.com/restful-services-with-hateoas-rest-apis-and-hypermedia-on-jvm.html
restful和rest
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的restful和rest_HATEOAS的RESTful服务:JVM上的REST API和超媒体的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ddos攻击会关闭服务器吗(ddos攻击
- 下一篇: ps怎么添加画板(ps怎么添加画板数量)