RESTful API设计
RESTful API设计
资源建模
RESTful API以资源为中心,通过URI标识资源,HTTP方法表示操作。
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
@PostMapping
public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserRequest request) {
UserDTO user = userService.create(request);
URI location = URI.create("/api/v1/users/" + user.getId());
return ResponseEntity.created(location).body(user);
}
@PutMapping("/{id}")
public ResponseEntity<UserDTO> updateUser(@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
return ResponseEntity.ok(userService.update(id, request));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
HATEOAS超媒体
通过HATEOAS让API响应包含相关操作的链接,实现自描述性。
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@GetMapping("/{id}")
public EntityModel<UserDTO> getUser(@PathVariable Long id) {
UserDTO user = userService.findById(id);
EntityModel<UserDTO> model = EntityModel.of(user);
model.add(linkTo(methodOn(UserController.class).getUser(id)).withSelfRel());
model.add(linkTo(methodOn(UserController.class).listUsers()).withRel("users"));
model.add(linkTo(methodOn(UserController.class).deleteUser(id)).withRel("delete"));
return model;
}
}
状态码规范
正确使用HTTP状态码表达API语义。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("NOT_FOUND", e.getMessage()));
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidation(ValidationException e) {
return ResponseEntity.badRequest()
.body(new ErrorResponse("VALIDATION_ERROR", e.getMessage()));
}
@ExceptionHandler(DuplicateResourceException.class)
public ResponseEntity<ErrorResponse> handleConflict(DuplicateResourceException e) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(new ErrorResponse("CONFLICT", e.getMessage()));
}
}
分页与过滤
统一的分页响应格式和过滤查询参数设计。
@GetMapping
public ResponseEntity<PagedModel<UserDTO>> listUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String sort) {
PageRequest pageRequest = PageRequest.of(page, size, Sort.by(sort));
Page<UserDTO> users = userService.findAll(pageRequest);
PagedModel.PageMetadata metadata = new PagedModel.PageMetadata(
size, page, users.getTotalElements(), users.getTotalPages());
PagedModel<UserDTO> pagedModel = PagedModel.of(users.getContent(), metadata);
return ResponseEntity.ok(pagedModel);
}