Giới thiệu HATEOAS – GP Coder (Lập trình Java)

TÓM TẮT

HATEOAS là gì ?

HATEOAS (Hypermedia As The Engine Of Application State) là một trong những chuẩn được khuyến nghị cho RESTful API. Thuật ngữ “Hypermedia” có nghĩa là bất kỳ nội dung nào có chứa các liên kết (link) đến các media khác như image, movie và text.

Kiểu kiến trúc này được cho phép bạn sử dụng những link hypermedia trong nội dung response để client hoàn toàn có thể tự động hóa điều hướng đến tài nguyên tương thích bằng cách duyệt qua những link hypermedia. Nó tương tự như như một người dùng web điều hướng qua những website bằng cách nhấp vào những link thích hợp để chuyển đến nội dung mong ước .HATEOAS mong ước phía client không cần biết chút nào về cấu trúc phía server, client chỉ cần request đến một URL duy nhất, rồi từ đó mọi đường đi nước bước tiếp theo sẽ do hướng dẫn của phía server trả về .

Ví dụ HATEOAS với JAX-RS

Giả sử chúng ta có một ứng dụng blog, một blog gồm nhiều bài viết, mỗi bài viết được viết bởi một tác giả nào đó. Ở mỗi bài viết có thể có nhiều bình luận và được đánh dấu một số tag. Sơ đồ class ở server như sau:

Hệ thống phân phối những REST API cho ứng dụng blog được diễn đạt như sau :

  • @GET /articles : lấy danh sách thông tin tất cả các article.
  • @GET /articles/1 : lấy thông tin một article có id=1
  • @GET /articles/1/comments : lấy danh sách comment của một article có id=1
  • @GET /articles/1/tags: lấy danh sách tag của một article có id=1

Response trả về khi gọi @GET /articles :

[
    {
        "id": 1,
        "content": "HATEOAS example by gpcoder",
        "publishedDate": "14/07/2019",
        "authorId": 1
    },
    {
        "id": 2,
        "content": "HATEOAS example by gpcoder",
        "publishedDate": "14/07/2019",
        "authorId": 1
    },
    {
        "id": 3,
        "content": "HATEOAS example by gpcoder",
        "publishedDate": "14/07/2019",
        "authorId": 1
    }
]

Để lấy thông tin chi tiết của article, chúng ta sẽ gọi tiếp @GET /articles/ + id của article. Tương tự chúng ta cũng cần ghép chuỗi comments, tags để có URL lấy danh sách comments, tags.

Cách phong cách thiết kế này có yếu tố là tất cả chúng ta phải biết cấu trúc resource của server để gọi cho đúng. Nếu server đổi khác cấu trúc, phía client cũng phải đổi khác theo .Bây giờ hãy xem cách HATEOAS xử lý yếu tố này như sau :

  • JAX-RS cung cấp 2 class: UriInfoLink builder để tạo ra các hypermedia Link.
  • Chúng ta có thể tạo tạo thêm một thuộc tính Link trong Model class để cung cấp hypermedia Link cho client hoặc gán trực tiếp chúng trong Header.

Article. java

package com.gpcoder.model;

import javax.ws.rs.core.Link;
import javax.xml.bind.annotation.XmlRootElement;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement
public class Article {

	private Integer id;
	private String content;
	private String publishedDate;
	private Integer authorId;
	 private Link self;
}

ArticleService. java

package com.gpcoder.api;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;

import javax.annotation.security.PermitAll;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import com.gpcoder.model.Article;

// URI:
// http(s)://:(port)///
// http://localhost:8080/RestfulWebServiceExample/rest/articles
@Path("/articles")
@PermitAll
public class ArticleService {
	
	@GET
	@Path("/")
	public Response getArticles(@Context UriInfo uriInfo) {
		List
articles = Arrays.asList( createArticle(1), createArticle(2), createArticle(3) ); for (Article article : articles) { Link selfLink = Link.fromUri(uriInfo.getAbsolutePath().resolve(article.getId().toString())).rel("self").type("GET").build(); article.setSelf(selfLink); } // http://localhost:8080/RestfulWebServiceExample/rest/articles Link selfLink = Link.fromUri(uriInfo.getAbsolutePath()).rel("self").type("GET").build(); // http://localhost:8080/RestfulWebServiceExample/rest/articles?page=2 Link nextLink = Link.fromUriBuilder(uriInfo.getAbsolutePathBuilder() .queryParam("page", "2")) .rel("next") .type("GET") .build(); // http://localhost:8080/RestfulWebServiceExample/rest/articles?page=0 Link prevLink = Link.fromUriBuilder(uriInfo.getAbsolutePathBuilder() .queryParam("page", "0")) .rel("prev") .type("GET") .build(); return Response.ok(articles).links(selfLink, nextLink, prevLink).build(); } @GET @Path("/{id}") public Response getArticle(@PathParam("id") int id, @Context UriInfo uriInfo) { Article article = createArticle(id); // http://localhost:8080/RestfulWebServiceExample/rest/articles/1 Link selfLink = Link.fromUri(uriInfo.getAbsolutePath().resolve(article.getId().toString())).rel("self").type("GET").build(); // http://localhost:8080/RestfulWebServiceExample/rest/articles/1/comments Link commentLink = Link.fromUriBuilder(uriInfo.getAbsolutePathBuilder() .path(article.getId().toString()).path("comments")) .rel("comments") .type("GET").build(); // http://localhost:8080/RestfulWebServiceExample/rest/articles/1/tags Link tagLink = Link.fromUriBuilder(uriInfo.getAbsolutePathBuilder() .path(article.getId().toString()).path("tags")) .rel("tags") .type("GET").build(); article.setSelf(selfLink); return Response.ok(article).links(selfLink, commentLink, tagLink).build(); } private Article createArticle(Integer id) { Article article = new Article(); article.setId(id); article.setContent("HATEOAS example by gpcoder"); article.setPublishedDate(LocalDate.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))); article.setAuthorId(1); return article; } @GET @Path("?page={page}") public Response getArticles(@QueryParam("page") int page) { return null; } @GET @Path("/{id}/comments") public Response getComments(@PathParam("id") int id) { return null; } @GET @Path("/{id}/tags") public Response getTags(@PathParam("id") int id) { return null; } }

Mở Postman để test tác dụng :

@GET http://localhost:8080/RestfulWebServiceExample/rest/articles

[
    {
        "id": 1,
        "content": "HATEOAS example by gpcoder",
        "publishedDate": "14/07/2019",
        "authorId": 1,
        "self": {
            "uri": "http://localhost:8080/RestfulWebServiceExample/rest/1",
            "params": {
                "rel": "self",
                "type": "GET"
            },
            "type": "GET",
            "rel": "self",
            "uriBuilder": {
                "absolute": true
            },
            "rels": [
                "self"
            ],
            "title": null
        }
    },
    {
        "id": 2,
        "content": "HATEOAS example by gpcoder",
        "publishedDate": "14/07/2019",
        "authorId": 1,
        "self": {
            "uri": "http://localhost:8080/RestfulWebServiceExample/rest/2",
            "params": {
                "rel": "self",
                "type": "GET"
            },
            "type": "GET",
            "rel": "self",
            "uriBuilder": {
                "absolute": true
            },
            "rels": [
                "self"
            ],
            "title": null
        }
    },
    {
        "id": 3,
        "content": "HATEOAS example by gpcoder",
        "publishedDate": "14/07/2019",
        "authorId": 1,
        "self": {
            "uri": "http://localhost:8080/RestfulWebServiceExample/rest/3",
            "params": {
                "rel": "self",
                "type": "GET"
            },
            "type": "GET",
            "rel": "self",
            "uriBuilder": {
                "absolute": true
            },
            "rels": [
                "self"
            ],
            "title": null
        }
    }
]

Như bạn thấy, ở mỗi article đều cung ứng link để truy xuất thông tin chi tiết cụ thể của một article. Dựa vào đây tất cả chúng ta hoàn toàn có thể gọi API tiếp mà không cần chăm sóc cấu trúc resource trên server như thế nào .Mở tab Headers, tất cả chúng ta có thông tin truy xuất những resource khác như phân trang : next, prev .

Tương tự: @GET http://localhost:8080/RestfulWebServiceExample/rest/articles/1

{
    "id": 1,
    "content": "HATEOAS example by gpcoder",
    "publishedDate": "14/07/2019",
    "authorId": 1,
    "self": {
        "uri": "http://localhost:8080/RestfulWebServiceExample/rest/articles/1",
        "params": {
            "rel": "self",
            "type": "GET"
        },
        "type": "GET",
        "rel": "self",
        "uriBuilder": {
            "absolute": true
        },
        "rels": [
            "self"
        ],
        "title": null
    }
}

Như bạn thấy HATEOAS rất hữu dụng, tuy nhiên việc giải quyết và xử lý để đo lường và thống kê ra những hành vi, hyperlink tương ứng khá phức tạp và bandwidth dành cho nó cũng không dễ chịu và thoải mái chút nào. Hiện tại không có nhiều ứng phân phối REST API với HATEOAS. Nếu không thiết yếu, hãy tương hỗ HATEOAS là optional trải qua header .

Tài liệu tham khảo:

4.8

Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé!

Shares

Bình luận

phản hồi

ĐÁNH GIÁ post
Bài viết liên quan

Tư vấn miễn phí (24/7) 094 179 2255