Что такое RESTful веб-сервис (REST API)?

«Что такое RESTful веб-сервис (REST API)?» — вопрос из категории Архитектура, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Основные принципы (ограничения) REST:

  1. Единообразие интерфейса (Uniform Interface):
    • Ресурсы: Все сущности (пользователи, заказы) представлены как ресурсы с уникальными URI (например, /api/users/123).
    • Представления (Representations): Клиент взаимодействует с ресурсом через его представление (JSON, XML). Один и тот же ресурс может иметь разные представления.
    • Самодостаточные сообщения (Self-descriptive messages): Каждый запрос/ответ содержит всю информацию, необходимую для его обработки (метод, заголовки, тело).
    • HATEOAS (Hypermedia as the Engine of Application State): В идеале, ответы API содержат гиперссылки на возможные следующие действия (например, ссылка на создание заказа).
  2. Отсутствие состояния (Stateless): Сервер не хранит состояние сессии клиента. Каждый запрос должен содержать всю необходимую для его выполнения аутентификацию и контекст (обычно через токены в заголовках).
  3. Кэшируемость (Cacheable): Ответы должны явно указывать, можно ли их кэшировать и как долго (через HTTP-заголовки Cache-Control).
  4. Клиент-серверная архитектура: Четкое разделение ответственности.
  5. Многоуровневая система (Layered System): Клиент не знает, общается ли он напрямую с сервером или через промежуточные узлы (прокси, балансировщики).

Пример RESTful контроллера на ASP.NET Core:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductRepository _repository;

    [HttpGet] // GET /api/products
    public async Task<ActionResult<IEnumerable<ProductDto>>> GetProducts()
        => Ok(await _repository.GetAllAsync());

    [HttpGet("{id}")] // GET /api/products/5
    public async Task<ActionResult<ProductDto>> GetProduct(int id)
    {
        var product = await _repository.GetByIdAsync(id);
        return product == null ? NotFound() : Ok(product);
    }

    [HttpPost] // POST /api/products
    public async Task<ActionResult<ProductDto>> CreateProduct([FromBody] CreateProductDto dto)
    {
        var createdProduct = await _repository.CreateAsync(dto);
        return CreatedAtAction(nameof(GetProduct), new { id = createdProduct.Id }, createdProduct);
    }

    [HttpPut("{id}")] // PUT /api/products/5 (полное обновление)
    public async Task<IActionResult> UpdateProduct(int id, [FromBody] UpdateProductDto dto)
    {
        var result = await _repository.UpdateAsync(id, dto);
        return result ? NoContent() : NotFound(); // 204 No Content при успехе
    }

    [HttpDelete("{id}")] // DELETE /api/products/5
    public async Task<IActionResult> DeleteProduct(int id)
    {
        var result = await _repository.DeleteAsync(id);
        return result ? NoContent() : NotFound();
    }
}

Преимущества: Масштабируемость, простота понимания, независимость от клиента, хорошая поддержка кэширования. Сложности: Строгое следование всем принципам (особенно HATEOAS) на практике часто упрощается, что приводит к спорам о "RESTfulness".