JAX-RS和OpenAPI对Hypermedia API的支持:任重而道远
或早或晚,大多數積極使用REST(ful) Web服務和API的開發人員都偶然發現了這種真正的外星事物,即HATEOAS : 超文本作為應用程序狀態的引擎 。 對HATEOAS是什么以及它與REST的關系的好奇最終將導致發現Richardson成熟度模型 ,該模型使REST和RESTful的行業定義神秘化。 后者是一個啟發,但提出了一個問題:這些年來,我們是否一直在錯誤地進行REST ?
讓我們嘗試從不同的角度回答這個問題。 HATEOAS是REST核心架構約束之一。 從這個角度來看,答案是“是”,為了聲稱符合REST ,Web服務或API應該支持它。 但是,如果您四處看看(甚至參考您過去或現在的經驗),您可能會發現大多數Web服務和API只是域模型周圍的CRUD包裝器,而沒有HATEOAS支持。 這是為什么? 可能有多個原因,但是從開發人員的工具箱角度來看, HATEOAS的支持不是那么好。
在今天的帖子中,我們將討論有關HATEOAS的 JAX-RS 2.x必須提供的內容 ,如何從服務器和客戶端的角度使用它以及如何增強OpenAPI v3.0.x規范以暴露超媒體。作為合同的一部分。 如果您很興奮,請讓我們開始吧。
因此,我們的JAX-RS Web API將圍繞管理公司及其員工而構建。 基礎是Spring Boot和Apache CXF ,其中Swagger是OpenAPI規范的實現。 AppConfig是我們需要定義的唯一配置,以啟動和運行應用程序(這要歸功于Spring Boot的自動配置功能)。
@SpringBootConfiguration public class AppConfig { @Bean OpenApiFeature createOpenApiFeature() { final OpenApiFeature openApiFeature = new OpenApiFeature(); openApiFeature.setSwaggerUiConfig( new SwaggerUiConfig().url( "/api/openapi.json" )); return openApiFeature; } ????@Bean JacksonJsonProvider jacksonJsonProvider() { return new JacksonJsonProvider(); } }Company和Person這個模型非常簡單(請注意,這兩個類之間沒有直接關系)。
public class Company { private String id; private String name; } public class Person { private String id; private String email; private String firstName; private String lastName; }該模型通過CompanyResource公開, CompanyResource是典型的JAX-RS資源類,帶有@Path注釋,此外還帶有OpenAPI的@Tag注釋。
@Component @Path ( "/companies" ) @Tag (name = "companies" ) public class CompanyResource { @Autowired private CompanyService service; }很好,資源類尚未定義端點,因此讓我們加強一下。 我們的第一個端點將通過標識符查找公司,并以JSON格式返回其表示形式。 但是,由于我們沒有包含任何與員工相關的細節,因此提示消費者(Web UI或任何其他客戶端)在哪里查找真是太棒了。 有多種方法可以執行此操作,但是由于我們堅持使用JAX-RS ,因此可以使用開箱即用的Web鏈接 ( RFC-5988 )。 該代碼段包含數千個單詞。
@Produces (MediaType.APPLICATION_JSON) @GET @Path ( "{id}" ) public Response getCompanyById( @Context UriInfo uriInfo, @PathParam ( "id" ) String id) { return service .findCompanyById(id) .map(company -> Response .ok(company) .links( Link.fromUriBuilder(uriInfo .getRequestUriBuilder()) .rel( "self" ) .build(), Link.fromUriBuilder(uriInfo .getBaseUriBuilder() .path(CompanyResource. class )) .rel( "collection" ) .build(), Link.fromUriBuilder(uriInfo .getBaseUriBuilder() .path(CompanyResource. class ) .path(CompanyResource. class , "getStaff" )) .rel( "staff" ) .build(id) ) .build()) .orElseThrow(() -> new NotFoundException( "The company with id '" + id + "' does not exists" )); }這里幾乎沒有發生任何事情。 我們關心的是使用ResponseBuilder :: links方法,其中提供了三個鏈接。 第一個是self ,它本質上是鏈接上下文(定義為RFC-5988的一部分)。 第二個是collection ,它指向CompanyResource端點,該端點返回公司列表(也包含在標準關系注冊表中)。 最后,第三個是我們自己的員工關系,我們通過一個名為getStaff的方法實現的另一CompanyResource端點組裝(我們將看到它不久)。 這些鏈接將在“ 鏈接”響應標頭中傳遞,并指導客戶端下一步去向。 讓我們通過運行該應用程序來實際查看它。
$ mvn clean package $ java -jar target/jax-rs- 2.1 -hateaos- 0.0 . 1 -SNAPSHOT.jar然后使用curl檢查來自此資源端點的響應(不必要的詳細信息已被濾除)。
$ curl -v http: //localhost:8080/api/companies/1 > GET /api/companies/ 1 HTTP/ 1.1 > Host: localhost: 8080 > User-Agent: curl/ 7.47 . 1 > Accept: */* > < HTTP/ 1.1 200 < Link: <http: //localhost:8080/api/companies/1>;rel="self" < Link: <http: //localhost:8080/api/companies/1/staff>;rel="staff" < Link: <http: //localhost:8080/api/companies>;rel="collection" < Content-Type: application/json < Transfer-Encoding: chunked < { "id" : "1" , "name" : "HATEOAS, Inc." }鏈接頭在那里,指的是其他感興趣的端點。 從客戶的角度來看,事情看起來也很簡單。 Response類提供專用的getLinks方法來包裝對Link響應標頭的訪問,例如:
final Client client = ClientBuilder.newClient(); try ( final Response response = client .target( " http://localhost:8080/api/companies/ {id}" ) .resolveTemplate( "id" , "1" ) .request() .accept(MediaType.APPLICATION_JSON) .get()) { ????????????final Optional staff = response .getLinks() .stream() .filter(link -> Objects.equals(link.getRel(), "staff" )) .findFirst(); ????????????staff.ifPresent(link -> { // follow the link here }); } finally { client.close(); }到目前為止,一切都很好。 展望未來,由于HATEOAS本質上是Web API合同的一部分,因此讓我們在桌上找出OpenAPI規范所具有的內容。 不幸的是, 到目前為止 尚不支持 HATEOAS ,但是從好的方面來說,存在鏈接的概念(盡管不應將它們與Web鏈接混淆,它們有些相似,但并不相同)。 為了說明作為OpenAPI規范一部分的鏈接的用法,讓我們用Swagger注釋裝飾端點。
@Operation ( description = "Find Company by Id" , responses = { @ApiResponse ( content = @Content (schema = @Schema (implementation = Company. class )), links = { @io .swagger.v3.oas.annotations.links.Link( name = "self" , operationRef = "#/paths/~1companies~1{id}/get" , description = "Find Company" , parameters = @LinkParameter (name = "id" , expression = "$response.body#/id" ) ), @io .swagger.v3.oas.annotations.links.Link( name = "staff" , operationRef = "#/paths/~1companies~1{id}~1staff/get" , description = "Get Company Staff" , parameters = @LinkParameter (name = "id" , expression = "$response.body#/id" ) ), @io .swagger.v3.oas.annotations.links.Link( name = "collection" , operationRef = "#/paths/~1companies/get" , description = "List Companies" ) }, description = "Company details" , responseCode = "200" ), @ApiResponse ( description = "Company does not exist" , responseCode = "404" ) } ) @Produces (MediaType.APPLICATION_JSON) @GET @Path ( "{id}" ) public Response getCompanyById( @Context UriInfo uriInfo, @PathParam ( "id" ) String id) { // ... }如果我們運行該應用程序并瀏覽到瀏覽器中的http:// localhost:8080 / api / api-docs (這是Swagger UI的托管位置),我們將能夠看到每個響應中的鏈接部分。
但是除此之外……您可以使用那里的鏈接做很多事情(如果您對該主題感興趣,請注意此問題 )。 吸引公司員工的資源終點看起來非常相似。
@Operation ( description = "Get Company Staff" , responses = { @ApiResponse ( content = @Content (array = @ArraySchema (schema = @Schema (implementation = Person. class ))), links = { @io .swagger.v3.oas.annotations.links.Link( name = "self" , operationRef = "#/paths/~1companies~1{id}~1staff/get" , description = "Staff" , parameters = @LinkParameter (name = "id" , expression = "$response.body#/id" ) ), @io .swagger.v3.oas.annotations.links.Link( name = "company" , operationRef = "#/paths/~1companies~1{id}/get" , description = "Company" , parameters = @LinkParameter (name = "id" , expression = "$response.body#/id" ) ) }, description = "The Staff of the Company" , responseCode = "200" ), @ApiResponse ( description = "Company does not exist" , responseCode = "404" ) } ) @Produces (MediaType.APPLICATION_JSON) @GET @Path ( "{id}/staff" ) public Response getStaff( @Context UriInfo uriInfo, @PathParam ( "id" ) String id) { return service .findCompanyById(id) .map(c -> service.getStaff(c)) .map(staff -> Response .ok(staff) .links( Link.fromUriBuilder(uriInfo .getRequestUriBuilder()) .rel( "self" ) .build(), Link.fromUriBuilder(uriInfo .getBaseUriBuilder() .path(CompanyResource. class ) .path(id)) .rel( "company" ) .build() ) .build()) .orElseThrow(() -> new NotFoundException( "The company with id '" + id + "' does not exists" )); }如您所料,除了指向self的鏈接之外,它還包括指向公司的鏈接。 當我們使用curl嘗試時,預期的響應標頭將返回。
$ curl -v http: //localhost:8080/api/companies/1/staff > GET /api/companies/ 1 /staff HTTP/ 1.1 > Host: localhost: 8080 > User-Agent: curl/ 7.47 . 1 > Accept: */* > < HTTP/ 1.1 200 < Link: <http: //localhost:8080/api/companies/1/staff>;rel="self" < Link: <http: //localhost:8080/api/companies/1>;rel="company" < Content-Type: application/json < Transfer-Encoding: chunked < [ { "id" : "1" , "email" : "john@smith.com" , "firstName" : "John" , "lastName" : "Smith" }, { "id" : "2" , "email" : "bob@smith.com" , "firstName" : "Bob" , "lastName" : "Smith" } ]那么我們可以得出什么樣的結論呢? HATEOAS實際上通過動態地驅動對話來統一Web API提供者和使用者之間的交互模型。 這非常強大,但是其中的大多數框架和工具要么都對HATEOAS提供了相當基本的支持(例如Web Linking ),要么根本沒有。
在很多情況下,只要使用Web鏈接就足夠了(到目前為止,我們已經看到了示例,例如分頁,導航等),但是假設創建,編輯或修補現有資源又如何呢? 如何用超媒體豐富集合中返回的各個元素(在RFC-6537中進行描述)? HATEOAS是否值得所有這些努力?
與往常一樣,答案是“取決于”,也許我們應該超越JAX-RS ? 在下一篇文章中(s_,我們將繼續解決問題。
完整的源代碼可在Github上找到 。
翻譯自: https://www.javacodegeeks.com/2019/02/hypermedia-apis-support-jax-rs-openapi.html
總結
以上是生活随笔為你收集整理的JAX-RS和OpenAPI对Hypermedia API的支持:任重而道远的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑内存扩展下载什么(电脑内存扩展下载什
- 下一篇: 高清电脑背景图片(电脑背景图片)