Страницы

Поиск по вопросам

четверг, 5 декабря 2019 г.

Версионирование микросервиса

#архитектура #best_practice #версионирование


Имеется микросервис (.Net core, но это не так важно). В нём реализованы новые фичи,
где-то поменялись DTO и эту реализацию требуется сделать новой версией (v2), с сохранением
старого функционала (v1).  

Были предложены следующие варианты:  


Сделать ветку v1 от текущего master, зафиксировать её и выложить отдельным инстансом.
При корректировке логики, затрагивающей обе ветки, делать cherry-pick соответствующих
коммитов.
Сдублировать логику v1 и v2 в одном решении, разнеся её по разным пространствам имён,
реализовать два контроллера - для v1 и v2.


Первый подход подразумевает инфраструктурные затраты. Второй уродлив донельзя в виде
кода.
Вопрос: что считается best practice? Возможно, есть какой-то третий (четвёртый, пятый)
подход, который мы не рассмотрели?
    


Ответы

Ответ 1



Предисловие Как вы сами понимаете, "единого" и/или "идеального" способа решения данной проблемы не существует, существуют лишь наиболее и наименее удобные в разрезе каждого конкретного случая подходы. Могу предложить тот, к которому методом проб и ошибок пришла команда разработки, в разрезе которой нахожусь я. Общие предложения (client-side и server-side) Предлагаю воспользоваться близким к первому из выдвинутых вами вариантов, т.е. разбить v1 и v2 версии вашего микросервиса на 2 независимых модуля (2 клиентских библиотеки на клиентской стороне и 2 единицы диплоймента на серверной) по совокупности качеств: возможность параллельного развития независимость разработки наличие возможности отказа от обратной совместимости между версиями сервиса При дальнейшем появлении последующих версий сервиса (раз у вас появилась вторая версия сервиса, то, скорее всего, позднее появится и третья) придерживайтесь аналогичной стратегии. Внесение feature, bug fix и иных доработок микросервиса: При версионированной разработке микросервиса следует выделять, по крайней мере, major и minor-версии (формата ${major-version}.${minor-version}): изменение minor-версии происходит в пределах той же версии сервиса изменение major-версии требует выделения новой версии сервиса Доработки следует выполнять, опираясь на следующие правила: доработка, не ломающая обратной совместимости, вносится с поднятием minor-версии в разрезе той же major-версии сервиса доработка, отламывающая обратную совместимость, потребует поднятия major-версии, то есть создания новой версии сервиса Client-side (предложения по клиентской части) Предоставление клиентских библиотек для работы с вашими микросервисами: Зачастую разработчики сервисов также предоставляют клиентскую библиотеку для работы с их микросервисами, которая предоставляет удобное API и инкапсулирует в себе детали интеграционного взаимодействия: валидирует на консистентность значения полей получаемого объекта основе отвалидированного объекта формирует и отправляет http-запрос опосредованной через load balancer на серверную часть вашего микросервиса, т.е. в web-приложение, обрабатывающее предназначенные для соответствующей версии сервиса запросы получает ответ на отправленный http-запрос и накладывает его на удобные для пользователя классы из API, предоставляя их в качестве возвращаемого значения В случае, если вы предоставляете клиентскую библиотеку для работы с вашим микросервисом, то в целевом решении её удобнее всего будет также дробить по версиям сервиса, но с некоторыми замечаниями: major-версия клиентской библиотеки должна соответствовать major-версии связанного с ней микросервиса (под major-версией подразумевается то, что вы в контексте вопроса просто называете версией) каждая major-версия библиотеки должна предоставлять свое API в отдельном "пространстве" namespace в C# package в Java ... Пояснение: Предоставляя API каждой версии в обособленном "пространстве" вы предоставляете пользователям возможность одновременно использовать несколько версий вашего API в одном модуле кода, что, может оказаться, особенно востребовано в переходный период между версиями. Например, когда для потребителя вашего сервиса целиковый перевод его модуля, уже использующего предыдущую версию, на новую версию сервиса за раз может оказаться трудоемкой, либо в особых случаях, вообще, не требующей полного перехода задачей. Server-side (предложения по серверной части) На серверной части выделяются отдельные единицы диплоймента (независимые артефакты) для каждой версии сервиса по совокупности качеств: возможность параллельного развития независимость разработки и поставки на сервера приложений (а, следовательно, и стенды) возможность одновременного использования n'го количества версий сервиса в разрезе одного сервера приложений наличие возможности поштучного/точечного введения в эксплуатацию и выведения из эксплуатации микросервисов (web-приложений, содержащих Endpoint'ы/Conteoller'ы для обработки запросов только в разрезе соответствующей версии сервиса) наличие возможности отказа от обратной совместимости между версиями микросервиса При дальнейшем появлении последующих версий сервиса (раз у вас появилась вторая версия сервиса, то, скорее всего, позднее появится и третья) придерживайтесь аналогичной стратегии. Пояснение: Для каждой версии микросервиса вы выделяете отдельный модуль/артефакт, который будет поставляться как отдельная единица диплоймента web-приложения. Каждая из данных единиц диплоймента будет подниматься в уникальном в разрезе версий микросервисов контексте: some/web/app/context/v1 some/web/app/context/v2 ... some/web/app/context/vn Данное решение может показаться не слишком изящным в виду факта версионирования на уровне контекста web-приложения (далеко не всем придется по нраву идея явного указания версии в url'е), но стоит привести несколько доводов в пользу данного решения, чтобы оно не казалось столь отталкивающим: в разрезе одного сервера приложений (WebSphere/WildFly/Tomcat/Jetty и т.д.) появляются возможности: одновременно комбинировать необходимые версии микросервисов независимо вводить в эксплуатацию и выводить из эксплуатации различные версии микросервисов появляется возможность L7 load balancing'а (L7 сетевой модели OSI/ISO) в разрезе каждой версии сервиса на соответствующие экземпляры web-приложений, на которых крутятся данные микросервисы, например, посредством Nginx: заводите отдельный upstream под каждую версию сервиса заводите отдельный location под каждую версию сервиса каждый из location'ов будет проксировать запрос на соответствующий его версии сервиса upstream upstream производит load balancing на одно из соответствующих версии сервиса web-приложение (которое обрабатывает запросы только определенной версии сервиса) даже если несколько web-приложений соответствующих различным версиям микросервисов развернуты в разрезе одного сервера приложений. Load balancing производится, например, одним из следующих алгоритмов: Round Robin IP-Hash Least connected (в этом же случае появятся издержки на стороне nginx на поддержание актуального списка активных connection'ов, т.е. http-соединений) ... Использование базы данных: В случае, когда необходимо использование персистентного хранилища данных для корректной работы вашего микросервиса, то для версионирования схемы данных можно воспользоваться одним из следующих способов: изоляция данных одной версии микросервиса от данных другой evolutionary database design Про второй способ версионирования схемы БД вы можете прочитать по ссылке, поэтому расскажу про первый, который является наиболее логичным решением в разрезе контекста данного ответа. В качестве способа изоляции данных можно посмотреть в сторону двух наиболее распространенных: различные БД + высокий уровень изоляции данных В данном случае, уровень изоляции будет зависеть от серверов, где хостятся БД для данных различных версий сервисов, если они различны, то даже одного сервера не возымеет эффекта на сервисы, данные которых он не хранит. - дополнительные, весомые затраты серверных ресурсов (CPU, RAM и ROM), уходящих на содержание сразу нескольких инстансов БД (возможно, даже различных БД) механизм изоляции, предоставляемый средствами используемой БД - отсутствие изолированности с точки зрения отказоустойчивости (падение одной БД скажется на работе всех версий сервисов) В общем случае, уровень изолированности будет зависеть от устойчивости к разделению в пределах БД (partition tolerance из CAP theorem) + практически полное отсутствие дополнительных затрат серверных ресурсов Например, в случае использования СУБД Oracle для хранения данных различных версий сервисов можно реализовать одним из следующих способов: использовать различные схемы данных таблицы, сиквенсы и иные сущности БД всех версий сервисов оставлять с одинаковым наименованием ради минимизации объема изменений в коде микросервиса между версиями над DataSource для работы с БД создать обертку, которая будет переключать контекст запроса на необходимую схему данных (ALTER SESSION SET CURRENT_SCHEMA) во избежание явного указания схемы данных в запросах к БД использовать механизм партиционирования (шардирования) для данных каждой версии сервиса использовать отдельно выделенную партицию, а в запросах к партиционированной таким образом таблице явно указывать значение столбца партиционирования (например, выделить столбец VERSION), тем самым неявно указывая СУБД Oracle на необходимость поиска лишь в определенном файле партиций, заставляя её игнорируя остальные, так как в них гарантированно отсутствуют данные для запрашиваемой версии сервиса В большинстве случаев, в соотношении стоимость-востребованность, второй вариант изолированности данных оказывается наиболее подходящим. Стоит заметить, что в случае, если у вас какая-то особая бизнес-логика, которая предполагает, что посредством более высокой версии сервиса вы должны иметь доступ к данным более низкой версии, то никто не запрещает написать периодически запускаемый (в частности, одноразовый) конвертер данных из хранилища, содержащего данные более низкой версии, в хранилище, содержащее данные с более высокой версией. Например, в случае СУБД Oracle конвертер можно оформить в виде PL/SQL хранимой процедуры P. S. Весь мой пост преподнесен несколько в разрезе терминологии java enterprise, но, надеюсь, что вышеописанное удастся сколько-нибудь адекватно наложить на архитектуру вашего .Net-приложения.

Ответ 2



Подобные ситуации затрагивают еще ряд вопросов: - будет ли развитие двух версий параллельно и если да, насколько долго? - насколько сильно отличается версия v2 от v1? Если развитие первой версии на уровне багфиксов, то первый вариант. Если параллельное развитие будет еще долго и версии отличаются сильно, то тоже первый вариант. Если параллельное развитие будет еще долго и версии отличаются не сильно, то можно новые методы добавить в старый контроллер с перфиксом в имени, а на внешней стороне решить на уровне роутинга запросов и сделать красивые пути вида /v1/ под капотом controller/action и /v2/ под капотом controller/v2Action.

Комментариев нет:

Отправить комментарий