Что такое уровни зрелости REST?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое уровни зрелости 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.