🌐 Розділ 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 з тілом запиту, а не виклик RPC-дієслова /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 параметри фільтрують, сортують і налаштовують пагінацію вибірки).

Пов’язані теми