🌐 Раздел 6 · Вопрос #11

Что такое RESTful API дизайн

В основе RESTful дизайна лежит ресурсно-ориентированная архитектура (Resource-Oriented Architecture, ROA):


🟢 Junior Level

RESTful API дизайн — это подход к проектированию программных интерфейсов, базирующийся на архитектурных принципах REST (Representational State Transfer) и семантике протокола HTTP.

В основе RESTful дизайна лежит ресурсно-ориентированная архитектура (Resource-Oriented Architecture, ROA):

  1. Ресурсы (Resources) — это сущности предметной области (пользователи, заказы, товары). Ресурсы всегда идентифицируются через URI с использованием существительных во множественном числе (/users, /orders).
  2. Действия (Actions) — выражаются стандартными HTTP-методами (GET, POST, PUT, PATCH, DELETE).
  3. Результат операции — выражается стандартными HTTP статус-кодами (200, 201, 204, 400, 404, 500).

Канонический пример RESTful эндпоинтов

HTTP Метод URI Семантическое назначение Успешный HTTP статус
GET /api/v1/users Получить список пользователей (с пагинацией) 200 OK
GET /api/v1/users/42 Получить конкретного пользователя с ID 42 200 OK
POST /api/v1/users Создать нового пользователя 201 Created (+ Location)
PUT /api/v1/users/42 Полностью заменить данные пользователя 42 200 OK или 204 No Content
PATCH /api/v1/users/42 Частично обновить поля пользователя 42 200 OK
DELETE /api/v1/users/42 Удалить пользователя 42 204 No Content

Главные антипаттерны в именовании

  • ❌ GET /api/getUsers — глагол в URI дублирует HTTP-метод GET.
  • ❌ POST /api/deleteUser?id=42 — метод POST используется вместо идемпотентного DELETE.
  • ❌ POST /api/user/save — RPC-стиль в стиле старых SOAP-сервисов.

🟡 Middle Level

Модель зрелости Ричардсона (Richardson Maturity Model)

Леонард Ричардсон выделил 4 уровня соответствия веб-сервиса истинному REST:

┌────────────────────────────────────────────────────────┐
│  Level 3: HATEOAS (Hypermedia as the Engine of State)  │
├────────────────────────────────────────────────────────┤
│  Level 2: HTTP Verbs & Status Codes (Стандарт отрасли) │
├────────────────────────────────────────────────────────┤
│  Level 1: Resources & Individual URIs (/users/42)      │
├────────────────────────────────────────────────────────┤
│  Level 0: The Swamp of POX (RPC over HTTP POST)        │
└────────────────────────────────────────────────────────┘
  1. Level 0 (The Swamp of POX): HTTP используется как слепой транспортный туннель. Все запросы шлются на один URL (/api/service) методом POST (SOAP, XML-RPC, JSON-RPC).
  2. Level 1 (Resources): Появление раздельных URI для каждой сущности (/users/1, /orders/2), но взаимодействие идет через один или два метода (например, все действия — POST).
  3. Level 2 (HTTP Verbs & Status Codes): Золотой стандарт коммерческой разработки. Используются правильные HTTP методы (GET, POST, PUT, DELETE), статусы ответов (201, 204, 404) и заголовки.
  4. Level 3 (HATEOAS): Сервер вместе с данными возвращает ссылки на возможные дальнейшие действия клиента. В 95% промышленных систем считается избыточным из-за высокого оверхеда на парсинг и передачу ссылок.

Фильтрация, пагинация и сортировка

Для выборки подмножеств ресурсов используются Query Parameters, а не создание новых URI:

  • Пагинация (Offset / Limit): GET /api/v1/orders?offset=20&limit=10
  • Курсорная пагинация (Cursor-based): GET /api/v1/orders?cursor=eyJpZCI6MTAxfQ&limit=20 (критично для лент новостей и больших таблиц, исключает сдвиг записей).
  • Сортировка: GET /api/v1/products?sort=price,desc&sort=name,asc
  • Фильтрация: GET /api/v1/tickets?status=OPEN&priority=HIGH

Решение проблемы N+1 в REST (Sparse Fieldsets и Embedding)

В отличие от GraphQL, REST API часто страдает от избыточной выборки (Over-fetching) или проблемы N+1 HTTP-запросов. В RESTful дизайне это решается двумя паттернами:

  1. Sparse Fieldsets: выборка только нужных полей клиента: GET /api/v1/users/42?fields=id,email,displayName
  2. Resource Embedding: внедрение связанных сущностей: GET /api/v1/orders/101?embed=customer,items

🔴 Senior Level

Design-First (API-First) vs Code-First

В энтерпрайз-разработке выбор подхода определяет скорость и качество интеграции:

Критерий API-First (Design-First) Code-First
Методология Сначала пишется OpenAPI 3.1 спецификация (YAML), затем генерируется код. Сначала пишутся Spring контроллеры, документация генерируется SpringDoc.
Параллельная работа Фронтенд, мобильные клиенты и бэкенд разрабатываются одновременно по Mock-серверу (Prism). Клиентские команды вынуждены ждать написания бэкенда.
Соответствие контракту 100% гарантия совпадения документации и API. Линтинг через Spectral. Документация может расходиться с реальным поведением при сложных маппингах.
Инструменты openapi-generator-maven-plugin, Stoplight, SwaggerHub. springdoc-openapi-starter-webmvc-ui.

Архитектура Spring MVC: обработка REST запросов

За кулисами Spring Boot за обработку запроса отвечают:

  1. DispatcherServlet получает входящий HTTP запрос.
  2. RequestMappingHandlerMapping по таблице маршрутов находит подходящий метод класса @RestController.
  3. HandlerAdapter подготавливает вызов метода, активируя HandlerMethodArgumentResolver (парсинг @RequestBody, @PathVariable, @RequestParam).
  4. HttpMessageConverter (Jackson MappingJackson2HttpMessageConverter) десериализует тело запроса из JSON и сериализует ответ в JSON.

Образцовый Spring Boot 3 RESTful контроллер

package com.example.controller;

import com.example.dto.CreateUserRequest;
import com.example.dto.UpdateUserRequest;
import com.example.dto.UserResponseDto;
import com.example.service.UserService;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import java.net.URI;

@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    // 1. Получение коллекции с пагинацией и сортировкой: 200 OK
    @GetMapping
    public ResponseEntity<Page<UserResponseDto>> getUsers(
            @RequestParam(required = false) String status,
            @PageableDefault(size = 20, sort = "createdAt") Pageable pageable) {
        return ResponseEntity.ok(userService.findAll(status, pageable));
    }

    // 2. Получение одного ресурса: 200 OK
    @GetMapping("/{id}")
    public ResponseEntity<UserResponseDto> getUserById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }

    // 3. Создание ресурса: 201 Created + заголовок Location
    @PostMapping
    public ResponseEntity<UserResponseDto> createUser(@Valid @RequestBody CreateUserRequest request) {
        UserResponseDto created = userService.create(request);

        // Формируем URI созданного ресурса: /api/v1/users/{id}
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(created.id())
                .toUri();

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

    // 4. Полная замена: 200 OK
    @PutMapping("/{id}")
    public ResponseEntity<UserResponseDto> replaceUser(@PathVariable Long id, 
                                                      @Valid @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.update(id, request));
    }

    // 5. Удаление ресурса: 204 No Content
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

🎯 Шпаргалка для интервью

30-секундный ответ

RESTful API дизайн — это архитектурный подход к проектированию веб-сервисов, при котором API структурируется вокруг ресурсов (существительные во множественном числе), действия над ними выполняются стандартными HTTP-методами (GET, POST, PUT, PATCH, DELETE), а результат возвращается стандартными HTTP-статус-кодами.

Согласно модели Ричардсона (Richardson Maturity Model), современным промышленным стандартом является Level 2 (Resources + HTTP Verbs + Status Codes). Фильтрация и пагинация выносятся в query-параметры. В enterprise-проектах стандартом является подход API-First (Design-First) с описанием контракта в OpenAPI 3.1 до написания кода.

4 каверзных вопроса с ответами

  1. Что такое Richardson Maturity Model и почему Level 3 (HATEOAS) редко используется на практике? Ответ: Модель зрелости описывает путь от чистого RPC (Level 0) через ресурсы (Level 1) и HTTP-методы со статусами (Level 2) к гипермедиа HATEOAS (Level 3). HATEOAS редко применяется из-за высокого оверхеда на формирование графа ссылок, раздувания размера JSON на 30–50% и сложности его поддержки на мобильных клиентах, которым проще работать с фиксированными контрактами OpenAPI.

  2. Зачем при создании ресурса методом POST возвращать заголовок Location? Ответ: Заголовок Location: /api/v1/users/42 указывает клиенту точный канонический URI вновь созданного подчиненного ресурса в соответствии со спецификацией RFC 9110, позволяя клиенту сразу перейти к чтению объекта без ручной конкатенации строк.

  3. В чем разница между подходом Design-First и Code-First при проектировании REST API? Ответ: При Design-First сначала создается OpenAPI-контракт, согласовываются схемы данных между бэкендом, фронтендом и мобильными разработчиками, и только затем генерируется код. При Code-First разработчики сначала пишут контроллеры, а документация генерируется постфактум. Design-First минимизирует интеграционные риски и позволяет вести разработку параллельно.

  4. Как в RESTful дизайне правильно отобразить нестандартное действие, например, «заблокировать пользователя» или «перевести средства»? Ответ: Через подчиненные ресурсы состояний: POST /users/42/block-actions или изменение состояния через PATCH: PATCH /users/42 {"status": "BLOCKED"}. Для финансовых операций создается ресурс транзакции: POST /transfers с телом запроса, а не вызов глагола /users/42/transferMoney.

Красные флаги (чего категорически нельзя говорить)

  • ❌ «В REST API глаголы в URL — это признак хорошего тона для понятности» (глаголы в URI нарушают принципы REST; действие обязано выражаться HTTP-методом).
  • ❌ «Для всех операций нужно возвращать 200 OK, а статус ошибки передавать в поле JSON status» (ломает стандарт HTTP, лишает промежуточные кэши и мониторинг возможности отслеживать сбои).
  • ❌ «REST API всегда требует использования HATEOAS, иначе это не REST» (в прагматичной индустрии подавляющее большинство сервисов работают на Richardson Level 2).
  • ❌ «Query параметры и Path variables абсолютно взаимозаменяемы» (Path переменные идентифицируют ресурс, а Query параметры фильтруют, сортируют и настраивают пагинацию выборки).

Связанные темы