Поделиться через


Рекомендации по проектированию веб-API RESTful

Реализация веб-API RESTful — это веб-API, который использует принципы архитектуры REST для достижения без отслеживания состояния, слабо зависимого интерфейса между клиентом и службой. Веб-API, который является RESTful, поддерживает стандартный протокол HTTP для выполнения операций с ресурсами и возврата представлений ресурсов, содержащих ссылки гипермедиа и коды состояния операции HTTP.

Веб-API RESTful должно соответствовать следующим принципам.

  • Независимость платформы, что означает, что клиенты могут вызывать веб-API независимо от внутренней реализации. Для обеспечения независимости платформы веб-API использует HTTP в качестве стандартного протокола, предоставляет четкую документацию и поддерживает знакомый формат обмена данными, например JSON или XML.

  • Свободное взаимодействие, что означает, что клиент и веб-служба могут развиваться независимо. Клиенту не нужно знать внутреннюю реализацию веб-службы, а веб-служба не должна знать внутреннюю реализацию клиента. Чтобы добиться свободного взаимодействия в веб-API RESTful, используйте только стандартные протоколы и реализуйте механизм, позволяющий клиенту и веб-службе согласиться с форматом данных для обмена.

В этой статье описаны рекомендации по проектированию веб-API RESTful. В нем также рассматриваются распространенные шаблоны и рекомендации по созданию веб-API, которые легко понять, гибко и поддерживать.

Основные понятия проектирования веб-API RESTful

Чтобы реализовать веб-API RESTful, необходимо понять следующие понятия.

  • Универсальный идентификатор ресурса (URI): ИНТЕРФЕЙСы REST API предназначены для ресурсов, которые представляют собой любой объект, данные или службу, к которым клиент может получить доступ. Каждый ресурс представлен URI, который однозначно идентифицирует этот ресурс. Например, URI для определенного заказа клиента может быть следующим:

    https://api.contoso.com/orders/1
    
  • Представление ресурсов определяет, как ресурс, определяемый его URI, кодируется и транспортируется по протоколу HTTP в определенном формате, например XML или JSON. Клиенты, которые хотят получить определенный ресурс, должны использовать URI ресурса в запросе к API. API возвращает представление ресурсов данных, указывающих URI. Например, клиент может отправить запрос GET к идентификатору https://api.contoso.com/orders/1 URI, чтобы получить следующий текст JSON:

    {"orderId":1,"orderValue":99.9,"productId":1,"quantity":1}
    
  • Универсальный интерфейс заключается в том, как API RESTful обеспечивают свободное взаимодействие между реализацией клиента и службы. Для ИНТЕРФЕЙСов REST API, созданных на основе HTTP, универсальный интерфейс включает использование стандартных http-команд для выполнения таких операций, как GET, , POSTи PUTPATCHDELETE ресурсов.

  • Модель запроса без отслеживания состояния: API RESTful используют модель запроса без отслеживания состояния, что означает, что HTTP-запросы независимы и могут выполняться в любом порядке. По этой причине сохранение временных сведений о состоянии между запросами невозможно. Единственное место, где хранятся сведения, находится в самих ресурсах, и каждый запрос должен быть атомарной операцией. Модель запросов без состояния поддерживает высокую масштабируемость, так как не требуется сохранять привязку между клиентами и конкретными серверами. Однако модель без отслеживания состояния также может ограничить масштабируемость из-за проблем с масштабируемостью внутреннего хранилища веб-службы. Дополнительные сведения о стратегиях масштабирования хранилища данных см. в разделе "Секционирование данных".

  • Ссылки гипермедиа: ИНТЕРФЕЙСы REST API могут управляться ссылками гипермедиа, содержащимися в каждом представлении ресурсов. Например, в следующем блоке кода показано представление порядка в формате JSON. Он содержит ссылки для получения или обновления информации о клиенте, связанном с заказом.

    {
      "orderID":3,
      "productID":2,
      "quantity":4,
      "orderValue":16.60,
      "links": [
        {"rel":"product","href":"https://api.contoso.com/customers/3", "action":"GET" },
        {"rel":"product","href":"https://api.contoso.com/customers/3", "action":"PUT" }
      ]
    }
    

Определите URI ресурсов веб-API RESTful

Веб-API RESTful основывается на ресурсах. Чтобы упорядочить проектирование API вокруг ресурсов, определите URI ресурсов, которые сопоставляют с бизнес-сущностями. По возможности создавайте URI ресурсов, основываясь на существительных (ресурсов), а не на глаголах (операции над ресурсом).

Например, в системе электронной коммерции основные бизнес-сущности могут быть клиентами и заказами. Чтобы создать заказ, клиент отправляет сведения о заказе в HTTP-запрос POST в URI ресурса. Http-ответ на запрос указывает, успешно ли выполнено создание заказа.

Универсальный код ресурса (URI) для создания ресурса заказа может быть примерно таким:

https://api.contoso.com/orders // Good

Избегайте использования глаголов в URI для представления операций. Например, не рекомендуется использовать следующий универсальный код ресурса (URI):

https://api.contoso.com/create-order // Avoid

Сущности часто группируются в такие коллекции, как клиенты или заказы. Коллекция — это отдельный ресурс из элементов внутри коллекции, поэтому он должен иметь собственный универсальный код ресурса (URI). Например, следующий универсальный код ресурса (URI) может представлять коллекцию заказов:

https://api.contoso.com/orders

После получения коллекции клиент может выполнить запрос GET к URI каждого элемента. Например, чтобы получить сведения о определенном заказе, клиент выполняет GET-запрос HTTP по URI https://api.contoso.com/orders/1, чтобы получить следующий текст JSON как представление внутренних данных заказа.

{"orderId":1,"orderValue":99.9,"productId":1,"quantity":1}

Соглашения об именовании URI для ресурсов

При разработке веб-API RESTful важно использовать правильные соглашения об именовании и отношениях для ресурсов:

  • Используйте существительные для имен ресурсов. Используйте существительные для представления ресурсов. Например, используйте функцию /orders вместо /create-order. Методы HTTP GET, POST, PUT, PATCH и DELETE уже подразумевают словесное действие.

  • Используйте множественные существительные для обозначения URI коллекций. Как правило, рекомендуется использовать множественные существительные для URI, ссылающихся на коллекции. Рекомендуется организовать URI для коллекций и элементов в иерархию. Например, /customers это путь к коллекции клиента и /customers/5 путь к клиенту с идентификатором, равным 5. Этот подход помогает обеспечить интуитивно понятный веб-API. Кроме того, многие платформы веб-API могут направлять запросы на основе параметризованных путей URI, поэтому можно определить маршрут для пути /customers/{id}.

  • Рассмотрим связи между различными типами ресурсов и способы раскрытия этих связей. Например, /customers/5/orders может представлять все заказы для клиента 5. Вы также можете приблизиться к отношениям в другом направлении, представляя ассоциацию от заказа к клиенту. В этом сценарии URI может быть /orders/99/customer. Однако расширение этой модели слишком далеко может стать громоздким для реализации. Лучший подход — включить ссылки в текст сообщения HTTP-ответа, чтобы клиенты могли легко получить доступ к связанным ресурсам. Используйте гипертекст как механизм состояния приложения (HATEOAS), чтобы включить навигацию по связанным ресурсам. Это подробнее описывает этот механизм.

  • Сохраняйте связи простыми и гибкими. В более сложных системах может потребоваться предоставить URI, которые позволяют клиенту перемещаться по нескольким уровням связей, например /customers/1/orders/99/products. Однако этот уровень сложности может оказаться трудным для поддержания и является негибким, если отношения между ресурсами изменяются в будущем. Попробуйте сохранить URIs довольно простыми. После того как приложение имеет ссылку на ресурс, вы сможете использовать эту ссылку для поиска элементов, связанных с этим ресурсом. Предыдущий запрос можно заменить URI /customers/1/orders , чтобы найти все заказы для клиента 1, а затем использовать /orders/99/products для поиска продуктов в этом порядке.

    Подсказка

    Избегайте необходимости использования URI ресурсов, которые более сложные, чем коллекция/элемент/коллекция.

  • Избегайте большого количества небольших ресурсов. Все веб-запросы накладывают нагрузку на веб-сервер. Чем больше запросов, тем больше нагрузка. Веб-API, предоставляющие большое количество небольших ресурсов, называются чатыми веб-API. Старайтесь избежать этих API- интерфейсов, так как им требуется клиентское приложение для отправки нескольких запросов, чтобы найти все необходимые данные. Вместо этого рассмотрите возможность денормализации данных и объединения связанных сведений в большие ресурсы, которые можно получить с помощью одного запроса. Тем не менее, вам всё-таки необходимо сбалансировать этот подход по отношению к накладным расходам на извлечение данных, которые клиенту не нужны. Получение больших объектов может увеличить задержку запроса и увеличить затраты на пропускную способность. Дополнительные сведения об этих антипаттернах производительности см. в статье Chatty I/O и Extraneous Fetching.

  • Избегайте создания API, которые отражают внутреннюю структуру базы данных. Цель REST — моделировать бизнес-сущности и операции, которые приложение может выполнять в этих сущностях. Клиент не должен иметь доступ к внутренней реализации. Например, если данные хранятся в реляционной базе данных, веб-API не должен предоставлять каждую таблицу как коллекцию ресурсов. Такой подход увеличивает область атаки и может привести к утечке данных. Вместо этого думайте о веб-API как абстракции базы данных. При необходимости введите уровень сопоставления между базой данных и веб-API. Этот уровень гарантирует, что клиентские приложения изолированы от изменений в базовой схеме базы данных.

Подсказка

Возможно, невозможно сопоставить каждую операцию, реализованную веб-API, с определенным ресурсом. Эти неисправные сценарии можно обрабатывать с помощью HTTP-запросов, которые вызывают функцию и возвращают результаты в виде сообщения HTTP-ответа.

Например, веб-API, реализующий простые операции калькулятора, такие как добавление и вычитание, может предоставлять URI, предоставляющие эти операции как псевдоресурсы, и использовать строку запроса для указания необходимых параметров. Запрос GET к URI /add?operand1=99&operand2=1 возвращает ответное сообщение с текстом, содержащим значение 100.

Однако следует использовать эти формы URI умеренно.

Определение методов веб-API RESTful

Методы веб-API RESTful соответствуют методам запроса и типам носителей, определенным протоколом HTTP. В этом разделе описываются наиболее распространенные методы запросов и типы носителей, используемые в ВЕБ-API RESTful.

Методы HTTP-запроса

Протокол HTTP определяет множество методов запроса, указывающих действие, которое требуется выполнить в ресурсе. Наиболее распространенными методами, используемыми в ВЕБ-API RESTful, являются GET, POST, PUT, PATCH и DELETE. Каждый метод соответствует определенной операции. При разработке веб-API RESTful используйте эти методы в соответствии с определением протокола, ресурсом, к которому осуществляется доступ, и выполняемым действием.

Важно помнить, что влияние определенного метода запроса должно зависеть от того, является ли ресурс коллекцией или отдельным элементом. В следующей таблице приведены некоторые соглашения, используемые большинством реализаций RESTful.

Это важно

В следующей таблице используется пример сущности электронной коммерции customer . Веб-API не требует реализации всех методов запроса. Методы, которые он реализует, зависят от конкретного сценария.

Ресурс ОПУБЛИКОВАТЬ ПОЛУЧИТЬ ЗАПИСАТЬ УДАЛИТЬ
/клиенты Создание нового клиента Извлечь всех клиентов Массовое обновление клиентов Удаление всех клиентов
/клиенты/1 Ошибка Получение сведений о клиенте 1 Обновление сведений о клиенте 1, если оно существует Удаление клиента 1
/customers/1/orders Создание нового заказа для клиента 1 Получение всех заказов для клиента 1 Массовое изменение заказов для клиента 1 Удаление всех заказов для клиента 1

Запросы GET

Запрос GET получает представление ресурса по указанному URI. Текст сообщения ответа содержит сведения о запрошенных ресурсах.

Запрос GET должен возвращать один из следующих кодов состояния HTTP:

Код состояния HTTP Причина
200 OK; Метод успешно вернул ресурс.
204 No Content (нет содержимого). Текст ответа не содержит содержимого, например если поисковый запрос не возвращает совпадений в ответе HTTP.
404 (не найдено) Запрошенный ресурс не найден.

Запросы POST

Запрос POST должен создать ресурс. Сервер назначает URI для нового ресурса и возвращает этот URI клиенту.

Это важно

Для запросов POST клиент не должен пытаться создать собственный универсальный код ресурса (URI). Клиент должен отправить запрос в URI коллекции, а сервер должен назначить URI новому ресурсу. Если клиент пытается создать собственный URI и выдает запрос POST для определенного URI, сервер возвращает код состояния HTTP 400 (BAD REQUEST), чтобы указать, что метод не поддерживается.

В модели RESTful запросы POST используются для добавления нового ресурса в коллекцию, определяемую URI. Однако запрос POST также можно использовать для отправки данных для обработки в существующий ресурс без создания нового ресурса.

Запрос POST должен возвращать один из следующих кодов состояния HTTP:

Код состояния HTTP Причина
200 OK; Метод выполнил некоторую обработку, но не создает новый ресурс. Результат операции может быть включен в текст отклика.
201 Создано Ресурс был успешно создан. URI нового ресурса включен в заголовок Location ответа. Текст ответа содержит представление ресурса.
204 No Content (нет содержимого). Текст ответа не содержит содержимого.
400 (недопустимый запрос) Клиент разместил недопустимые данные в запросе. Текст ответа может содержать дополнительные сведения об ошибке или ссылке на универсальный код ресурса (URI), предоставляющий дополнительные сведения.
405 (метод не разрешен) Клиент попытался выполнить запрос POST к URI, который не поддерживает запросы POST.

Запрос PUT

Запрос PUT должен обновить существующий ресурс, если он существует или, в некоторых случаях, создать новый ресурс, если он не существует. Чтобы выполнить запрос PUT, сделайте:

  1. Клиент задает универсальный код ресурса (URI) ресурса и содержит текст запроса, содержащий полное представление ресурса.
  2. Клиент выполняет запрос.
  3. Если ресурс с этим URI уже существует, он заменен. В противном случае создается новый ресурс, если маршрут поддерживает его.

Методы PUT применяются к ресурсам, представляющим собой отдельные элементы, такие как конкретный клиент, а не коллекции. Сервер может поддерживать обновления, но возможно не поддерживать создание через PUT. Поддержка создания с помощью PUT зависит от того, может ли клиент эффективно и надежно назначить URI ресурсу, прежде чем он существует. Если это не удается, используйте POST для создания ресурсов и назначьте серверу универсальный код ресурса (URI). Затем используйте PUT или PATCH для обновления URI.

Это важно

Запросы PUT должны быть идемпотентными, что означает, что отправка одного запроса несколько раз всегда приводит к изменению одного ресурса с одинаковыми значениями. Если клиент повторно отправляет запрос PUT, результаты должны оставаться неизменными. В то время как, идемпотентность запросов POST и PATCH не гарантируется.

Запрос PUT должен возвращать один из следующих кодов состояния HTTP:

Код состояния HTTP Причина
200 OK; Ресурс был успешно обновлен.
201 Создано Ресурс был успешно создан. Текст ответа может содержать представление ресурса.
204 No Content (нет содержимого). Ресурс был обновлен успешно, но текст ответа не содержит содержимого.
409 (конфликт) Запрос не удалось завершить из-за конфликта с текущим состоянием ресурса.

Подсказка

Рассмотрите возможность реализации массовых операций HTTP PUT, которые могут пакетно обновлять несколько ресурсов в коллекции. Запрос PUT должен указать универсальный код ресурса (URI) коллекции. Текст запроса должен указать сведения об измененных ресурсах. Такой подход может помочь уменьшить количество чатов и повысить производительность.

Запросы PATCH

Запрос PATCH выполняет частичное обновление существующего ресурса. Клиент задает универсальный код ресурса (URI) для ресурса. Текст запроса задает набор изменений, применяемых к ресурсу. Этот метод может быть более эффективным, чем использование запросов PUT, так как клиент отправляет изменения только и не все представление ресурса. PATCH также может создать новый ресурс, указав набор обновлений пустого или пустого ресурса, если сервер поддерживает это действие.

При запросе PATCH клиент отправляет набор обновлений существующему ресурсу в виде документа исправления. Сервер обрабатывает документ исправления для выполнения обновления. Документ исправлений задает только набор изменений, применяемых вместо описания всего ресурса. Спецификация метода PATCH, RFC 5789, не определяет определенный формат для документов исправлений. Формат должен быть выведен из типа носителя в запросе.

JSON — это один из наиболее распространенных форматов данных для веб-API. Основные форматы патчей на основе JSON — это JSON patch и JSON merge patch.

JSON merge patch проще, чем JSON patch. Документ исправлений имеет ту же структуру, что и исходный ресурс JSON, но он включает только подмножество полей, которые следует изменить или добавить. Кроме того, поле можно удалить, указав null значение поля в документе исправлений. Эта спецификация означает, что патч слияния не подходит в случае, если исходный ресурс может иметь явно указанные значения null.

Например, предположим, что исходный ресурс имеет следующее представление JSON:

{
    "name":"gizmo",
    "category":"widgets",
    "color":"blue",
    "price":10
}

Ниже приведён возможный патч слияния JSON для этого ресурса:

{
    "price":12,
    "color":null,
    "size":"small"
}

Это исправление слияния сообщает серверу об обновлении price, удалении colorи добавлении size. Значения для name и category не изменяются. Дополнительные сведения о JSON merge patch см. в RFC 7396. Тип медиа для исправления слияния JSON — application/merge-patch+json.

Объединяющий патч не подходит, если исходный ресурс может содержать явные нулевые значения из-за особого значения null в документе патча. Документ исправлений также не указывает порядок применения обновлений сервером. Зависит ли важность этого порядка от данных и области. Исправление JSON, определенное в RFC 6902, является более гибким, так как оно указывает изменения в виде последовательности операций, которые следует применять, включая добавление, удаление, замена, копирование и тестирование для проверки значений. Тип медиа для JSON-патча — application/json-patch+json.

Запрос PATCH должен возвращать один из следующих кодов состояния HTTP:

Код состояния HTTP Причина
200 OK; Ресурс был успешно обновлен.
400 (недопустимый запрос) Неправильно сформированный документ исправлений.
409 (конфликт) Документ исправления действителен, но изменения не могут быть применены к ресурсу в текущем состоянии.
415 (неподдерживаемый тип носителя) Формат документа исправлений не поддерживается.

ЗАПРОСЫ DELETE

Запрос DELETE удаляет ресурс по указанному URI. Запрос DELETE должен возвращать один из следующих кодов состояния HTTP:

Код состояния HTTP Причина
204 (НЕТ СОДЕРЖИМОГО) Ресурс был успешно удален. Процесс успешно обработан, а текст ответа не содержит дополнительных сведений.
404 (НЕ НАЙДЕНО) Ресурс не существует.

Типы MIME ресурсов

Представление ресурсов заключается в том, как ресурс, который определяется URI, кодируется и транспортируется по протоколу HTTP в определенном формате, например XML или JSON. Клиенты, которые хотят получить определенный ресурс, должны использовать универсальный код ресурса (URI) в запросе к API. API отвечает, возвращая представление ресурса данных, указанных в URI.

В протоколе HTTP форматы представления ресурсов задаются с помощью типов носителей, также называемых типами MIME. Для небинарных данных большинство веб-API поддерживают JSON (тип носителя = application/json) и, возможно, XML (тип носителя = application/xml).

Заголовок Content-Type в запросе или ответе указывает формат представления ресурса. В следующем примере демонстрируется запрос POST, содержащий данные JSON:

POST https://api.contoso.com/orders
Content-Type: application/json; charset=utf-8
Content-Length: 57

{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}

Если сервер не поддерживает тип носителя, он должен вернуть код состояния HTTP 415 (неподдерживаемый тип носителя).

Запрос клиента может включать заголовок Accept, содержащий список типов носителей, которые клиент принимает с сервера в ответном сообщении. Рассмотрим пример.

GET https://api.contoso.com/orders/2
Accept: application/json, application/xml

Если сервер не может соответствовать ни одному из перечисленных типов носителей, он должен вернуть код состояния HTTP 406 (недопустимо).

Реализация асинхронных методов

Иногда метод POST, PUT, PATCH или DELETE может потребовать обработки, для завершения которой требуется время. Если вы ожидаете завершения перед отправкой ответа клиенту, это может привести к неприемлемой задержке. В этом сценарии рекомендуется сделать метод асинхронным. Асинхронный метод должен возвращать код состояния HTTP 202 (принято), чтобы указать, что запрос был принят для обработки, но не является неполным.

Предоставление конечной точки, возвращающей состояние асинхронного запроса, чтобы клиент смог отслеживать состояние, опрашив конечную точку состояния. Включите URI конечной точки состояния в заголовок Location ответа 202. Рассмотрим пример.

HTTP/1.1 202 Accepted
Location: /api/status/12345

Если клиент отправляет запрос GET в эту конечную точку, ответ должен содержать текущее состояние запроса. При необходимости он может включать предполагаемое время завершения или ссылку на отмену операции.

HTTP/1.1 200 OK
Content-Type: application/json

{
    "status":"In progress",
    "link": { "rel":"cancel", "method":"delete", "href":"/api/status/12345" }
}

Если асинхронная операция создает новый ресурс, конечная точка состояния должна вернуть код состояния 303 (см. другие) после завершения операции. В ответе 303 добавьте заголовок location, который предоставляет URI нового ресурса:

HTTP/1.1 303 See Other
Location: /api/orders/12345

Дополнительные сведения см. в разделах «Предоставление асинхронной поддержки длительных запросов» и «Шаблон Request-Reply».

Реализация разбиения на страницы и фильтрации данных

Чтобы оптимизировать извлечение данных и уменьшить размер полезных данных, реализуйте разбивку на страницы и фильтрацию на основе запросов в конструкторе API. Эти методы позволяют клиентам запрашивать только подмножество необходимых данных, что может повысить производительность и сократить использование пропускной способности.

  • Разбиение на страницы разделяет большие наборы данных на небольшие управляемые блоки. Используйте параметры запроса, например limit , чтобы указать количество возвращаемых элементов и offset указать начальную точку. Обязательно предоставьте осмысленные значения по умолчанию для limit и offset, такие как limit=25 и offset=0. Рассмотрим пример.

    GET /orders?limit=25&offset=50
    
    • limit: указывает максимальное количество возвращаемых элементов.

      Подсказка

      Чтобы предотвратить атаки типа "отказ в обслуживании", рекомендуется ввести верхний предел числа возвращаемых элементов. Например, если ваша служба устанавливает max-limit=25, а клиент запрашивает limit=1000, ваша служба может либо вернуть 25 элементов, либо ошибку HTTP BAD-REQUEST в зависимости от документации по API.

    • offset: указывает начальный индекс для данных.

  • Фильтрация позволяет клиентам уточнить набор данных, применяя условия. API может разрешить клиенту передавать фильтр в строке запроса URI:

    GET /orders?minCost=100&status=shipped
    
    • minCost: фильтрует заказы, которые имеют минимальную стоимость 100.
    • status: фильтрует заказы с определенным статусом.

Учитывайте приведенные ниже рекомендации.

  • Сортировка позволяет клиентам сортировать данные с помощью sort параметра, например sort=price.

    Это важно

    Подход сортировки может негативно повлиять на кэширование, так как параметры строки запроса являются частью идентификатора ресурса, который многие реализации кэша используются в качестве ключа для кэшированных данных.

  • Выбор полей для определяемых клиентомfields проекций позволяет клиентам указывать только поля, необходимые им с помощью параметра, например fields=id,name. Например, можно использовать параметр строки запроса, который принимает список полей с разделителями-запятыми, например /orders?fields=ProductID,Quantity.

Ваш API должен проверять запрошенные поля, чтобы убедиться, что клиент имеет право на доступ к ним и не будут показывать поля, которые обычно недоступны через API.

Поддерживать частичные ответы

Некоторые ресурсы содержат большие двоичные поля, такие как файлы или изображения. Чтобы преодолеть проблемы, вызванные ненадежными и периодическими подключениями, и повысить время отклика, рассмотрите возможность частичного извлечения больших двоичных ресурсов.

Для поддержки частичных ответов веб-API должен поддерживать заголовок Accept-Ranges для запросов GET для больших ресурсов. Этот заголовок указывает, что операция GET поддерживает частичные запросы. Клиентское приложение может отправлять запросы GET, возвращающие подмножество ресурса, указанное в диапазоне байтов.

Кроме того, рекомендуется реализовать HTTP-запросы HEAD для этих ресурсов. Запрос HEAD аналогичен запросу GET, за исключением того, что он возвращает только заголовки HTTP, описывающие ресурс, с пустым текстом сообщения. Клиентское приложение может выдавать запрос HEAD, чтобы определить, следует ли получать ресурс с помощью частичных запросов GET. Рассмотрим пример.

HEAD https://api.contoso.com/products/10?fields=productImage

Ниже приведен пример ответа:

HTTP/1.1 200 OK

Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580

Заголовок Content-Length предоставляет общий размер ресурса, а заголовок Accept-Ranges указывает, что соответствующая операция GET поддерживает частичные результаты. Клиентское приложение может использовать эти сведения для получения изображения в небольших блоках. Первый запрос получает первые 2500 байт с помощью заголовка Range:

GET https://api.contoso.com/products/10?fields=productImage
Range: bytes=0-2499

Сообщение ответа указывает, что этот ответ является частичным, возвращая код состояния HTTP 206. Заголовок Content-Length указывает фактическое количество байтов, возвращаемых в тексте сообщения, а не размер ресурса. Заголовок Content-Range указывает, какая часть ресурса возвращается (байты 0–2499 из 4580):

HTTP/1.1 206 Partial Content

Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580

[...]

Последующий запрос из клиентского приложения может получить оставшуюся часть ресурса.

Внедрить HATEOAS

Одной из основных причин использования REST является возможность навигации по всему набору ресурсов без предварительного знания схемы URI. Каждый HTTP-запрос GET должен возвращать сведения, необходимые для поиска ресурсов, связанных непосредственно с запрошенным объектом, через гиперссылки, включенные в ответ. Запрос также должен содержать сведения, описывающие операции, доступные для каждого из этих ресурсов. Этот принцип известен как HATEOAS, или гипертекст как движок состояния приложения. Система фактически является конечным компьютером состояния, и ответ на каждый запрос содержит сведения, необходимые для перемещения из одного состояния в другое. Никаких других сведений не требуется.

Примечание.

Нет стандартов общего назначения, определяющих, как моделировать принцип HATEOAS. Примеры, приведенные в этом разделе, иллюстрируют одно возможное частное решение.

Например, чтобы обработать связь между заказом и клиентом, представление заказа может включать ссылки, определяющие доступные операции для клиента заказа. Следующий блок кода является возможным представлением:

{
  "orderID":3,
  "productID":2,
  "quantity":4,
  "orderValue":16.60,
  "links":[
    {
      "rel":"customer",
      "href":"https://api.contoso.com/customers/3",
      "action":"GET",
      "types":["text/xml","application/json"]
    },
    {
      "rel":"customer",
      "href":"https://api.contoso.com/customers/3",
      "action":"PUT",
      "types":["application/x-www-form-urlencoded"]
    },
    {
      "rel":"customer",
      "href":"https://api.contoso.com/customers/3",
      "action":"DELETE",
      "types":[]
    },
    {
      "rel":"self",
      "href":"https://api.contoso.com/orders/3",
      "action":"GET",
      "types":["text/xml","application/json"]
    },
    {
      "rel":"self",
      "href":"https://api.contoso.com/orders/3",
      "action":"PUT",
      "types":["application/x-www-form-urlencoded"]
    },
    {
      "rel":"self",
      "href":"https://api.contoso.com/orders/3",
      "action":"DELETE",
      "types":[]
    }]
}

В этом примере links массив содержит набор ссылок. Каждая ссылка представляет операцию на связанной сущности. Данные для каждой ссылки включают связь ("клиент"), URI (https://api.contoso.com/customers/3), метод HTTP и поддерживаемые типы MIME. Клиентское приложение должно иметь эти сведения для вызова операции.

Массив links также содержит самоссылочную информацию об извлеченном ресурсе. Эти ссылки имеют отношение сам.

Набор возвращаемых ссылок может изменяться в зависимости от состояния ресурса. Идея о том, что гипертекст является механизмом состояния приложения , описывает этот сценарий.

Реализация управления версиями

Веб-API не остается статическим. По мере изменения бизнес-требований добавляются новые коллекции ресурсов. При добавлении новых ресурсов могут измениться связи между ресурсами, а структура данных в ресурсах может быть изменена. Обновление веб-API для обработки новых или различных требований является простым процессом, но необходимо учитывать последствия, которые такие изменения имеют в клиентских приложениях, использующих веб-API. Разработчик, который разрабатывает и реализует веб-API, имеет полный контроль над этим API, но они не имеют той же степени контроля над клиентскими приложениями, созданными партнерскими организациями. Важно продолжать поддерживать существующие клиентские приложения, позволяя новым клиентским приложениям использовать новые функции и ресурсы.

Веб-API, реализующий управление версиями, может указывать на функции и ресурсы, предоставляемые им, а клиентское приложение может отправлять запросы, направленные на определенную версию компонента или ресурса. В следующих разделах описано несколько различных подходов, каждый из которых имеет свои собственные преимущества и компромиссы.

Нет управления версиями

Этот подход является самым простым и может работать для некоторых внутренних API. Значительные изменения могут быть представлены в виде новых ресурсов или новых ссылок. Добавление содержимого в существующие ресурсы может не привести к критическому изменению, так как клиентские приложения, которые не ожидают видеть это содержимое, игнорируют его.

Например, запрос к URI https://api.contoso.com/customers/3 должен возвращать детали одного клиента, который содержит поля id, name и address, ожидаемые клиентским приложением.

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Fabrikam, Inc.","address":"1 Microsoft Way Redmond WA 98053"}

Примечание.

Для простоты примеры ответов, показанных в этом разделе, не включают ссылки HATEOAS.

DateCreated Если поле добавляется в схему ресурса клиента, ответ выглядит следующим образом:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Fabrikam, Inc.","dateCreated":"2025-03-22T12:11:38.0376089Z","address":"1 Microsoft Way Redmond WA 98053"}

Существующие клиентские приложения могут продолжать работать правильно, если они могут игнорировать нераспознанные поля. Между тем, новые клиентские приложения можно разрабатывать для обработки этого нового поля. Однако может произойти более резкое изменение схемы ресурсов, включая удаление полей или переименование. Или связи между ресурсами могут измениться. Эти обновления могут представлять собой критические изменения, которые препятствуют правильному функционированию существующих клиентских приложений. В этих сценариях рассмотрим один из следующих подходов:

Управление версиями URI

Каждый раз, когда вы изменяете веб-API или изменяете схему ресурсов, добавьте номер версии в универсальный код ресурса (URI) для каждого ресурса. Ранее существующие URI должны продолжать работать нормально, возвращая ресурсы, соответствующие исходной схеме.

Например, address поле в предыдущем примере переструктурировано в подполях, содержащих каждую составную часть адреса, например streetAddress, citystateи zipCode. Эту версию ресурса можно предоставить с помощью URI, содержащего номер версии, например https://api.contoso.com/v2/customers/3:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Fabrikam, Inc.","dateCreated":"2025-03-22T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}

Этот механизм управления версиями прост, но зависит от сервера для маршрутизации запроса в соответствующую конечную точку. Однако он может стать неуправляемым, так как веб-API созревает через несколько итерации, и сервер должен поддерживать множество различных версий. С точки зрения пуриста, во всех случаях клиентские приложения извлекают одни и те же данные (клиент 3), поэтому URI не должен отличаться в соответствии с версией. Эта схема также усложняет реализацию HATEOAS, так как все ссылки должны включать номер версии в свои URI.

Управление версиями строк запроса

Вместо предоставления нескольких URI можно указать версию ресурса с помощью параметра в строке запроса, добавленной к HTTP-запросу, например https://api.contoso.com/customers/3?version=2. Параметр версии по умолчанию должен иметь понятное значение, например 1, если старые клиентские приложения опустят его.

Этот подход имеет семантическое преимущество в том, что один и тот же ресурс всегда получается из одного и того же URI. Однако этот метод зависит от кода, обрабатывающего запрос для анализа строки запроса и отправки соответствующего HTTP-ответа. Этот подход также усложняет реализацию HATEOAS таким же образом, как механизм управления версиями URI.

Примечание.

Некоторые старые веб-браузеры и веб-прокси не кэшируют ответы на запросы, содержащие строку запроса в URI. Некшированные ответы могут снизить производительность веб-приложений, использующих веб-API и запускаемых из более старого веб-браузера.

Управление версиями заголовков

Вместо добавления номера версии в качестве параметра строки запроса можно реализовать пользовательский заголовок, указывающий версию ресурса. Этот подход требует, чтобы клиентское приложение добавило соответствующий заголовок к любым запросам. Однако код, обрабатывающий запрос клиента, может использовать значение по умолчанию, например версию 1, если заголовок версии опущен.

В следующих примерах используется пользовательский заголовок с именем Custom-Header. Значение этого заголовка указывает версию веб-API.

Версия 1.

GET https://api.contoso.com/customers/3
Custom-Header: api-version=1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Fabrikam, Inc.","address":"1 Microsoft Way Redmond WA 98053"}

Версия 2.

GET https://api.contoso.com/customers/3
Custom-Header: api-version=2
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Fabrikam, Inc.","dateCreated":"2025-03-22T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}

Как и управление версиями URI и управление версиями строк запроса, необходимо включить соответствующий пользовательский заголовок в любые ссылки для реализации HATEOAS.

Управление версиями типов мультимедиа

Когда клиентское приложение отправляет HTTP-запрос GET на веб-сервер, он должен использовать заголовок Accept, чтобы указать формат содержимого, который он может обрабатывать. Как правило, назначение заголовка Accept — разрешить клиентскому приложению указать, должен ли текст ответа быть XML, JSON или другим общим форматом, который клиент может проанализировать. Однако можно определить пользовательские типы носителей, которые содержат сведения, позволяющие клиентскому приложению указать, какая версия ресурса она ожидает.

В следующем примере показан запрос, указывающий заголовок Accept со значением application/vnd.contoso.v1+json. Элемент vnd.contoso.v1 указывает веб-серверу, что он должен вернуть версию 1 ресурса. Элемент json указывает, что формат текста ответа должен иметь формат JSON:

GET https://api.contoso.com/customers/3
Accept: application/vnd.contoso.v1+json

Код, обрабатывающий запрос, отвечает за обработку заголовка Accept и его максимальное соблюдение. Клиентское приложение может указать несколько форматов в заголовке Accept, что позволяет веб-серверу выбрать наиболее подходящий формат для текста ответа. Веб-сервер подтверждает формат данных в тексте ответа с помощью заголовка Content-Type:

HTTP/1.1 200 OK
Content-Type: application/vnd.contoso.v1+json; charset=utf-8

{"id":3,"name":"Fabrikam, Inc.","address":"1 Microsoft Way Redmond WA 98053"}

Если заголовок Accept не указывает известные типы носителей, веб-сервер может создать сообщение ответа HTTP 406 (Неприменимо) или вернуть сообщение с типом носителя по умолчанию.

Этот механизм управления версиями является простым и хорошо подходит для HATEOAS, который может включать тип MIME связанных данных в ссылки на ресурсы.

Примечание.

При выборе стратегии версионирования учитываются последствия, особенно в отношении кэширования веб-сервера. Схемы управления версиями URI и управления версиями строк запроса являются понятными для кэша, так как одно и то же сочетание URI или строки запроса ссылается на одни и те же данные каждый раз.

Механизмы управления версиями заголовков и типов мультимедиа обычно требуют больше логики для проверки значений в пользовательском заголовке или заголовке Accept. В крупномасштабной среде многие клиенты, использующие различные версии веб-API, могут привести к значительному количеству повторяющихся данных в кэше на стороне сервера. Эта проблема может стать острой, если клиентское приложение взаимодействует с веб-сервером через прокси-сервер, который реализует кэширование и пересылает запрос на веб-сервер только в том случае, если в его кэше в настоящее время не содержится копия запрошенных данных.

Многотенантные веб-API

Мультитенантное решение веб-API совместно используется несколькими клиентами, например отдельными организациями, имеющими собственные группы пользователей.

Многотенантность значительно влияет на дизайн веб-API, поскольку она определяет, как ресурсы доступны и обнаруживаются для многих арендаторов в рамках одного веб-API. Разработайте API с учетом мультитенантности, чтобы избежать необходимости будущего рефакторинга для реализации изоляции, масштабируемости или настроек, специфичных для конкретного клиента.

Хорошо спроектированный API должен четко определить, как арендаторы идентифицируются в запросах, будь то через поддомены, пути, заголовки или токены. Эта структура обеспечивает согласованный и гибкий интерфейс для всех пользователей в системе. Дополнительные сведения см. в разделе "Сопоставление запросов к клиентам" в мультитенантном решении.

Многотенантность влияет на структуру конечных точек, обработку запросов, проверку подлинности и авторизацию. Этот подход также влияет на способ маршрутизации и обработки запросов шлюзов API, подсистем балансировки нагрузки и внутренних служб. Ниже приведены распространенные способы обеспечения мультитенантности в веб-API.

Используйте изоляцию на уровне поддомена или домена (тенантность на уровне DNS).

Этот подход направляет запросы с помощью доменов, относящихся к клиенту. Домены подстановочных знаков используют поддомены для гибкости и простоты. Пользовательские домены, которые позволяют клиентам использовать собственные домены, обеспечивают более широкий контроль и могут быть адаптированы для удовлетворения конкретных потребностей. Оба метода используют правильную конфигурацию DNS, включая A и CNAME записи, для направления трафика в соответствующую инфраструктуру. Домены подстановочных знаков упрощают настройку, но пользовательские домены обеспечивают более фирменный интерфейс.

Сохраните имя узла между обратным прокси-сервером и внутренними службами, чтобы избежать проблем, таких как перенаправление URL-адресов, и предотвратить предоставление внутренних URL-адресов. Этот метод обеспечивает правильную маршрутизацию трафика для конкретного клиента и помогает защитить внутреннюю инфраструктуру. Разрешение DNS имеет решающее значение для достижения расположения данных и обеспечения соответствия нормативным требованиям.

GET https://adventureworks.api.contoso.com/orders/3

Передача заголовков HTTP для конкретного клиента

Сведения о клиенте могут передаваться через пользовательские заголовки HTTP, такие как X-Tenant-ID или X-Organization-ID, через заголовки, основанные на узле, такие как Host или X-Forwarded-Host, или извлекаться из утверждений веб-маркера JSON (JWT). Выбор зависит от возможностей маршрутизации шлюза API или обратного прокси-сервера, при этом решения на основе заголовков требуют шлюза уровня 7 (L7) для проверки каждого запроса. Это требование добавляет затраты на обработку, что увеличивает затраты на вычисления при масштабировании трафика. Однако изоляция на основе заголовков обеспечивает ключевые преимущества. Она обеспечивает централизованную проверку подлинности, которая упрощает управление безопасностью в мультитенантных API. С помощью пакетов SDK или клиентов API контекст клиента динамически управляется во время выполнения, что снижает сложность конфигурации на стороне клиента. Кроме того, сохранение контекста клиента в заголовках приводит к более чистому проектированию API RESTful, избегая данных конкретного клиента в URI.

Важным аспектом маршрутизации на основе заголовков является то, что она усложняет кэширование, в особенности, когда уровни кэша полагаются исключительно на ключи, основанные на URI, и не принимают во внимание заголовки. Поскольку большинство механизмов кэширования оптимизированы для поиска URI, использование заголовков может привести к фрагментированным записям кэша. Фрагментированные записи сокращают обращения к кэшу и увеличивают нагрузку на сервер. Более критично, если уровень кэширования не различает ответы на основе заголовков, он может предоставлять кэшированные данные, предназначенные для одного клиента, другому, что создаёт риск утечки данных.

GET https://api.contoso.com/orders/3
X-Tenant-ID: adventureworks

или

GET https://api.contoso.com/orders/3
Host: adventureworks

или

GET https://api.contoso.com/orders/3
Authorization: Bearer <JWT-token including a tenant-id: adventureworks claim>

Передача сведений о клиенте через путь URI

Этот подход добавляет идентификаторы клиента в иерархию ресурсов и использует шлюз API или обратный прокси-сервер для определения соответствующего клиента на основе сегмента пути. Изоляция на основе пути эффективна, но она компрометирует дизайн RESTful веб-API и вводит более сложную логику маршрутизации. Часто требуется сопоставление шаблонов или регулярных выражений для синтаксического анализа и канонизации пути URI.

В отличие от этого, изоляция на основе заголовков передает сведения о клиенте через заголовки HTTP в виде пар "ключ-значение". Оба подхода позволяют эффективно использовать инфраструктуру для снижения операционных затрат и повышения производительности в крупномасштабных мультитенантных веб-API.

GET https://api.contoso.com/tenants/adventureworks/orders/3

Включение распределенной трассировки и контекста трассировки в API

Так как распределенные системы и архитектуры микрослужб становятся стандартными, сложность современных архитектур увеличивается. Использование заголовков, таких как Correlation-ID, X-Request-ID, или X-Trace-ID, для распространения контекста трассировки в запросах API является лучшей практикой для обеспечения сквозной видимости. Такой подход позволяет легко отслеживать запросы по мере их потока от клиента к внутренним службам. Это упрощает быструю идентификацию сбоев, отслеживает задержку и сопоставляет зависимости API между службами.

API, поддерживающие включение сведений трассировки и контекста, повышают уровень наблюдаемости и возможности отладки. Включив распределенную трассировку, эти API позволяют более детально понять поведение системы и упростить отслеживание, диагностику и устранение проблем в сложных средах с несколькими службами.

GET https://api.contoso.com/orders/3
Correlation-ID: 0f8fad5b-d9cb-469f-a165-70867728950e
HTTP/1.1 200 OK
...
Correlation-ID: 0f8fad5b-d9cb-469f-a165-70867728950e

{...}

Модель зрелости веб-API

В 2008 году Леонард Ричардсон предложил то, что в настоящее время называется Моделью зрелости Ричардсона (RMM) для веб-API. RMM определяет четыре уровня зрелости веб-API и основан на принципах REST в качестве архитектурного подхода к проектированию веб-служб. С увеличением уровня зрелости в RMM, API становится более REST-совместимым и ближе следует принципам REST.

Уровни:

  • Уровень 0: Определите один универсальный код ресурса (URI), а все операции — запросы POST к этому URI. Веб-службы протокола простого доступа к объектам обычно находятся на этом уровне.
  • Уровень 1. Создайте отдельные URI для отдельных ресурсов. Этот уровень еще не является REST-фул, но начинает соответствовать принципам дизайна REST.
  • Уровень 2. Используйте методы HTTP для определения операций с ресурсами. На практике многие опубликованные веб-API соответствуют примерно этому уровню.
  • Уровень 3. Используйте гипермедиа (HATEOAS). Этот уровень действительно является API RESTful, в соответствии с определением Fielding.

Инициатива OpenAPI

Инициатива OpenAPI была создана отраслевым консорциумом для стандартизации описаний REST API для разных поставщиков. Спецификация стандартизации называлась Swagger, прежде чем она была включена в инициативу OpenAPI и переименована в спецификацию OpenAPI (OAS).

Возможно, вы хотите применить OpenAPI для веб-API RESTful. Рассмотрим следующие моменты:

  • OAS поставляется с набором рекомендаций по проектированию REST API. Рекомендации являются выгодными для взаимодействия, но требуют, чтобы ваша конструкция соответствовала спецификациям.

  • OpenAPI способствует подходу, при котором контракт разрабатывается в первую очередь, а не реализуется. Contract-first означает, что сначала вы разрабатываете контракт API (интерфейс), а затем пишете код, реализующий контракт.

  • Такие средства, как Swagger (OpenAPI), могут создавать клиентские библиотеки или документацию из контрактов API. Пример см. в документации по веб-API ASP.NET Core с помощью Swagger/OpenAPI.

Дальнейшие действия