Hướng dẫn xây dựng API sử dụng HATEOAS
NỘI DUNG BÀI VIẾT
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