Привет, %username%!
Ты наверняка знаешь, что такое API интерфейсы и то, как много от них зависит в твоем проекте. Более того, я так же полагаю, что ты уже знаком с тем, что такое API first подход и знаешь, что Swagger и его Open API являются одними из самых популярных инструментов, помогающих ему следовать.
Но в этой статье я хочу рассказать про подход к реализации API first, концептуально отличающийся от того, что предлагает Swagger и Apiary. Во главе идеи стоит понятие Single contract и возможность его реализации на базе RAML 1.0.
Под катом:
- Краткое описание принципов API first;
- Single contract – ввод понятия, предпосылки к появлению, рассмотрение возможности его реализации на базе OAS (Swagger);
- RAML + annotations + overlays как база для Single contract, примеры;
- Проблемы RAML, концептуальные разногласия разработчиков;
- Идея SaaS сервиса на базе вышеизложенной идеи (картинка прототипа сверху).
От API first на Swagger до Single contract на RAML
При проектировании современных программных систем часто встает задача согласования и разработки интерфейсов для взаимодействия их компонентов друг с другом. В последнее десятилетие огромную популярность и развитие получили SPA и thick мобильные приложения взаимодействующие с сервером через API интерфейсы. Если раньше разработка интерактивного веб сайта происходила путем поэтапных правок кода серверной стороны для генерации HTML разметки с ее последующей передачей браузеру клиента, то теперь разработка динамических веб приложений сместилась в сторону создания единого API сервиса и параллельной разработки множества приложений (в том числе и SPA) работающих с этим API как с главным источником данных. Такой подход позволяет более удобно разделять задачи, организовывать команды, специализирующиеся только на конкретных технологиях (привлекать более узконаправленных специалистов), организовать параллельную разработку на самых первых этапах, а также позволяет создать единую точку коммуникации — API интерфейс.
Такая единая точка коммуникации требует формального и однозначного определения, этим документом является API спецификация. Для разработки и документирования API спецификаций сегодня применяются различные технологии и языки, например: OAS (Swagger), Apiary и RAML.
Следующие три пункта определяют природу API first подхода:
- API интерфейс должен быть самым первым клиентским интерфейсом разрабатываемого приложения;
- В первую очередь разрабатывается API-спецификация, а затем программная часть ее клиентов;
- Этапы жизни API интерфейса должны совпадать с этапами жизни его документации.
Если рассматривать процесс отталкиваясь от вышеизложенного, то API спецификация лежит в центре процесса разработки, а все узлы, составляющие систему и использующие этот API как шлюз взаимодействия, являются клиентами API спецификации. Таким образом, серверную часть системы можно считать таким же клиентом API спецификации, как и любой другой узел, использующий API для коммуникации с ним. Доменные модели прикладной области не обязательно должны совпадать с моделями, описанными в API-спецификации. Их возможные намеренные совпадения со структурами классов в коде клиентских приложений или со структурами схем БД вводятся скорее для упрощения процесса разработки, например, при использовании кодогенератора по OAS спецификации. Логически, описанное выше можно подвести под определение Single contract. Single contract — many clients.
Single Contract. Контрактные инструменты и библиотеки
Термин Single contract не претендует ни на какое участие в критике за его использование в тексте статьи. Его приминение в данном контексте является лично моей придумкой.
Расширение понятия API first, до более общего Single contract позволяет рассматривать API-спецификацию не только как формальное описание интерфейса взаимодействия между компонентами системы, но и как единый контракт, используемый любым количеством внешних библиотек и инструментов в качестве источника конфигурации. В таком случае, эти инструменты и библиотеки можно воспринимать как клиенты контракта наравне со SPA или мобильными приложениями. Примерами таких клиентов могут быть:
- Генератор документации
- API mock-server
- Сервис нагрузочного тестирования
- Библиотека валидации запросов/ответов
- Кодогенератор
- Генератор UI интерфейсов
- и т. д.
Single contract для таких клиентов является единым файлом конфигурации и источником данных. Контрактные инструменты работают только на основе информации, полученной из конкретного контракта. Очевидно, что для полноценного функционала таких разнородных клиентов как API mock server одного описания API не достаточно, нужна дополнительная метаинформация, например, описание связей между GET параметрами запроса (id ресурса) и данными, которые должен вернуть сервер, подсказки, указывающие на поля ответа и параметры запроса, использующиеся для организации пагинации. Далее этот пример будет рассмотрен более подробно. Специфичная информация для конкретных инструментов, в то же время, должна существовать и поддерживаться неразрывно от основного документа, иначе это приведет к нарушению концепции единого контракта.
Swagger (OAS) как инструмент описания Single contract
Существующие наиболее популярные на рынке Swagger (OAS) и Apiary (Blueprint) позволяют описывать HTTP API интерфейсы, используя специальные языки: Open API на базе YAML или JSON, Blueprint на базе Markdown, что делает спецификации легко читаемыми. Также существует множество инструментов и библиотек, созданных большим open-source сообществом. Swagger на данный момент широко распространен и, можно сказать, стал де-факто стандартом API first. Многие внешние системы поддерживают импорт Swagger спецификаций, например SoapUI, Readme.io, Apigee и т.д. Кроме того, существующие SaaS Swagger Hub и Apiary позволяют пользователям создавать проекты, загружать или создавать свои спецификации, пользоваться встроенными генераторами документации и mock-серверами, а также публиковать ссылки для доступа к ним извне.
Swagger вместе с его OAS 3.0 выглядят довольно уверенно и его функционала для описания API (особенно простого) хватает в большинстве случаев. Далее приведен список плюсов и минусов Swagger:
Плюсы:
- Понятный и легко читаемый язык описания;
- Большое open-source сообщество;
- Множество как официальных, так и open-source редакторов, генераторов, библиотек;
- Наличие основной команды разработки, постоянно работающей над развитием и улучшением формата;
- Условно бесплатный хаб для спецификаций;
- Подробная официальная документация;
- Низкий порог вхождения.
Минусы:
- Слабая поддержка модульности;
- Отсутствие автогенерации примеров ответов запросов на основании описания их структур;
- Нередки проблемы с плохой стабильностью продуктов SmartBear (авторы swagger) и запоздалой реакцией на это разработчика (мнение основано сугубо как на личном опыте использования, так и на опыте нашей команды).
Но главным ограничением не позволяющим использовать OAS как средство описания Single contract является отсутствие возможности прикрепления пользовательской мета информации для описания дополнительных параметров целевых инструментов/библиотек.
Поэтому все инструменты, работающие на основе Swagger-спецификаций, обязаны довольствоваться тем набором информации, что способен в себя вместить базовый формат.
Например, реализация «умного» mock api server требует больше информации, чем способен дать документ спецификации, именно поэтому встроенный в Swagger Hub mock API способен только на генерацию фейковых данных на основе типов данных/структур, полученных из документа спецификации. Несомненно, этого мало и такая функциональность mock-сервера может удовлетворить лишь несложный API клиент.
В нашей компании, в ходе разработки одного из проектов (React SPA + API server), потребовался следующий функционал mock-сервера:
- иммитация пагинации. Сервер не должен возвращать совершенно случайные значения полей currentPage, nextPage, pagesTotal в ответе на запросы списков, а уметь имитировать реальное поведение механизма пагинации с генерацией значений этих метаполей в зависимости от полученного значения page от клиента;
- генерация тел ответов содержащих различные наборы данных в зависимости от конкретного параметра входящего запроса;
- умение выстраивать реальные связи между фейковыми объектами: поле foo_id сущности Bar должно ссылаться на ранее сгенерированную Foo сущность. Этого можно добиться путем добавления поддержки идемпотентности mock-сервером;
- имитация работы различных методов авторизации: OAuth2, JWT и т д.
Без всего этого очень сложно разрабатывать SPA параллельно разработке серверной части системы. И, в то же время, такой mock-сервер, по причине описанной ранее, почти невозможно реализовать без дополнительной специфичной мета информации, которая могла бы храниться прямо в API-спецификации и информировать его о требуемом поведении при имитации очередного эндпоинта. Эту проблему можно решить добавлением требуемых параметров в виде отдельного файла с конфигурациями параллельно базовой OAS спецификации, но, в таком случае, поддерживать такие два разных источника нужно будет по отдельности.
Если инструментов, работающих в окружении процесса разработки по такому принципу, будет больше чем один mock-сервер, то мы получим “зоопарк” инструментов, каждый из которых, при наличии своего уникального функционала, вынужден иметь свой уникальный файл конфигурации, логически привязанный к базовой API-спецификации, но фактически расположенный отдельно и живущий “своей жизнью”.
Проблема: разработчик будет вынужден поддерживать актуальность всех конфигураций вслед за изменением версий базовой спецификации, зачастую в совершенно разных местах и форматах.
Некоторые примеры сервисов, работающих по схожему принципу:
- SoapUI – система для тестирования REST & SOAP интерфейсов. Поддерживает импорт проекта из Swagger-спецификации. При изменении базовой спецификации Swagger, конфигурация проекта основанного на перечне API вызовов, продолжает существовать параллельно и требует ручной синхронизации;
- Другие продукты SmartBear;
- Apigee – сервис для управления жизненным циклом API. Использует Swagger-спецификации в качестве шаблонов, на основе которых, позволяет инициализировать свои конфигурации внутренних сервисов. Автоматическая синхронизация также отсутствует;
- Readme.io – сервис который позволяет создавать красивую документацию на основе Swagger-спецификации, а также имеет механизм отслеживания изменений базовой спецификации и разрешения конфликтов путем обновления конфигурации проекта на стороне сервиса. Наверняка, это потребовало излишней сложности разработки данного сервиса.
К этому списку можно добавить многие другие сервисы, предоставляющие функцию интеграции со Swagger-спецификацией. Интеграция для большинства из них означает обычное копирование базовой структуры Swagger-спецификации и последующее автозаполнение полей локальных конфигураций без поддержки синхронизации с изменениями базовой спецификации.
RAML, annotations, overlays
Стремление найти инструмент, исключающий ранее указанное ограничение OAS, позволяющий рассматривать спецификацию как единый контракт для всех клиентских инструментов, привело нас к знакомству с языком RAML. Про RAML достаточно написано, можно прочитать, например, здесь https://www.infoq.com/articles/power-of-raml. Разработчики RAML попытались заложить в язык поддержку модульности на уровень его концепции. Теперь каждая компания или индивидуальный разработчик может создавать свои или использовать уже готовые публичные словари при проектировании API, переопределять и наследовать уже готовые модели данных. Начиная с версии 1.0 RAML поддерживает 5 различных типов внешних модулей: include, library, extension, trait, overlay, что позволяет применять каждый из них в зависимости от задачи максимально гибко.
Пришло время обсудить главную возможность RAML, которая, по не совсем понятным причинам, не имеет аналогов в OAS и Blueprint — Annotations.
Annotations в RAML — это возможность прикрепления пользовательских метаданных к базовым структурам языка.
Именно эта функция RAML и стала поводом для написания этой статьи.
Пример:
#%RAML 1.0
title: Example API
mediaType: application/json
# Annotation types block may be placed into external file
annotationTypes:
validation-rules:
description: |
Describes strict validation rules for the model properties.
Can be used by validation library
allowedTargets: [ TypeDeclaration ]
type: string[]
info-tip:
description: |
Can be used by Documentation generator for showing tips
allowedTargets: [ Method, DocumentationItem, TypeDeclaration ]
type: string
condition:
description: |
Named example can be returned if condition is evaluated to true.
Can be used by Intelligent mock server
allowedTargets: [ Example ]
type: string
types:
Article:
type: object
properties:
id:
type: integer
title: string
paragraphs: Paragraph[]
createdAt:
type: string
(validation-rules): ["regex:/d{4}-[01]d-[0-3]dT[0-2]d:[0-5]d:[0-5]d(?:.d+)?Z?/"]
Paragraph:
type: object
properties:
order:
type: integer
(validation-rules): ["min:0"]
content: string
(validation-rules): ["max-length:1024"]
/articles/{articleId}:
get:
(info-tip): This endpoint is deprecated
description: Returns Article object by ID
responses:
200:
body:
application/json:
type: Article
Сами структуры пользовательских аннотаций должны иметь четкие описания на языке RAML. Для этого используется специальная секция annotationTypes, определения из которой, также можно вынести во внешний модуль. Таким образом, появляется возможность определения специальных параметров внешнего инструмента в виде аннотаций, привязанных базовому определению API RAML. Во избежание захламления базовой спецификации огромным количеством аннотаций для различных внешних инструментов, имеется поддержка возможности их выноса в отдельные файлы — overlays (а можно и в extensions), с классификацией по области применения. Вот что сказано про overlays в документации к RAML (https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md#overlays):
An overlay adds or overrides nodes of a RAML API definition while preserving its behavioral, functional aspects. Certain nodes of a RAML API definition specify the behavior of an API: its resources, methods, parameters, bodies, responses, and so on. These nodes cannot be changed by applying an overlay. In contrast, other nodes, such as descriptions or annotations, address concerns beyond the functional interface, such as the human-oriented descriptive documentation in some language, or implementation or verification information for use in automated tools. These nodes can be changed by applying an overlay.
Overlays are particularly important for separating interface from implementation. Overlays enable separate lifecycles for the behavioral aspects of the API that need to be controlled tightly, such as a contract between the API provider and its consumers, versus those needing little control, such as the human- or implementation-oriented aspects that can evolve at different paces. For example, adding hooks for testing and monitoring tools, appending metadata relevant to a registry of APIs, or providing updated or translated human documentation can be achieved without changing any aspects of the behavioral aspects of the API. These things can be controlled through a rigorous version and change management process.
Другими словами, данный функционал позволяет “отделять зерна от плевел”, например, основное описание API-спецификации, от дополнительной метаинформации специфичной для конкретного инструмента использующего ее для работы. Метаинформация в каждом отдельном оверлее “навешивается” на различные блоки спецификации в виде аннотаций.
Пример базовой структуры:
#%RAML 1.0
title: Phrases API
mediaType: application/json
types:
Phrase:
type: object
properties:
content: string
/phrases:
get:
queryParameters:
whoSaid: string
responses:
200:
body:
application/json:
type: Phrase
Overlay:
#%RAML 1.0 Overlay
usage: Applies annotations for Intelligent mock server
extends: example_for_article_2_1.raml
annotationTypes:
condition:
description: |
Named example
can be returned
if condition is
evaluated to true
type: string
allowedTargets: Example
/phrases:
get:
responses:
200:
body:
application/json:
examples:
firstExample:
(condition): $whoSaid is Hamlet
content: "To be, or not to be?"
secondExample:
(condition): $whoSaid is Homer Simpson
content: "D'oh!"
В результате появляется возможность реализации единого контракта: вся функциональная, поведенческая и метаинформация хранится и версионируются в одном едином месте, а контрактные инструменты — клиенты контракта, должны иметь поддержку используемых аннотаций в данной спецификации. С другой стороны, именно сами инструменты могут предъявлять собственные требования к аннотациям, которые необходимо “навесить” на спецификацию — это обеспечит более широкий круг возможностей при разработке контрактных инструментов.
Вышеизложенная концепция изображена на рисунке ниже:
Среди минусов данного подхода можно выделить высокую сложность ручной синхронизации файла базовой спецификации и каждого из оверлеев: при обновлении структуры базовой спецификации, нужно применить требуемые изменения в структурах оверлеев. Эта проблема становится более серьезной при появлении более чем одного оверлея.
Возможным и самым очевидными решением будет разработка специального редактора или дополнения для существующего онлайн редактора RAML https://github.com/mulesoft/api-designer. Область редактирования остается неизменной, но появляется возможность создания табов: каждый новый таб является окном для редактирования закрепленного за ним оверлея. При редактировании базовой структуры спецификации в основном окне, меняются также структуры во всех созданных табах, а при обнаружении несовместимости новой структуры с уже существующими аннотациями размещенных в табах-оверлеях появляется предупреждение. Более детальное рассмотрение такого редактора является отдельной темой и заслуживает серьезного обдумывания.
Существующие наработки
В ходе поиска существующих решений, близких к реализации идеи использования аннотаций как средства описания метаинформации, были найдены следующие решения:
- https://github.com/raml-org/raml-annotations репозиторий содержащий официальные аннотации утвержденные сообществом разработчиков RAML. В текущей версии доступны только OAuth2 аннотации. Могут быть использованы внешними инструментами для получения метаинформации описывающей аспекты реализации OAuth2 для разрабатываемой API спецификации;
- https://github.com/petrochenko-pavel-a/raml-annotations библиотека аннотаций пользователя @petrochenko-pavel-a с логической группировкой по областям применения. Проект скорее экспериментальный, но отлично иллюстрирует идею использования аннотаций. Наиболее интересные группы аннотаций:
- additionalValidation.raml – аннотации для описания дополнительных правил валидации моделей спецификации. Могут использоваться, например, серверной библиотекой для валидации запросов по RAML-спецификации;
- mock.raml – аннотации для описания деталей работы mock сервера основанного на RAML-спецификации;
- semanticContexts.raml – аннотации, указывающие на семантический контекст отдельных объявляемых структурных блоков RAML спецификации;
- structural.raml – аннотации, уточняющие роль отдельной RAML сущности в общей структуре описываемой доменной модели;
- uiCore.raml – пример аннотаций возможных для использования инструментами генерации UI интерфейсов на основе RAML спецификации;
Также в репозитории содержатся библиотеки служебных типов пригодных для использования в качестве примитивов при описании структур данных RAML спецификации.
Проблемы RAML
Несмотря на функциональность, прогрессивность базовой идеи, внимания со стороны крупных производителей программного обеспечения (cisco, spotify, vmware и т.д.), на сегодняшний день RAML имеет серьезные проблемы, которые могут стать фатальными относительно его успешной судьбы:
- Малое и разрозненное open-source сообщество;
- Непонятная стратегия основного разработчика RAML — mulesoft. Компания разрабатывает продукты которые являются лишь копией существующих решений на основе OAS (входящие в состав Anypoint Platform), вместо того чтобы создавать сервисы подчеркивающие преимущества RAML перед Swagger;
- Последствие первого пункта: малое количество open-source библиотек/инструментов;
- Более высокий порог вхождения чем у OAS (это странно, но многие считают именно так);
- Ввиду большого количества багов и проблем с UX/UI совершенно непригодный и отталкивающий пользователей главный сервис, точка входа в RAML — https://anypoint.mulesoft.com/.
Концептуальные разногласия. Первое заключение
Существуют противоречия внутри сообщества относительно базовой концепции. Кто-то считает, что RAML это model definition language, а кто-то что это API definition language как OAS или Blueprint (ребята которые зовут себя разработчиками RAML часто упоминают про это в различных комментариях). Концепция model definition language позволила бы внутри RAML спецификации описывать именно доменную модель предметной области без жесткой привязки к контексту описания API ресурсов, расширяя этим горизонты вариантов использования спецификации внешними инструментами (фактически создавая фундамент для существования настоящего Single contract!). Вот такое определение понятия ресурса можно увидеть на сайте readhat docs (http://restful-api-design.readthedocs.io/en/latest/resources.html, кстати, всем рекомендую к прочтению этот замечательный гайд по проектированию API):
We call information that describes available resources types, their behavior, and their relationships the resource model of an API. The resource model can be viewed as the RESTful mapping of the application data model.
В RAML application data model это типы объявленные в блоке types, а resource model of an API — это то, что описывается в блоке resources RAML. Следовательно, необходимо наличие возможности описания этого mapping. Но текущая реализация RAML позволяет сделать такой mapping только 1 к 1, то есть, использовать types “как есть” внутри объявления API ресурсов.
Я считаю это самой главной проблемой языка, решение которой, позволит RAML выйти за рамки API definition language и стать полноценным model definition language: более общим языком (нежели чем OAS или Blueprint) используемым для описания единых контрактов систем, по сути своей, являющихся формальным ядром множества их компонентов.
Выше перечисленное делает RAML слабым игроком, который в данный момент не способен выиграть конкурентную борьбу у Swagger. Возможно, именно поэтому в результате главный разработчик RAML пошел на кардинальные меры https://blogs.mulesoft.com/dev/api-dev/open-api-raml-better-together/
Идея Single contract RAML SaaS
Базируясь на понятии Single contract, отталкиваясь от идеи
Новый сервис, по аналогии со Swagger hub, будет являться
Технически каждый таб будет являться абстракцией над RAML overlay содержащим аннотации каждого конкретного плагина. Этим гарантируется совместимость спецификации с любым инструментом поддерживающим RAML 1.0.
Каталог плагинов должен быть открытым для расширения open source сообществом. Также возможна реализация платных плагинов, что может послужить стимулом для разработки новых.
Возможные плагины: API документация с поддержкой большого количества аннотаций для гибкой параметризации ее рендеринга, “умный” mock сервер (из примера выше), скачиваемые библиотеки для валидации запросов или кодогенерации, средства отладки исходящих запросов API для мобильных приложений (caching proxy), нагрузочные тесты с настройкой flow тестирования через аннотации, различные плагины для интеграции с внешними сервисами.
Данная идея сервиса содержит явные преимущества перед существующими сервисами для управления API-спецификациями и ее реализация закладывает начало возможного изменения подхода к имплементации любых внешних систем так или иначе связанных с API.
Второе заключение
Целью этой статьи не является критика Swagger, Apiary или других де-факто стандартных инструментов для разработки API, а скорее рассмотрение концептуального различия c подходом к проектированию спецификаций продвигаемым RAML, попыткой введения понятия Contract first и рассмотрения возможности его реализации на базе RAML. Другой целью явилось желание привлечь к RAML вполне заслуженного внимания разработчиков для дальнейшего возможного развития его сообщества.
Официальный сайт RAML
Slack канал
Спецификация
Спасибо за внимание.
Автор: andrew_sparrow