Gymterview
middle

Что такое HATEOAS?

HATEOAS (Hypermedia as the Engine of Application State) — принцип REST, согласно которому сервер в ответе предоставляет клиенту ссылки на доступные действия и связанные ресурсы.

Аналогия из жизни: HATEOAS — как навигация в интернет-магазине. Вы открываете страницу товара, и на ней есть ссылки: «Добавить в корзину», «Похожие товары», «Отзывы». Вам не нужно знать URL-ы заранее — страница сама подсказывает, что можно сделать дальше.

Обычный ответ без HATEOAS:

Пример
{
  "id": 42,
  "name": "Иван",
  "status": "active"
}

Ответ с HATEOAS:

Пример
{
  "id": 42,
  "name": "Иван",
  "status": "active",
  "_links": {
    "self": {"href": "/api/users/42"},
    "orders": {"href": "/api/users/42/orders"},
    "deactivate": {"href": "/api/users/42/deactivate", "method": "POST"}
  }
}

Преимущества

  • Клиент не зависит от жёстко заданных URL.
  • API становится самодокументируемым.
  • Сервер может динамически управлять доступными действиями (например, скрывать ссылку deactivate для уже неактивного пользователя).
Реализация в Spring HATEOAS
@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public EntityModel<User> getUser(@PathVariable Long id) {
        User user = userService.findById(id);

        EntityModel<User> model = EntityModel.of(user);
        model.add(linkTo(methodOn(UserController.class).getUser(id)).withSelfRel());
        model.add(linkTo(methodOn(OrderController.class).getOrdersByUser(id))
                  .withRel("orders"));

        if ("active".equals(user.getStatus())) {
            model.add(linkTo(methodOn(UserController.class).deactivateUser(id))
                      .withRel("deactivate"));
        }

        return model;
    }

    @GetMapping
    public CollectionModel<EntityModel<User>> getAllUsers() {
        List<EntityModel<User>> users = userService.findAll().stream()
            .map(user -> EntityModel.of(user,
                linkTo(methodOn(UserController.class).getUser(user.getId())).withSelfRel()))
            .toList();

        return CollectionModel.of(users,
            linkTo(methodOn(UserController.class).getAllUsers()).withSelfRel());
    }
}

Зависимость:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

На практике HATEOAS используется нечасто из-за сложности реализации и того, что большинство клиентов (SPA, мобильные приложения) проще строить с заранее известными URL.

На собеседовании: нужно объяснить принцип и привести пример ответа с _links. Бонус — упомянуть, что HATEOAS — это уровень 3 Richardson Maturity Model и что на практике он редко реализуется полностью.