post-image

Hướng dẫn xây dựng API sử dụng HATEOAS

Spring Boot Starters & Common Configurations

HATEOAS là viết tắt của Hypermedia As The Engine Of Application State. HATEOAS là một trong những chuẩn được khuyến nghị nên sử dụng trong RESTful API. Bài viết này chúng ta sẽ cùng tìm hiểu về khái niệm này và làm 1 ví dụ về nó nhé ^^.

HATEOAS là gì ?

Đầu tiên để có thể làm được 1 ví dụ sử dụng HATEOAS thì chúng ta hãy cùng tìm hiểu qua về HATEOAS, để biết HATEOAS là gì.

Như mình đã nói ở trên, 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ụ sử dụng HATEOAS

Cài đặt thư viện

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-hateoas'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'mysql:mysql-connector-java'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}Code language: JavaScript (javascript)

Chuẩn bị

 Tạo model Student

Chúng ta sẽ tạo class Studentnhư sau:

Student.java

package com.example.demo.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Data
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String address;
}
Code language: CSS (css)

Tạo Repository StudentRepository

Tạo interface StudentRepository kế thừa interface JpaRepository có sẵn của Spring:

package com.example.demo.repository;

import com.example.demo.model.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface IStudentRepository extends JpaRepository<Student, Long> {
}
Code language: CSS (css)

Tạo Service cho Student

Interface IStudentService.java

package com.example.demo.service;

import com.example.demo.model.Student;

import java.util.Optional;

public interface IStudentService {
    Iterable<Student> findAll();

    Optional<Student> findById(Long id);

    Student save(Student student);

    void remove(Long id);
}
Code language: JavaScript (javascript)

StudentService.java

package com.example.demo.service;


import com.example.demo.model.Student;
import com.example.demo.repository.IStudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class StudentService implements IStudentService {
    @Autowired
    private IStudentRepository studentRepository;

    @Override
    public Iterable<Student> findAll() {
        return studentRepository.findAll();
    }

    @Override
    public Optional<Student> findById(Long id) {
        return studentRepository.findById(id);
    }

    @Override
    public Student save(Student student) {
        return studentRepository.save(student);
    }

    @Override
    public void remove(Long id) {
        studentRepository.deleteById(id);
    }
}
Code language: CSS (css)

Cấu hình class StudentNotFoundException

Chúng ta tạo một class StudentNotFoundException như sau:

package com.example.demo.exception;

public class StudentNotFoundException extends RuntimeException {

    public StudentNotFoundException(String exception) {
        super(exception);
    }

}Code language: PHP (php)

Tiến hành tạo StudentController

Chúng ta sẽ tạo StudentController với các API như sau:

package com.example.demo.controller;

import com.example.demo.exception.StudentNotFoundException;
import com.example.demo.model.Student;
import com.example.demo.service.IStudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;
import java.util.Optional;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

@RestController
@RequestMapping("/api/students")
public class StudentController {
    @Autowired
    private IStudentService studentService;

    @GetMapping
    public ResponseEntity<Iterable<Student>> getAllStudent() {
        return new ResponseEntity<>(studentService.findAll(), HttpStatus.OK);
    }

    @GetMapping("/{id}")
    public EntityModel<Student> getStudentDetail(@PathVariable Long id) {
        Optional<Student> studentOptional = studentService.findById(id);
        return studentOptional.map(student -> {
            EntityModel<Student> resource = EntityModel.of(studentOptional.get());

            WebMvcLinkBuilder linkTo = linkTo(methodOn(this.getClass()).getAllStudent());

            resource.add(linkTo.withRel("all-students"));

            return resource;
        }).orElseThrow(() -> new StudentNotFoundException("id:" + id));
    }

    @PostMapping
    public ResponseEntity<Object> createStudent(@RequestBody Student student) {
        Student savedStudent = studentService.save(student);

        URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
                .buildAndExpand(savedStudent.getId()).toUri();

        return ResponseEntity.created(location).build();
    }

    @PutMapping("/{id}")
    public ResponseEntity<Object> updateStudent(@RequestBody Student student, @PathVariable long id) {

        Optional<Student> studentOptional = studentService.findById(id);

        if (!studentOptional.isPresent())
            return ResponseEntity.notFound().build();

        student.setId(id);

        studentService.save(student);

        return ResponseEntity.noContent().build();
    }

    @DeleteMapping("/{id}")
    public void deleteStudent(@PathVariable long id) {
        studentService.remove(id);
    }
}
Code language: JavaScript (javascript)

Leave a Reply

Your email address will not be published. Required fields are marked *