← Назад к вопросам

Что такое уровни зрелости REST?

2.0 Middle🔥 241 комментариев
#REST API и микросервисы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое уровни зрелости REST?

Уровни зрелости REST (Richardson Maturity Model) — это модель, разработанная Леонардом Ричардсоном, которая описывает четыре уровня эволюции API, от простого к полнофункциональному REST API. Каждый уровень добавляет новые возможности и ограничения.

Визуальная пирамида уровней

        ┌─────────────────────────┐
        │  Level 3: Hypermedia    │ TRUE REST
        ├─────────────────────────┤
        │ Level 2: HTTP Verbs     │
        ├─────────────────────────┤
        │ Level 1: Resources      │
        ├─────────────────────────┤
        │ Level 0: POX            │ Not REST
        └─────────────────────────┘

Level 0: POX (Plain Old XML)

POX (Plain Old XML/JSON) — это не REST. Это простое использование HTTP как транспорта.

Характеристики:

  • Один единственный URL endpoint
  • Все операции через POST
  • Параметры в body
  • Не используется HTTP семантика
// Level 0 пример
@RestController
public class OrderController {
    // Один endpoint для всего
    @PostMapping("/api")
    public Response handleRequest(@RequestBody RequestPayload payload) {
        if (payload.getAction().equals("getOrder")) {
            return getOrder(payload.getOrderId());
        } else if (payload.getAction().equals("createOrder")) {
            return createOrder(payload.getOrder());
        } else if (payload.getAction().equals("deleteOrder")) {
            return deleteOrder(payload.getOrderId());
        }
        return new ErrorResponse("Unknown action");
    }
    
    private Response getOrder(Long orderId) { ... }
    private Response createOrder(Order order) { ... }
    private Response deleteOrder(Long orderId) { ... }
}

// Клиентский код
public class Client {
    public void getOrder() {
        RequestPayload payload = new RequestPayload();
        payload.setAction("getOrder");
        payload.setOrderId(123L);
        
        Response response = restTemplate.postForObject("/api", payload, Response.class);
    }
    
    public void createOrder() {
        RequestPayload payload = new RequestPayload();
        payload.setAction("createOrder");
        payload.setOrder(new Order("iPhone 15"));
        
        Response response = restTemplate.postForObject("/api", payload, Response.class);
    }
}

// JSON payload примеры:
// GET: {"action": "getOrder", "orderId": 123}
// CREATE: {"action": "createOrder", "order": {"name": "iPhone"}}
// DELETE: {"action": "deleteOrder", "orderId": 123}

Проблемы Level 0:

  • HTTP не используется эффективно
  • Сложно кэшировать
  • Сложно маршрутизировать
  • Не масштабируется

Level 1: Resources

Level 1 вводит ресурсы — разные URL endpoints для разных сущностей.

Характеристики:

  • Несколько URL endpoints (по одному на ресурс)
  • Всё ещё используются в основном POST
  • Начало RPC thinking в REST
// Level 1 пример
@RestController
@RequestMapping("/api")
public class ResourceController {
    // Разные endpoints для разных ресурсов
    
    @PostMapping("/orders")
    public Response createOrder(@RequestBody Order order) {
        return new SuccessResponse(orderService.save(order));
    }
    
    @PostMapping("/orders/{id}")
    public Response getOrder(@PathVariable Long id) {
        return new SuccessResponse(orderService.findById(id));
    }
    
    @PostMapping("/orders/{id}/delete")
    public Response deleteOrder(@PathVariable Long id) {
        orderService.deleteById(id);
        return new SuccessResponse("Deleted");
    }
    
    @PostMapping("/products")
    public Response createProduct(@RequestBody Product product) {
        return new SuccessResponse(productService.save(product));
    }
    
    @PostMapping("/products/{id}")
    public Response getProduct(@PathVariable Long id) {
        return new SuccessResponse(productService.findById(id));
    }
}

// Клиентский код
public class Level1Client {
    public void getOrder(Long id) {
        // Разные URL для разных ресурсов
        Response response = restTemplate.postForObject(
            "/api/orders/" + id, 
            null, 
            Response.class
        );
    }
    
    public void createOrder(Order order) {
        Response response = restTemplate.postForObject(
            "/api/orders", 
            order, 
            Response.class
        );
    }
}

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

  • Более организованно
  • Видна структура ресурсов
  • Легче понять API

Проблемы:

  • Всё ещё используется POST для всего
  • HTTP методы не используются

Level 2: HTTP Verbs

Level 2 используёт HTTP методы (GET, POST, PUT, DELETE) правильно.

Характеристики:

  • Разные URL для разных ресурсов
  • Правильное использование HTTP методов
  • Правильные HTTP status codes
  • Всё ещё нет гиперссылок
// Level 2 пример
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    // GET — получение списка
    @GetMapping
    public ResponseEntity<List<Order>> getAllOrders() {
        List<Order> orders = orderService.findAll();
        return ResponseEntity.ok(orders);
    }
    
    // GET — получение одного ресурса
    @GetMapping("/{id}")
    public ResponseEntity<Order> getOrder(@PathVariable Long id) {
        Order order = orderService.findById(id)
            .orElseThrow(() -> new OrderNotFoundException(id));
        return ResponseEntity.ok(order);
    }
    
    // POST — создание нового ресурса
    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody Order order) {
        Order created = orderService.save(order);
        // Правильный status code: 201 Created
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
    
    // PUT — полное обновление ресурса
    @PutMapping("/{id}")
    public ResponseEntity<Order> updateOrder(
            @PathVariable Long id, 
            @RequestBody Order order) {
        Order updated = orderService.update(id, order);
        return ResponseEntity.ok(updated);
    }
    
    // PATCH — частичное обновление ресурса
    @PatchMapping("/{id}")
    public ResponseEntity<Order> partialUpdate(
            @PathVariable Long id, 
            @RequestBody Map<String, Object> updates) {
        Order updated = orderService.partialUpdate(id, updates);
        return ResponseEntity.ok(updated);
    }
    
    // DELETE — удаление ресурса
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteOrder(@PathVariable Long id) {
        orderService.deleteById(id);
        // Status code: 204 No Content
        return ResponseEntity.noContent().build();
    }
}

// Правильные HTTP status codes
public class StatusCodes {
    // Success
    200 OK          // GET, PUT, PATCH успешны
    201 Created     // POST успешен
    204 No Content  // DELETE успешен
    
    // Client errors
    400 Bad Request     // Неверный запрос
    404 Not Found       // Ресурс не найден
    409 Conflict        // Конфликт (например, дубликат)
    
    // Server errors
    500 Internal Server Error
}

Клиентский код Level 2:

public class Level2Client {
    private final RestTemplate restTemplate = new RestTemplate();
    
    public void demonstrateLevel2() {
        String baseUrl = "https://api.example.com/orders";
        
        // GET — получить ресурс
        ResponseEntity<Order> getResponse = restTemplate.getForEntity(
            baseUrl + "/123", 
            Order.class
        );
        System.out.println("GET " + baseUrl + "/123: " + getResponse.getStatusCode());
        // 200 OK
        
        // POST — создать ресурс
        Order newOrder = new Order("iPhone 15");
        ResponseEntity<Order> postResponse = restTemplate.postForEntity(
            baseUrl, 
            newOrder, 
            Order.class
        );
        System.out.println("POST " + baseUrl + ": " + postResponse.getStatusCode());
        // 201 Created
        
        // PUT — обновить ресурс
        Order update = new Order("iPhone 15 Pro");
        restTemplate.put(baseUrl + "/123", update);
        // 200 OK
        
        // DELETE — удалить ресурс
        restTemplate.delete(baseUrl + "/123");
        // 204 No Content
    }
}

Преимущества Level 2:

  • Понятнее и стандартнее
  • Легче кэшировать (GET безопасен)
  • Правильные semantics
  • Проксирование работает

Проблемы:

  • Нет гиперссылок
  • Клиент должен знать URL структуру
  • Нет информации о доступных операциях

Level 3: Hypermedia (TRUE REST)

Level 3 вводит HATEOAS (Hypermedia As The Engine Of Application State) — гиперссылки, которые подсказывают, какие действия доступны.

Характеристики:

  • Все features Level 2
  • Плюс гиперссылки (links)
  • Клиент может открыть API и следовать ссылкам
  • Самоописывающийся API
// HATEOAS Response DTO
public class OrderResource {
    private Long id;
    private String description;
    private LocalDateTime createdAt;
    
    // Гиперссылки
    private List<Link> links = new ArrayList<>();
    
    public OrderResource(Order order) {
        this.id = order.getId();
        this.description = order.getDescription();
        this.createdAt = order.getCreatedAt();
    }
    
    public void addLink(String rel, String href) {
        links.add(new Link(rel, href));
    }
    
    public static class Link {
        public String rel;    // Отношение (self, next, prev, collection)
        public String href;   // URL
        
        public Link(String rel, String href) {
            this.rel = rel;
            this.href = href;
        }
    }
}

// Level 3 Controller
@RestController
@RequestMapping("/api/orders")
public class Level3OrderController {
    
    @GetMapping("/{id}")
    public ResponseEntity<OrderResource> getOrder(@PathVariable Long id) {
        Order order = orderService.findById(id)
            .orElseThrow(() -> new OrderNotFoundException(id));
        
        OrderResource resource = new OrderResource(order);
        
        // Добавляем гиперссылки
        resource.addLink("self", "/api/orders/" + id);
        resource.addLink("collection", "/api/orders");
        
        // Доступные действия
        resource.addLink("update", "/api/orders/" + id);
        resource.addLink("delete", "/api/orders/" + id);
        
        if (order.canTransition("ship")) {
            resource.addLink("ship", "/api/orders/" + id + "/ship");
        }
        
        if (order.canTransition("cancel")) {
            resource.addLink("cancel", "/api/orders/" + id + "/cancel");
        }
        
        return ResponseEntity.ok(resource);
    }
}

JSON Response (Level 3):

{
  "id": 123,
  "description": "iPhone 15",
  "createdAt": "2024-01-15T10:30:00Z",
  "links": [
    {
      "rel": "self",
      "href": "/api/orders/123"
    },
    {
      "rel": "collection",
      "href": "/api/orders"
    },
    {
      "rel": "update",
      "href": "/api/orders/123"
    },
    {
      "rel": "delete",
      "href": "/api/orders/123"
    },
    {
      "rel": "ship",
      "href": "/api/orders/123/ship"
    },
    {
      "rel": "cancel",
      "href": "/api/orders/123/cancel"
    }
  ]
}

Клиентский код (Level 3):

public class Level3Client {
    public void demonstrateDiscovery() {
        // 1. Получаем ресурс
        OrderResource order = restTemplate.getForObject(
            "/api/orders/123", 
            OrderResource.class
        );
        
        // 2. Клиент смотрит доступные ссылки
        OrderResource.Link shipLink = order.findLink("ship");
        
        // 3. Если доступно, выполняем операцию
        if (shipLink != null) {
            restTemplate.postForEntity(shipLink.href, null, Void.class);
        }
        
        // Клиент не знает предварительно URL!
        // API сама подсказывает, что доступно
    }
}

Преимущества Level 3:

  • Истинный REST
  • Клиент может быть независимым
  • API может меняться, не ломая клиентов
  • Самоописывающийся
  • Видны доступные операции

Проблемы:

  • Сложнее реализовать
  • Complexity

Таблица сравнения

            │ Level 0 │ Level 1 │ Level 2 │ Level 3
────────────┼─────────┼─────────┼─────────┼─────────
Resources   │   ✗     │   ✓     │   ✓     │   ✓
HTTP Verbs  │   ✗     │   ✗     │   ✓     │   ✓
HATEOAS     │   ✗     │   ✗     │   ✗     │   ✓
REST        │   ✗     │   ✗     │   ~     │   ✓
────────────┴─────────┴─────────┴─────────┴─────────

Когда использовать каждый уровень

Level 0: Только для простых скриптов и legacy систем
Level 1: Старые API, RPC-стиль с URL структурой
Level 2: Современные REST API (большинство используют)
Level 3: Требуется максимальная гибкость и эволюция API

Большинство современных REST API находятся на Level 2, тогда как истинный REST (Level 3) используется редко из-за сложности реализации. Однако понимание этой модели важно для проектирования качественных API.