Мы все любим иметь новые блестящие инструменты, но ненавидим рутинную работу по их постоянному обновлению. Это применимо ко всему: операционным системам, приложениям, API, пакетам Linux. Больно, когда наш код перестает работать из-за обновления, и вдвойне больно, когда обновление даже не было инициировано нами.
При разработке веб-API вы постоянно рискуете сломать пользовательский код с каждым новым обновлением. Если ваш продукт — API, то эти обновления каждый раз будут ужасать. Основными продуктами Monite являются наш API и SDK с белой этикеткой. Мы являемся компанией, ориентированной на API, поэтому мы очень заботимся о том, чтобы наш API был стабильным и простым в использовании. Следовательно, проблема нарушения изменений находится в верхней части нашего списка приоритетов.
Распространенное решение — выдавать клиентам предупреждения об устаревании и редко выпускать критические изменения. Внезапно выпуск ваших релизов теперь может занять месяцы, а некоторые функции должны оставаться скрытыми или даже не объединенными до каждого следующего выпуска. Это замедляет вашу разработку и вынуждает пользователей обновлять свою интеграцию каждые несколько месяцев.
Если вы будете выпускать релизы быстрее, вашим пользователям придется слишком часто обновлять свою интеграцию. Если вы увеличите время между выпусками, ваша компания будет двигаться медленнее. Чем неудобнее вы сделаете это для пользователей — тем удобнее будет для вас, и наоборот. Это, конечно, не оптимальный сценарий. Мы хотели двигаться в своем собственном темпе, не нарушая ничего для существующих клиентов, что было бы невозможно при обычном подходе к прекращению поддержки. Вот почему мы выбрали альтернативное решение: Управление версиями API.
Это довольно простая идея: выпустить все критические изменения в любое время, но скрыть их в новой версии API. Это дает вам лучшее из обоих миров: интеграция пользователей не будет регулярно нарушаться, и вы сможете двигаться с любой скоростью, которая вам нравится. Пользователи будут мигрировать, когда захотят, без какого-либо давления.
Учитывая простоту идеи, она идеально подходит для любой компании. Это то, что вы ожидаете прочитать в типичном инженерном блоге. К сожалению, это не так просто.
Управлять версиями API сложно, очень сложно. Его иллюзорная простота быстро исчезает, как только вы начинаете его реализовывать. К сожалению, Интернет никогда вас не предупреждает, поскольку ресурсов по этой теме на удивление мало. Абсолютное большинство из них спорят о том, куда поместить версию API, но лишь в нескольких немногочисленных статьях пытаются ответить: «Как нам реализовать это?». Наиболее распространенные из них:
Отдельные развертывания могут оказаться очень дорогими и сложными в поддержке, копирование отдельных маршрутов не очень хорошо масштабируется при больших изменениях, а копирование всего приложения создает так много дополнительного кода, что вы начнете тонуть в нем уже после нескольких версий.
Даже если вы попытаетесь выбрать самый дешевый вариант, вскоре вам придется столкнуться с бременем управления версиями. На первый взгляд это покажется простым: добавьте сюда еще одну схему, там еще одну ветку бизнес-логики и в конце продублируйте несколько маршрутов. Но при наличии достаточного количества версий ваша бизнес-логика быстро станет неуправляемой, многие из ваших разработчиков будут путать версии приложения и версии API и начнут версионировать данные в вашей базе данных, и ваше приложение станет невозможно поддерживать.
Вы можете надеяться, что у вас никогда не будет более двух или трех версий API одновременно; что вы сможете удалять старые версии каждые несколько месяцев. Это верно, если вы поддерживаете лишь небольшое количество внутренних потребителей. Но клиенты за пределами вашей организации не будут получать удовольствие от необходимости обновляться каждые несколько месяцев.
Версии API могут быстро стать одной из самых дорогостоящих частей вашей инфраструктуры, поэтому крайне важно заранее провести тщательное исследование. Если вы поддерживаете только внутренних потребителей, вам может быть проще использовать что-то вроде GraphQL, но оно может быстро стать таким же дорогим, как и управление версиями.
Если вы стартап, было бы разумно отложить создание версий API до более поздних этапов разработки, когда у вас появятся ресурсы, чтобы сделать это правильно. До тех пор может быть достаточно отказа от поддержки и стратегии дополнительных изменений. Ваш API не всегда будет выглядеть великолепно, но, по крайней мере, вы сэкономите много денег, избегая явного управления версиями.
После нескольких проб и множества ошибок мы оказались на распутье: наши предыдущие подходы к управлению версиями, о которых мы упоминали выше, были слишком дорогими в обслуживании. В результате наших усилий я разработал следующий список требований, которые потребуются от идеальной системы управления версиями:
К сожалению, альтернатив нашим существующим подходам практически не было. Именно тогда мне в голову пришла сумасшедшая идея: что, если мы попытаемся создать что-то сложное, что-то идеальное для этой работы — что-то вроде системы управления версиями API Stripe?
В результате бесчисленных экспериментов у нас теперь есть Cadwyn: платформа управления версиями API с открытым исходным кодом, которая не только реализует подход Stripe, но и существенно дополняет его. Мы будем говорить о его реализации Fastapi и Pydantic, но основные принципы не зависят от языка и платформы.
Проблема всех других подходов к управлению версиями заключается в том, что мы слишком много дублируем. Зачем нам дублировать весь маршрут, контроллер или даже приложение, если нарушена лишь небольшая часть нашего контракта?
При использовании Cadwyn всякий раз, когда разработчикам API необходимо создать новую версию, они вносят критические изменения в свои последние схемы, модели и бизнес-логику. Затем они создают изменение версии — класс, который инкапсулирует все различия между новой версией и предыдущей версией.
Например, предположим, что раньше наши клиенты могли создавать пользователя с адресом, но теперь мы хотели бы разрешить им указывать несколько адресов вместо одного. Изменение версии будет выглядеть так:
class ChangeUserAddressToAList(VersionChange): description = ( "Renamed `User.address` to `User.addresses` and " "changed its type to an array of strings" ) instructions_to_migrate_to_previous_version = ( schema(User).field("addresses").didnt_exist, schema(User).field("address").existed_as(type=str), ) @convert_request_to_next_version_for(UserCreateRequest) def change_address_to_multiple_items(request): request.body["addresses"] = [request.body.pop("address")] @convert_response_to_previous_version_for(UserResource) def change_addresses_to_single_item(response): response.body["address"] = response.body.pop("addresses")[0]
instructions_to_migrate_to_previous_version используются Cadwyn для генерации кода для старых версий схем API, а две функции конвертера — это трюк, который позволяет нам поддерживать столько версий, сколько нам нужно. Процесс выглядит следующим образом:
После того, как наши сопровождающие API создали изменение версии, им необходимо добавить его в наш VersionBundle, чтобы сообщить Cadwyn, что это VersionChange будет включено в какую-то версию:
VersionBundle( Version( date(2023, 4, 27), ChangeUserAddressToAList ), Version( date(2023, 4, 12), CollapseUserAvatarInfoIntoAnID, MakeUserSurnameRequired, ), Version(date(2023, 3, 15)), )
Вот и все: мы добавили критическое изменение, но наша бизнес-логика обрабатывает только одну версию — последнюю. Даже после того, как мы добавим десятки версий API, наша бизнес-логика по-прежнему будет свободна от логики управления версиями, постоянного переименования, if и преобразователей данных.
Изменения версий зависят от общедоступного интерфейса API, и мы почти никогда не добавляем критические изменения в существующие версии API. Это означает, что как только мы выпустим версию, она не будет сломана.
Поскольку изменения версий описывают критические изменения внутри версий, а в старых версиях критических изменений нет, мы можем быть уверены, что изменения наших версий полностью неизменяемы — у них никогда не будет причин для изменения. Неизменяемые сущности гораздо проще поддерживать, чем если бы они были частью бизнес-логики, поскольку она постоянно развивается. Изменения версий также применяются одно за другим, образуя цепочку преобразователей между версиями, которые могут перенести любой запрос на любую более новую версию и любой ответ на любую более старую версию.
Контракты API гораздо сложнее, чем просто схемы и поля. Они состоят из всех конечных точек, кодов состояния, ошибок, сообщений об ошибках и даже поведения бизнес-логики. Cadwyn использует тот же DSL, который мы описали выше, для обработки конечных точек и кодов состояния, но ошибки и поведение бизнес-логики — это совсем другая история: их невозможно описать с помощью DSL, их необходимо встроить в бизнес-логику.
Из-за этого поддерживать такие изменения версий намного дороже, чем все остальные, поскольку они влияют на бизнес-логику. Мы называем это свойство «побочным эффектом» и стараемся избегать их любой ценой из-за необходимости их обслуживания. Все изменения версии, которые изменят бизнес-логику, должны быть помечены как имеющие побочные эффекты. Это позволит узнать, какие изменения версии «опасны»:
class RequireCompanyAttachedForPayment(VersionChangeWithSideEffects): description = ( "User must now have a company_id in their account " "if they want to make new payments" )
Это также позволит специалистам по обслуживанию API проверять, использует ли клиентский запрос версию API, которая включает этот побочный эффект:
if RequireCompanyToBeAttachedForPayment.is_applied: validate_company_id_is_attached(user)
Cadwyn имеет множество преимуществ: он значительно снижает нагрузку на наших разработчиков и может быть интегрирован в нашу инфраструктуру для автоматического создания журнала изменений и улучшения нашей документации API.
Однако бремя управления версиями все еще существует, и даже сложная структура не является панацеей. Мы делаем все возможное, чтобы использовать управление версиями API только в случае крайней необходимости. Мы также стараемся сделать наш API правильным с первой попытки, создав специальный «Совет по API». Все существенные изменения API проверяются там нашими лучшими разработчиками, тестировщиками и техническими писателями, прежде чем приступить к какой-либо реализации.
Особая благодарность Брандуру Личу за его статью о версиях API на Stripe и за помощь, которую он оказал мне, когда я реализовал Cadwyn: без его помощи это было бы невозможно.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3