Какая из концепций является ключевым для REST API?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Какая из концепций является ключевым для REST API?
REST (Representational State Transfer) — архитектурный стиль для построения веб-сервисов. Давайте разберём ключевые концепции, которые делают API действительно RESTful.
Основные ключевые концепции REST
1. Ресурсы (Resources) — САМАЯ КЛЮЧЕВАЯ КОНЦЕПЦИЯ
Ресурсы — это сердце REST. Всё в REST моделируется как ресурсы:
// Ресурс "User"
GET /api/v1/users/123
// Запрашиваем конкретного пользователя с ID 123
// Ресурс "Orders for User"
GET /api/v1/users/123/orders
// Запрашиваем заказы этого пользователя
// Ресурс "Comment on Post"
GET /api/v1/posts/456/comments/789
// Вложенный ресурс: комментарий 789 на посте 456
2. HTTP методы (Uniform Interface)
REST использует стандартные HTTP методы для операций:
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
// GET — получить ресурс (safe, idempotent)
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
// POST — создать новый ресурс (не idempotent)
@PostMapping
public User createUser(@RequestBody CreateUserRequest request) {
return userService.create(request);
}
// PUT — полностью заменить ресурс (idempotent)
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody UpdateUserRequest request) {
return userService.update(id, request);
}
// PATCH — частично обновить ресурс (может быть idempotent)
@PatchMapping("/{id}")
public User patchUser(@PathVariable Long id, @RequestBody JsonPatch patch) {
return userService.patch(id, patch);
}
// DELETE — удалить ресурс (idempotent)
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
}
3. Stateless (Без состояния)
Каждый запрос содержит всю информацию, необходимую для его обработки:
// Плохо - зависит от состояния сессии
@GetMapping("/next-page")
public Page<User> getNextPage() {
// Какая была предыдущая страница? Откуда мне знать?
return userService.getNextPage();
}
// Хорошо - вся информация в запросе
@GetMapping("/users")
public Page<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "name") String sort
) {
return userService.getUsers(page, size, sort);
}
4. Representation (Представление ресурсов)
Ресурсы отправляются в определённом представлении (обычно JSON):
// Один ресурс может быть представлен несколькими способами
@GetMapping(value = "/users/{id}", produces = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE
})
public User getUser(@PathVariable Long id,
@RequestHeader(value = "Accept") String acceptHeader) {
return userService.findById(id);
// Представление зависит от Accept header
}
5. HATEOAS (Hypermedia As The Engine Of Application State)
Ответы содержат ссылки на связанные ресурсы:
@GetMapping("/users/{id}")
public EntityModel<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return EntityModel.of(user,
linkTo(methodOn(UserController.class).getUser(id)).withSelfRel(),
linkTo(methodOn(UserController.class).getAllUsers()).withRel("users"),
linkTo(methodOn(OrderController.class).getUserOrders(id)).withRel("orders")
);
}
// Response:
// {
// "id": 1,
// "name": "John",
// "email": "john@example.com",
// "_links": {
// "self": { "href": "/api/v1/users/1" },
// "users": { "href": "/api/v1/users" },
// "orders": { "href": "/api/v1/users/1/orders" }
// }
// }
6. Content Negotiation
Сервер и клиент договариваются о формате данных:
@GetMapping("/users/{id}")
@JsonView(User.PublicView.class)
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
// Или через Accept header
@GetMapping(value = "/users/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public User getUserAsJson(@PathVariable Long id) { ... }
@GetMapping(value = "/users/{id}", produces = MediaType.APPLICATION_XML_VALUE)
public User getUserAsXml(@PathVariable Long id) { ... }
Практический пример RESTful API
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
@Autowired
private OrderService orderService;
// GET /api/v1/orders — список всех заказов (пагинация)
@GetMapping
public Page<Order> listOrders(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size
) {
return orderService.listOrders(page, size);
}
// GET /api/v1/orders/123 — получить конкретный заказ
@GetMapping("/{id}")
public Order getOrder(@PathVariable UUID id) {
return orderService.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
}
// POST /api/v1/orders — создать новый заказ
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Order createOrder(@Valid @RequestBody CreateOrderRequest request) {
return orderService.create(request);
}
// PUT /api/v1/orders/123 — полностью обновить заказ
@PutMapping("/{id}")
public Order updateOrder(@PathVariable UUID id,
@Valid @RequestBody UpdateOrderRequest request) {
return orderService.update(id, request);
}
// PATCH /api/v1/orders/123 — частично обновить (например, только статус)
@PatchMapping("/{id}/status")
public Order updateOrderStatus(@PathVariable UUID id,
@RequestBody OrderStatus newStatus) {
return orderService.updateStatus(id, newStatus);
}
// DELETE /api/v1/orders/123 — удалить заказ
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteOrder(@PathVariable UUID id) {
orderService.delete(id);
}
// GET /api/v1/orders/123/items — вложенный ресурс
@GetMapping("/{orderId}/items")
public List<OrderItem> getOrderItems(@PathVariable UUID orderId) {
return orderService.getItems(orderId);
}
}
HTTP Status Codes — важная часть REST
@RestControllerAdvice
public class GlobalExceptionHandler {
// 200 OK — успешный GET
// 201 Created — успешный POST (создан ресурс)
// 204 No Content — успешный DELETE или PATCH без тела ответа
@ExceptionHandler(OrderNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND) // 404
public ErrorResponse handleNotFound(OrderNotFoundException e) {
return new ErrorResponse("Order not found", e.getMessage());
}
@ExceptionHandler(InvalidOrderException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST) // 400
public ErrorResponse handleBadRequest(InvalidOrderException e) {
return new ErrorResponse("Invalid order", e.getMessage());
}
@ExceptionHandler(UnauthorizedException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED) // 401
public ErrorResponse handleUnauthorized(UnauthorizedException e) {
return new ErrorResponse("Unauthorized", e.getMessage());
}
@ExceptionHandler(ForbiddenException.class)
@ResponseStatus(HttpStatus.FORBIDDEN) // 403
public ErrorResponse handleForbidden(ForbiddenException e) {
return new ErrorResponse("Forbidden", e.getMessage());
}
}
Best Practices для REST API
-
Используй существительные для URL, не глаголы:
- ✅
POST /users(создать) - ✅
DELETE /users/123(удалить) - ❌
GET /getUsers - ❌
POST /createUser
- ✅
-
Версионируй API:
/api/v1/,/api/v2/ -
Используй правильные HTTP статусы — это часть REST контракта
-
Документируй через Swagger/OpenAPI
-
Кэширование: используй
Cache-ControlиETagзаголовки -
Paging и Filtering:
?page=0&size=20&sort=name,desc
Главное: REST основана на идее стандартизации использования HTTP для взаимодействия с ресурсами. Ключевая концепция — это именно Ресурсы, к которым применяются стандартные HTTP методы.