Что такое API, из чего состоит его проектирование и как выполнить этот процесс создания дизайна веб-сервиса: примеры, подходы и практики.
Что такое API
Как бы это не звучало банально, но суть термина API (Application Programming Interface) лучше всего раскрывает его дословный перевод – прикладной програмный интерфейс или программный интерфейс приложения. Поскольку интерфейс в общем смысле – это способ взаимодействия чего-то с чем-то, получается, API обеспечивает возможность вызова команд (функций) приложения или программного компонента с помощью программного кода. Например, веб-сервис с REST API позволяет обратиться к нему извне через конечные точки, используя HTTP-запросы, разрешенные для конкретного ресурса.
Однако, API есть не только у веб-сервисов. К примеру, отдельные программные компоненты, такие как библиотеки, содержащие такие структуры данных, как классы и интерфейсы, тоже предоставляют API, позволяя пользоваться методами этих структур. В частности, API DataFrame в Python-библиотеке pandas для работы с датафреймами – табличными структурами данных, или API DataStream в Apache Flink, который предоставляет примитивы для операций потоковой обработки. Проектирование такого внутреннего API обычно сводится к определению основной структуры данных и методов манипулирования ей. Сперва следует четко определить основные концепты и объекты, с которыми пользователи будут взаимодействовать. Например, в pandas это двумерная таблица датафрейм (DataFrame) и одномерный массив (Series). Их API содержат методы этих классов, т.е. функции, которые можно применить к их частным случаям (объектам), чтобы получить доступ к определенным данным, профильтровать или преобразовать их и т.д.
Таким образом, первичное проектирование внутреннего API можно выполнить в табличном виде. Например, для датафрейма pandas это может выглядеть так.
Исходная структура данных | Функция | Метод | Входные параметры метода | Результат |
Dataframe | Проверить, содержится ли каждый элемент датафрейма в значениях. | Isin() | Итерируемое значение, словарь, двумерный или одномерный массив | True/False |
Dataframe | Узнать количество элементов | size | отсутвуют | Целое число |
Dataframe | Вывести первые несколько строк массива | head() | Целое число | Набор строк |
Проектирование внутреннего API немного проще, чем дизайн внешнего API, который предназначен для внешних акторов – разработчиков или сторонних приложений, поскольку внутренний API обычно используется в доверенной среде. Однако, вопросы безопасности – не единственные аспекты, которые определяют дизайн API веб-приложения. Что еще надо учесть, рассмотрим далее на примере REST API и RPC API (SOAP API, GraphQL API и gRPC API).
Основные вопросы проектирования веб-API и как на них ответить
Любой дизайн всегда основан на требованиях: функциональных и нефункциональных. Функциональные требования к внешнему API приложения можно свести к перечислению набора вариантов использования, нужных внешним акторам. Возьмем в качестве примера систему электронного документооборота (СЭД), предназначенную для автоматизации процессов работы с документами, от их создания до согласования, в т.ч. с использованием электронной подписи. В такой системе можно выделить 3 основных домена: документы, маршруты их движения и пользователи.
Реестр вариантов использования (ВИ, use case, юзкейс) для работы с документами можно представить следующим списком:
№ | Название | Описание |
UC-1 | Создание документа | Создать новый документ внутри системы, используя преднастроенный или собственный шаблон |
UC-2 | Редактирование документа | Редактировать существующий документ с сохранением истории изменений |
UC-3 | Просмотр реестра документов | Посмотреть список всех существующих в системе документов, включая фильтры и сортировку результатов |
UC-4 | Поиск документа | Найти документ по ключевым словам содержимого и метаданным, включая фильтры и сортировку результатов |
UC-5 | Просмотр документа | Посмотреть внутреннее содержимое и метаданные документа |
UC-6 | Просмотр истории изменений документа | Получить перечнь всех изменений, внесенных в документ, с указанием авторов и временных меток, а также с возможность сравнения версий |
UC-7 | Управление доступом к документу | Настроить права доступа к документу для пользователей, ролей и групп, включая определение уровней разрешений (чтение, редактирование, удаление) |
UC-8 | Отправка документ по маршруту на рассмотрение или согласование | Отправить документ другим пользователям для рассмотрения или согласования |
UC-9 | Согласование документа | Согласиться с представленной версией документа или отказать в согласовании |
UC-10 | Подписание документа | Добавить к документу электронную подпись для обеспечения подлинности и юридической силы |
UC-11 | Подписка на уведомления об изменениях документа | Подписаться на автоматическую отправку уведомлений об изменении документа, включая его внутреннее содержимое и метаданные |
Справедливости ради стоит отметить, что в реальных системах этот реестр юзкейсов гораздо шире. Однако, для модельного примера нам достаточно и такого списка.
После определения основных вариантов использования системы, т.е. возможностей доступа к основным сущностям домена и манипулированию ими, для проектирования API надо учесть нефункциональные требования. Основными НФТ здесь будут требования к быстродействию выполнения юзкейсов с учетом их максимального количества в единицу времени, а также требования к безопасности. Требования к отклику системы определяют характер взаимодействия с API: синхронный или асинхронный. При синхронном взаимодействии клиент отправляет запрос к серверу и ожидает немедленного ответа, прежде чем продолжить дальнейшие действия. Например, вход в систему или почти все юзкейсы из вышепредставленного реестра, кроме UC-4 (при большом объеме данных) и UC-11 синхронные. А событийную отправку уведомлений об изменении документа по подписке можно реализовать через асинхронные паттерны — веб-хуки или брокеры сообщений. Это значит, в коде проектируемой системы должна быть функция, реализующая отправку данных внешнему сервису при возникновении определенного события. Если UC-10 (Подписание документа) реализуется с использованием стороннего сервиса, этот юзкейс тоже может быть асинхронным или требовать установки двунаправленного канала для взаимодействия систем.
С прикладной точки зрения синхронный и асинхронный характер могут повлиять на выбор технологии и стиля проектирования API. Например, REST API, SOAP API и GraphQL API обычно реализуют синхронное взаимодействие, хотя могут работать в асинхронном режиме. Для этого можно использовать HTTP Long Polling в REST API, спецификации WS-Addressing и WS-ReliableMessaging в SOAP API и запросы типа подписка (subscription) в GraphQL API. gRPC изначально спроектирован с поддержкой асинхронных вызовов, чтобы клиенты и серверы могли обрабатывать запросы и ответы без блокировки потоков и поддерживать стриминг данных в обоих направлениях.
С точки зрения безопасности, аутентификация и авторизация (RBAC, ABAC, или другая политика) позволят гарантировать, что только определенные акторы имеют возможность выполнить конкретный юзкейс. В случае веб-приложений эти требования трассируются в выбор механизмов аутентификации (JWT, Cookie, OAuth и пр.), т.е. данных для подтверждения личности актора, обычно передаваемых в заголовке сообщения. Для SOAP API здесь больше возможностей, поскольку протокол SOAP работает поверх HTTP, можно передать дополнительные аутентификационные данные в заголовке SOAP-сообщения, кроме тех, что передаются в HTTP-запросе.
Также требования к защите передаваемых данных влияют на выбор протоколов передачи, например, шифрование с использованием криптографических протоколов SSL/TLS. Требования к защите от внешних атак, такие как предотвращение SQL-инъекций, XSS и прочих подобных угроз, могут реализоваться через валидацию входных данных. Реализовать валидацию полезной нагрузки можно с помощью строгой схемы данных, что является неотъемлемой частью SOAP, GraphQL и gRPC API.
Возвращаясь к производительности API, при его проектировании также стоит учесть вопрос балансировки нагрузки, распределяя входящие запросов между несколькими экземплярами серверного приложения. При этом следует помнить про транзакционность юзкейсов и stateful/stateless-характер приложения. Stateful-приложения, где состояние, т.е. данные о предыдущих взаимодействиях с этим клиентом, хранится на сервере, сложнее масштабировать. В stateless-системах каждый запрос обрабатывается независимо, без привязки к предыдущим взаимодействиям, что проще и надежнее, но требует передавать в запросе больше контекстной информации.
С точки зрения транзакционности, при проектировании API важно четко определить, какие действия должны выполняться в рамках одной транзакции. Это поможет избежать частичных изменений данных и гарантирует, что все связанные операции успешно завершатся все вместе или будет общий откат в случае ошибки. Для транзакционных юзкейсов особенно важно, чтобы повторные запросы не приводили к некорректным состояниям системы, т.е. чтобы повторных вызовах API из-за сбоев сети или других проблем не возникали дубли или потери данных. Например, ВИ UC-1, UC-2, UC-7, UC-8, UC-9 и UC-10, связанные с операциями мутации (Create, Update, Delete) являются транзакционными. Поэтому API СЭД должен обеспечивать атомарность этих операций и согласованность их данных, например, через проверку какого-то уникального ключа в идемпотентном запросе. Также в API нужно предусмотреть изоляцию транзакций, чтобы действия одного пользователя не влияли на операции других. Проще всего реализовать такую изоляцию транзакций на уровне хранилища данных. Продолжительность транзакций тоже влияет на проектирование API. Например, в случае синхронного взаимодействия типа запрос-ответ длительные транзакции требуют увеличенных таймаутов, чтобы предотвратить преждевременное завершение запросов, или разделения большой транзакции на серию более мелких, обеспечивая их согласованное выполнение.
SOAP изначально разработан с поддержкой комплексных транзакций через WS-стандарты, например, WS-AtomicTransaction. Это позволяет выполнять распределённые транзакции, обеспечивая согласованность данных между различными сервисами и системами. В REST API, gRPC API и GraphQL API нет встроенной поддержки транзакций, но в RPC-подобных архитектура, т.е. gRPC API и GraphQL API она может быть реализована. Например, протокол двухфазной фиксации (2PC, Two-Phase Commit) в gRPC можно реализовать, создавая соответствующие сервисные методы для каждой фазы. В GraphQL это сложнее, но тоже возможно через последовательные вызовы мутаций с логикой координации на стороне сервера. Компенсационные транзакции (Saga) в gRPC можно реализовать, управляя последовательностью вызовов и обработкой ошибок на уровне клиента или сервера. В GraphQL это реализуется через цепочку мутаций с обработкой ошибок и вызовом компенсационных мутаций при необходимости.
Объем передаваемых данных также влияет на выбор технологии и стиля проектирования API. Например, gRPC для передачи данных использует протокол HTTP/2 и бинарный формат protobuf, что позволяет эффективно обрабатывать большие объемы информации с низкой задержкой и высокой производительностью. А в REST API при работе с большими объемами данных может возникнуть проблема многократных запросов, увеличивая задержки и нагрузку на сеть. XML-формат сообщений в SOAP API также увеличивает объем передаваемой информации, а GraphQL позволяет клиентам запрашивать именно те данные, которые им необходимы, снижая количество запросов. Таким образом, возможности и ограничения сети тоже влияют на проектирования API.
Наконец, при проектировании API необходимо предусмотреть версионность для обеспечения обратной совместимости при внесении изменений. Указывать нужную версию можно в пути или в заголовках запроса.
Возвращаясь к технологии проектирования, сведем ее в табличную форму, сопоставив функциональные требования с нефункциональными.
ВИ | Характер (синхронный или асинхронный) | Входные данные, максимальный объем | Необходимость валидации входных данных | Выходные данные, максимальный объем | Максимальное время отклика (ответа сервера на запрос клиента) | Максимальная частота (количество выполнений в секунду) | Транзакционность
(да/нет) |
Аутентификация
(да/нет) |
UC-1 | ||||||||
UC-2 |
Проанализировав получившуюся таблицу с учетом возможностей разработчиков и корпоративного техрадара с перечнем технологий, допустимых к использованию, следует сделать выбор между REST API, SOAP API, GraphQL API или gRPC API. Вполне возможно, что придется использовать комбинацию этих подходов, реализовав большую систему из нескольких микросервисов, что увеличит сложность ее разработки и поддержки, а также снизит общую надежность.
Выбрав REST в качестве архитектурного стиля для дизайна API, нужно сопоставить ВИ с конечными точками веб-сервиса, т.е. определить URL ресурса и HTTP-методы доступа к нему. Еще нужно определить формат и схему данных полезной нагрузки HTTP-запросов и ответов, статусы ответов сервера на клиентские запросы и методы аутентификации в заголовке HTTP-запроса. Сперва это можно сделать в таблице.
ВИ | URL ресурса | HTTP-метод | Формат входных данных | Схема входных данных | Статусы ответа сервера | Формат выходных данных | Схема выходных данных | Аутентификация
в заголовке запроса |
UC-1 | /document | POST | JSON |
{ "type": "object", "properties": { "name": { "type": "string" }, "type": { "type": "string" }, "content": { "type": "object", "properties": { "file_path": { "type": "string" }, "file_type": { "type": "string" } }, "required": [ "file_path", "file_type" ] } }, "required": [ "name", "type", "content" ] } |
201 – документ создан
400 – некорректный ввод
|
JSON |
{ "type": "object", "properties": { "doc_number": { "type": "string" }, "title": { "type": "string" }, "created_at": { "type": "string" }, "state": { "type": "number" } }, "required": [ "doc_number", "title", "created_at", "state" ] }
|
JWT-токен пользователя в заголовке запроса |
UC-2 |
Далее на основании этой таблицы нужно разработать спецификацию OpenAPI (Swagger), пример которой я показывала здесь.
В случае RPC API следует определить функцию, которую надо вызвать на удаленном сервере для реализации того или иного юзкейса. Это также можно сделать сперва в табличном виде.
ВИ | Удаленная функция | Входные параметры (схема данных) | Результат (схема данных) | Дополнительные данные в заголовке запроса |
UC-1 | ||||
UC-2 |
Для gRPC API эта таблица поможет написать proto-определение сервиса, пример которого я приводила здесь. Для SOAP API такая таблица поможет определить функции в коде серверного приложения, а для GraphQL API – запросы, которые будут выполняться на удаленном сервере. Как это сделать, вы узнаете на моих курсах в Школе прикладного бизнес-анализа и проектирования информационных систем в нашем лицензированном учебном центре обучения и повышения квалификации системных и бизнес-аналитиков в Москве: