GP Coder

Trang chia sẻ kiến thức lập trình Java

  • Java Core
    • Basic Java
    • OOP
    • Exception Handling
    • Multi-Thread
    • Java I/O
    • Networking
    • Reflection
    • Collection
    • Java 8
  • Design pattern
    • Creational Pattern
    • Structuaral Pattern
    • Behavior Pattern
  • Web Service
    • SOAP
    • REST
  • JPA
  • Java library
    • Report
    • Json
    • Unit Test
  • Message Queue
    • ActiveMQ
    • RabbitMQ
  • All
Trang chủ Java Webservice REST Giới thiệu HATEOAS

Giới thiệu HATEOAS

Đăng vào 25/07/2019 . Được đăng bởi GP Coder . 9862 Lượt xem . Toàn màn hình

Nội dung

  • 1 HATEOAS là gì?
  • 2 Ví dụ HATEOAS với JAX-RS

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 cho phép bạn sử dụng các liên kết hypermedia trong nội dung response để client có thể tự động điều hướng đến tài nguyên phù hợp bằng cách duyệt qua các liên kết hypermedia. Nó tương tự như một người dùng web điều hướng qua các trang web bằng cách nhấp vào các link thích hợp để chuyển đến nội dung mong muốn.

HATEOAS mong muốn 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 chỉ 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 cung cấp các REST API cho ứng dụng blog được mô 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 thiết kế này có vấn đề là chúng ta phải biết cấu trúc resource của server để gọi cho đúng. Nếu server thay đổi cấu trúc, phía client cũng phải thay đổi theo.

Bây giờ hãy xem cách HATEOAS giải quyết vấn đề này như sau:

  • JAX-RS cung cấp 2 class: UriInfo và Link 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)://<domain>:(port)/<YourApplicationName>/<UrlPattern in web.xml>/<path>
// http://localhost:8080/RestfulWebServiceExample/rest/articles
@Path("/articles")
@PermitAll
public class ArticleService {
	
	@GET
	@Path("/")
	public Response getArticles(@Context UriInfo uriInfo) {
		List
<Article> 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 kết quả:

@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 cấp link để truy xuất thông tin chi tiết của một article. Dựa vào đây chúng ta có thể gọi API tiếp mà không cần quan tâm cấu trúc resource trên server như thế nào.

Mở tab Headers, chúng ta có thông tin truy xuất các 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 xử lý để tính toán ra những hành động, hyperlink tương ứng khá phức tạp và bandwidth dành cho nó cũng không dễ chịu chút nào. Hiện tại không có nhiều ứng cung cấp REST API với HATEOAS. Nếu không cần thiết, hãy hỗ trợ HATEOAS là optional thông qua header.

Tài liệu tham khảo:

  • https://jersey.github.io/documentation/latest/uris-and-links.html
4.8
06
Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé! Và Donate tác giả

Shares

Chuyên mục: Java Webservice, REST Được gắn thẻ: REST, Webservice

Giới thiệu Feign – Tạo ứng dụng Java RESTful Client không thể đơn giản hơn
Giới thiệu Swagger – Công cụ document cho RESTfull APIs

Có thể bạn muốn xem:

  • SOAP Web service: Authentication trong JAX-WS (03/06/2019)
  • Giới thiệu Castle Mock – Mock REST APIs và SOAP web-services (05/09/2019)
  • SOAP Web service: Upload và Download file sử dụng MTOM trong JAX-WS (06/06/2019)
  • Tìm hiểu về xác thực và phân quyền trong ứng dụng (01/07/2019)
  • Triển khai ứng dụng Jersey REST Web service lên Tomcat Server (29/08/2019)

Bình luận

bình luận

Tìm kiếm

Bài viết mới

  • Clean code 13/01/2024
  • Giới thiệu CloudAMQP – Một RabbitMQ server trên Cloud 02/10/2020
  • Kết nối RabbitMQ sử dụng Web STOMP Plugin 19/06/2020
  • Sử dụng publisher confirm trong RabbitMQ 16/06/2020
  • Sử dụng Dead Letter Exchange trong RabbitMQ 13/06/2020

Xem nhiều

  • Hướng dẫn Java Design Pattern – Factory Method (97378 lượt xem)
  • Hướng dẫn Java Design Pattern – Singleton (97026 lượt xem)
  • Giới thiệu Design Patterns (86692 lượt xem)
  • Lập trình đa luồng trong Java (Java Multi-threading) (85526 lượt xem)
  • Giới thiệu về Stream API trong Java 8 (83067 lượt xem)

Nội dung bài viết

  • 1 HATEOAS là gì?
  • 2 Ví dụ HATEOAS với JAX-RS

Lưu trữ

Thẻ đánh dấu

Annotation Authentication Basic Java Behavior Pattern Collection Creational Design Pattern Cấu trúc điều khiển Database Dependency Injection Design pattern Eclipse Exception Executor Service Google Guice Gson Hibernate How to Interceptor IO Jackson Java 8 Java Core JDBC JDK Jersey JMS JPA json JUnit JWT Message Queue Mockito Multithreading OOP PowerMockito RabbitMQ Reflection Report REST SOAP Structuaral Pattern Swagger Thread Pool Unit Test Webservice

Liên kết

  • Clean Code
  • JavaTpoint
  • Refactoring Guru
  • Source Making
  • TutorialsPoint
  • W3Schools Online Web Tutorials

Giới thiệu

GP Coder là trang web cá nhân, được thành lập với mục đích lưu trữ, chia sẽ kiến thức đã học và làm việc của tôi. Các bài viết trên trang này chủ yếu về ngôn ngữ Java và các công nghệ có liên quan đến Java như: Spring, JSF, Web Services, Unit Test, Hibernate, SQL, ...
Hi vọng góp được chút ít công sức cho sự phát triển cộng đồng Coder Việt.

Donate tác giả

Tìm kiếm các bài viết của GP Coder với Google Search

Liên hệ

Các bạn có thể liên hệ với tôi thông qua:
  • Trang liên hệ
  • Linkedin: gpcoder
  • Email: contact@gpcoder.com
  • Skype: ptgiang56it

Follow me

Copyright 2025 © GP Coder · All Rights Reserved · Giới thiệu · Chính sách · Điều khoản · Liên hệ ·

Share

Blogger
Delicious
Digg
Email
Facebook
Facebook messenger
Flipboard
Google
Hacker News
Line
LinkedIn
Mastodon
Mix
Odnoklassniki
PDF
Pinterest
Pocket
Print
Reddit
Renren
Short link
SMS
Skype
Telegram
Tumblr
Twitter
VKontakte
wechat
Weibo
WhatsApp
X
Xing
Yahoo! Mail

Copy short link

Copy link