HangOps.ru

Как разработать правильные API-интерфейсы

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

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

1. Будьте последовательны

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

  • Используйте один и то же стиль написания для полей, ресурсов и параметров (я предпочитаю `snake_case')
  • Используйте имена ресурсов во множественном или единственном числе (я предпочитаю множественное число).
    • /users/{id}, /orders/{id} или /user/{id}, /order/{id}
  • Используйте одни и те же методы аутентификации и авторизации для всех конечных точек
  • Используйте одни и те же HTTP-заголовки во всем API
    • Например, "Api-ключ" для передачи ключа API
  • Используйте одни и те же коды состояния HTTP в зависимости от типа ответа * * Например, "404", когда ресурс не может быть найден
  • Используйте одни и те же методы HTTP для одних и тех же действий.
    • Например, "УДАЛИТЬ" при удалении ресурса

2. Use ISO 8601 UTC dates

При работе с датой и временем API всегда должны возвращать строки в формате ISO 8601. Отображение дат в определенном часовом поясе, как правило, является задачей клиентских приложений.

   {
        "published_at": "2022-03-03T21:59:08Z"
   }

3. Сделайте исключение для общедоступных конечных точек

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

4. Предоставьте конечную точку проверки работоспособности

Укажите конечную точку (например, GET /health), которая определяет, является ли служба работоспособной или нет. Эта конечная точка может вызываться другими приложениями, такими как балансировщики нагрузки, для действий в случае сбоя в обслуживании.

5. Версия API

Убедитесь, что вы обновили свой API и передаете версию при каждом запросе, чтобы никакие изменения в другой версии не влияли на потребителей. Версии API могут передаваться с использованием HTTP-заголовков или параметров запроса/пути. Даже первая версия API (1.0) должна быть явно версионной.

Например:

  • https://api.averagecompany.com/v1/health
  • https://api.averagecompany.com/health?api_version=1.0

6. Примите аутентификацию по ключу API

Если API должен быть вызван третьей стороной, имеет смысл разрешить аутентификацию с помощью ключей API. Ключи API должны передаваться с использованием пользовательского HTTP-заголовка (например, Api-Key). У них должен быть срок годности, и должна быть возможность отозвать активные ключи, чтобы их можно было признать недействительными в случае их компрометации. Избегайте проверки ключей API в системе управления версиями (вместо этого используйте переменные среды).

7. Используйте разумные коды состояния HTTP

Используйте обычные коды состояния HTTP для указания успешного или неудачного выполнения запроса. Не используйте слишком много и используйте одни и те же коды состояния для одних и тех же результатов по всему API. Несколько примеров:

  • 200 общий успех
  • 201 успешное создание
  • 400 для неправильных запросов от клиента
  • 401 для несанкционированных запросов
  • 403 для отсутствующих разрешений
  • 404 для отсутствующих ресурсов
  • 429 для слишком большого количества запросов
  • 5xx для внутренних ошибок (их следует избегать любой ценой)

8. Используйте разумные методы HTTP

  • POST для создания ресурсов
    • POST /users
  • GET для чтения ресурсов (как отдельных ресурсов, так и коллекций)
    • GET /users
    • GET /users/{id}
  • PATCH для применения частичных обновлений к ресурсу
    • PATCH /users/{id}
  • PUT для применения полных обновлений к ресурсу (заменяет текущий ресурс)
    • PUT /users/{id}
  • DELETE для удаления ресурсов
    • DELETE /users/{id}

9. Используйте понятные, простые имена

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

Хорошо

  • GET /users => Получения пользователей
  • DELETE /users/{id} => Удаление пользователя
  • POST /users/{id}/notifications => Создайте уведомление для определенного пользователя
  • user.first_name
  • order.number

Плохо

  • GET /getUser
  • POST /updateUser
  • POST /notification/user
  • order.ordernumber
  • user.firstName

10. Используйте стандартизированные ответы на ошибки

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

// Request => GET /users/4TL011ax

// Response <= 404 Not Found
{
  "code": "user/not_found",
  "message": "A user with the ID 4TL011ax could not be found."
}
// Request => POST /users 
{
  "name": "John Doe"
} 
// Response <= 400 Bad Request 
{
  "code": "user/email\_required", 
  "message": "The parameter \[email\] is required."
}

11. Возврат созданных ресурсов после POST

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

// Request: POST /users
{
    "email": "jdoe@averagecompany.com",
    "name": "John Doe"
}

// Response
{
    "id": "T9hoBuuTL4",
    "email": "jdoe@averagecompany.com",
    "name": "John Doe"
}

12. Предпочитаю PATCH, а не PUT

Как упоминалось ранее, запросы на PATCH должны применять частичные обновления к ресурсу, в то время как PUT полностью заменяет существующий ресурс. Обычно хорошей идеей является разработка обновлений с учетом запросов на PATCH, потому что: * При использовании PUT для обновления только подмножества полей для ресурса все равно необходимо передать весь ресурс, что делает его более ресурсоемким и подверженным ошибкам. * Также довольно опасно разрешать обновлять любое поле без каких-либо ограничений * По моему опыту, практически не существует случаев использования, когда полное обновление ресурса имело бы смысл * Представьте себе ресурс order, у которого есть id и state. * Было бы очень опасно позволять потребителям обновлять state order * Изменение состояния с гораздо большей вероятностью будет вызвано другой конечной точкой (например, /orders/{id}/fulfill)

13. Будьте как можно более конкретны.

Как описано в предыдущем разделе, обычно рекомендуется быть как можно более конкретным при проектировании конечных точек, присвоении имен полям и принятии решения о том, какие запросы и ответы принимать. Если запрос на PATCH принимает только два поля (name и description), нет никакой опасности неправильного его использования и повреждения данных.

14. Используйте пагинацию

Разбейте на страницы все запросы, возвращающие набор ресурсов, и используйте одну и ту же структуру ответов. Используйте page_number и page_size (или аналогичные), чтобы управлять тем, какой фрагмент вы хотите извлечь.

// Request => GET /users?page_number=1&page_size=15

// Response <= 200 OK
{
    "page_number": 1,
    "page_size": 15,
    "count": 378,
    "data": [
        // resources
    ],
    "total_pages": 26,
    "has_previous_page": true,
    "has_next_page": true
}

15. Разрешить расширение ресурсов

Разрешить потребителям загружать связанные данные с помощью параметра запроса с именем expand (или аналогичного). Это особенно полезно, чтобы избежать обходов и загрузить все данные, необходимые для конкретного действия, за один раз.

// Request => GET /users/T9hoBuuTL4?expand=orders&expand=orders.items

// Response <= 200 OK
{
  "id": "T9hoBuuTL4",
  "email": "jdoe@averagecompany.com",
  "name": "John Doe",
  "orders": [
    {
      "id": "Hy3SSXU1PF",
      "items": [
        {
          "name": "API course"
        },
        {
          "name": "iPhone 13"
        }
      ]
    },
    {
      "id": "bx1zKmJLI6",
      "items": [
        {
          "name": "SaaS subscription"
        }
      ]
    }
  ]
}

Источник

Api