Что можно делать с аннотациями контрактов микросервисов?
В прошлом посте мы рассказывали о том, как и почему мы в Acronis делаем аннотации к микросервисам, и обещали поделиться своей практикой применения единого формата API для всей платформы Acronis Cyber Platform. Сегодня мы расскажем про свой опыт статических проверок аннотаций — aka первый шаг на пути внедрения аннотаций в компании.
Итак, предположим, что у вас уже есть аннотации ко всем или большинству API. Резонный вопрос, который нам также задавали коллеги: «А что можно делать с этими магическими объектами?»
На самом деле существует два типа проверок, которые можно проводить прямо по аннотации. Статические проверки проходят прямо по текстам аннотаций Swagger или RAML. Им больше ничего не нужно, и они по праву могут считаться первым плюсом использования формализованных контрактов микросервисов.
Динамические проверки могут быть запущены только в том случае, если у вас есть работающий сервис. Они несколько сложнее, так как позволяют нырнуть глубже и проверить, насколько вообще валидна аннотация, протестировать устойчивость сервиса, а также сделать многое другое. Но это тема следующего поста, а сегодня мы сосредоточимся на статике.
Да здравствует API Guideline!
Чтобы не запутывать ни себя, ни других, стоит сразу же оговориться, что статические проверки аннотаций создаются для контроля соответствия аннотаций API (и, будем надеяться, самих API) корпоративным требованиям. Это могут быть или просто практики, принятые в компании, или полноценный API Guideline, который формализует правила подготовки API для ваших сервисов.
Когда мы говорим о пестром мире микросервисов, это очень важно, потому что у каждой команды может быть свой фреймворк, свой язык программирования, уникальный стек или какие-то особенные библиотеки. Под влиянием практик, специфичных для микросервиса, меняется и представление API для внешнего наблюдателя. Это создает излишнее разнообразие. Для эффективного взаимодействия элементов экосистемы (или платформы), необходимо «выровнять» API, насколько возможно.
Приведем пример: в API одного компонента возвращают код 404, только если ресурс не существует. А другая команда привязывают эту ошибку к логике приложения и API вернет 404, когда пользователь хочет купить товар, который закончился на складе. Наглядно подобные проблемы очень хорошо описал atygaev здесь. Эта неконсистентность в понимании 404 кода будет тормозить разработку и приводить к ошибкам, а значит — к лишним обращения в поддержку или лишним часам дебага, но в любом случае к проблемам, измеряемым в денежном эквиваленте.
Специфика синтаксиса и нейминга, принятых в компании, дополняются различными аспектами, характерными непосредственно для REST. Разработчик должен ответить на такие вопросы, как:
- Будет ли POST идемпотентным или нет?
- Какие коды ошибок мы используем и что они значат?
- Можно ли добавлять к кодам ошибок бизнес-логику?
А теперь представьте, что каждая команда должна в отдельности ломать голову над этими ответами.
Формат поискового запроса также может быть совершенно разным. Например, существует с десяток способов сформировать выборку, в которой будут приведены только те пользователи, у которых МacBookPro и частые вирусные атаки. При работе над крупным проектом, состоящим из десятков микросервисов, необходимо, чтобы синтаксис поискового запроса был общим для всех версий и продуктов компании. И если человек привык обращаться к одному из продуктов/сервисов вашей разработки, он ожидает встретить такую же структуру запроса и ответа в другом. Иначе изумления (и огорчения) будет не избежать.
У многих компаний, особенно у гигантов, таких как Microsoft, PayPal, Google, имеются свои гайдлайны, и они очень хорошо продуманы. Но использовать чужие гайдлайны не всегда оказывается возможным, потому что они во многом привязаны к специфике работы компании. К тому же мышление у всех устроено по-разному, и правила могут отличаться просто потому, что людям удобнее работать так, а не иначе.
Понимание того, что Acronis нуждается в собственном API Guideline пришла не сразу, а с ростом количества разработчиков, микросервисов и собственно внешних клиентов и интеграций. В какой-то момент команде архитекторов пришлось установить единые правила декларирования API приняв во внимание как опыт упомянутых выше IT гигантов, так и уже устоявшихся де-факто практик в командах разработки.
Одной из проблем, при таком позднем внедрении API Guideline являются уже существующие опубликованные API, не соответствующие требованиям, что в свою очередь приводит к дополнительным затратам на переработку интерфейсов и на поддержание обратной совместимости. Поэтому если ваша бизнес модель предполагает будущую интеграцию и публикацию API, то думать о единых правилах нужно как можно раньше.
Конечно, принятие API Guideline не сделало все API конститентными автоматически. Каждый API должен был быть проанализирован, каждый девлид должен был понимать новые требования. Поэтому мы сделали автоматизацию проверок RAML против разработанного нами API Guideline.
Статический анализ
Сначала необходимо определиться, что мы будем статически анализировать. Далеко не все пункты API Guideline поддаются формализации, так что сначала необходимо выделить набор правил, которые можно легко понимать из аннотации API.
В первой версии мы выделили следующие правила:
- Проверка наличия описаний API. Как мы говорили в нашей предыдущей статье, на основе аннотации API можно создавать красивую документацию. Это означает, что каждый ресурс, query parameter и response должен иметь описание, которое даст всю необходимую информацию любому пользователю нашего API. Казалось бы, мелочь, но зато как легко показывать разработчикам, что их аннотация небогата на описания!
- Проверка наличия и корректности примеров. Языки для создания аннотаций API также позволяют описывать примеры. Например, в response мы можем добавить пример реального ответа сервиса, что-то из реальной жизни. Использование примеров подсказывает, как endpoint должен использоваться и работать и мы проверяем наличие примеров за счет статического анализа аннотаций.
- Проверка корректности кодов HTTP-ответов. Стандарт HTTP определяет большое количество кодов, однако интерпретировать их можно по разному. Наш гайдлайн формализует использование кодов. Мы также ограничиваем применимость разных кодов в соответствии с методом HTTP. Например, код 201, согласно спецификации HTTP, возвращается при создании ресурса. Поэтому GET, возвращающий 201, будет тревожным звоночком (либо код используется неверно, либо GET создает ресурс). Поэтому наш API Guideline запрещает подобное использование. Более того, архитекторами были зафиксированы наборы кодов для каждого метода, и теперь мы проверяем их в статическом режиме на уровне аннотаций.
- Проверка методов HTTP. Все хорошо знают, что протокол HTTP имеет набор стандартных методов (GET, POST, PUT и т.д.), а также дает возможность использования кастомных методов. Наш API Guideline описывает разрешенные методы, допустимые, но нежелательные (OPTIONS), а также полностью запрещенные (можно использовать только с благословления архитекторов). К запрещенным относится стандартный метод HEAD, а также любые кастомные методы. Все это также легко проверить в аннотациях к контрактам.
- Проверка прав доступа. Надеемся, что в 2019 году необходимость поддержки авторизации не требует дополнительных пояснений. Хорошая документация должна содержать информацию о том, какие роли поддерживает API и какие методы API доступны для каждой роли. Кроме документирования информация о ролевой модели, зафиксированная на уровне аннотаций, позволяет делать намного более интересные вещи в динамике, однако об этом мы расскажем в следующей статье.
- Проверка корректности нейминга. Снимает головную боль от использования разных подходов к наименованию сущностей. Вообще существует два «лагеря» — это сторонники CamelCase и snake_case.Camelcase — когда каждое слово начинается с заглавной буквы, а snake_case — когда слова разделяются нижним подчеркиванием и все написано маленькими буквами. В разных языках принято давать имена по-разному. Например, В Python принят snake_case, а в Go или Java — CamelCase. Наш собственный выбор мы сделали универсальным для всех проектов и зафиксировали в API Guideline. Поэтому через аннотации мы проверяем названия ресурсов и параметров статически;
- Проверка корректности кастомных хедеров. Это еще один пример проверки нейминга, но привязанный именно к хедерам. У нас в Acronis принято использовать формат вида X-Custom-Header-Name (несмотря на то, что такой формат признан устаревшим). И мы контролируем его соблюдение на уровне аннотаций.
- Проверка поддержки HTTPS. Любой современный сервис должен поддерживать HTTPS, а некоторые считают, что в наше время работать с HTTP — это вообще моветон. Поэтому в аннотации RAML или Swagger должно быть указано. что микросервис поддерживает HTTPS без HTTP.
- Проверка структуры URI. В доисторические времена, то есть до релиза API Guideline, URI запроса выглядел по разному в разных сервисах: где-то /api/2, где-то /api/service_name/v2 и так далее. Теперь у нас в гайдлайне определена стандартная структура URI для всех наших API. Руководство описывает, как они должны выглядеть, чтобы не создавать путаницы.
- Проверка совместимости новых версий. Еще один фактор, который должен держать в голове автор любого API — обратная совместимость. Важно проверить, сможет ли код, построенный на базе старого API, работать с новой версией. Исходя из этого изменения можно разделить на две категории: ломающие обратную совместимость и совместимые. Всем известно, что ломающие изменения недопустимы, и при желании что-то кардинально «наулучшать», разработчик должен выпускать новую версию API Но все могут ошибаться, так что еще одна наша цель на этапе статических проверок — пропускать только совместимые изменения и ругаться на несовместимые. Предполагается, что аннотация, как и код, хранится в репозитории и, следовательно, для нее имеется вся история изменений. Следовательно, мы можем проверять наш HTTP REST на предмет обратной совместимости. Например, добавление (метода, параметра, кода ответа) совместимости не нарушает, а удаление вполне может стать причиной потери совместимости. А это уже можно проверить на уровне аннотации.
Выводы
Статический анализ аннотаций нужен для того, чтобы проверить (нет, не качество сервиса), но качество API. Этот этап позволяет выровнять программные интерфейсы относительно друг друга, чтобы люди работали в четкой и понятной среде, где все достаточно предсказуемо.
Конечно, заниматься подобным формализмом нужно только в достаточно больших компаниях, когда проверка всех соответствий «вручную» оказывается очень долгой, дорогой и ненадежной. Небольшому стартапу это попросту не нужно. По крайней мере, до поры, до времени. Но если в вашем будущем есть планы стать единорогом, как и Акронис, то статические проверки и API Guideline вам в помощь.