Как стать автором
Обновить

Как мы столкнулись с версионированием и осознали, что вариант «просто проставить цифры» не работает

Время на прочтение7 мин
Количество просмотров12K
Всего голосов 12: ↑11 и ↓1+10
Комментарии32

Комментарии 32

легко живёте… Есть заказчик А, у которого поставили версию Х.
Прошло время… он хочет дополнительную функцию. Если поставить ему актуальную версию Y — задача решается легко. Но допуск изменённой версии в эксплуатацию означает проведение испытаний всего комплекса, для чего нужен месяц времени и энное количество аппаратного обеспечения. После испытаний — переобучение персонала на все происшедшие в комплексе изменения. Возможно, со сдачей экзаменов (хотя бы и формальных).

Запустить же дополнительную функцию на сильно устаревшей версии Х — делать уйму кода, не нужного в версии Y и не совместимого с ней.

И «мы были бы рады, если бы у нас было десять таких заказчиков, как вы. Но у нас их пятьдесят».

Итого: нумерация — хорошо, но ситуация с поддержкой нескольких версий разного возраста и разной совместимости легко решается только в небольшом количестве проектов, преимущественно «искаропки». С промышленными продуктами, близкими к материальному миру, жизнь куда сложнее.

Но живём же как-то. Отдельными ветками, выбором изменений, мержами, тестированием.

Семантическое версионирование решает пробелмы в подавляющем большинстве случаев.

Речь, естественно, о программно-аппаратных решениях с разными версиями железа/совместимости и прочим.

Чисто софтовые решения намногт более проще поддерживаются.

Семантическое версионирование решает пробелмы в подавляющем большинстве случаев.
Во вполне определённых случаях. Если же продукт предназначен для непосредственного взаимодействия с материальным миром — нет, не решает. Версионирование, безусловно, применяется, помогает организации работ и т.п. Без него было бы совсем плохо… но решением проблемы это всё же не является.

Я рассматриваю семантическое версионнирование как часть составного решения, которое помогает определить, насколько сильно изменилась программа в новом релизе. В целом на проблему нужно смотреть комплексно и "проставление чисел" тому или иному релизу - это часть необходимого минимума при создании ПО/API

часть — да. Обязательная — да. Но вовсе не решение, за исключением простых проектов.

Тут может помочь backport service, который будет стоять перед новой версией бекенда и сторонним ПО, обеспечивая обратную совместимость и предоставляя новый функционал. Но всё зависит от конкретного случая, возможно такая реализация может не подойти.

Я потому и пишу всё время о взаимодействии с реальным миром, что при этом в большинстве случаев не получается настолько строго зафиксировать интерфейсы. Простой пример — бэкенд, где всё можно красиво спроектировать и столь же стройно реализовать, и фронтенд, где тоже можно красиво спроектиролвать, но вот логики и стройности в реализации UI добиться крайне сложно.

И ещё одно: вот вы поменяли что-то в ядерной части. Всё прозрачно и бесшовно, нигде ничего вокруг менять не надо. Идеальная работа!
… а испытания повторить всё равно нужно. Таковы правила игры.

Да, всё зависит "от..", если вопрос касается фронт части, то тут мы можем сделать его максимально гибким и определять его структуру в бекенде. Подобное решение используется при разработке мобильных приложений, когда пользователь нечасто обновляет приложение на телефоне, но при этом структура приложения, например, меню/новые виджеты внутри нужно изменить.

Вы никак не можете выйти из простых случаев? Я всего лишь утверждаю, что «искаропки», а мобильные приложения как раз таковы, один из самых простых случаев, а бывают и другие.
Приложения, замыкающиеся в IT-сфере, очень просты в этом смысле. Решения для материального мира — скачком сложнее.

А вы не рассматривали альтернативы для RESTful API, которые изначально разрабатывались с учётом проблемы обратной совместимости? Например, GraphQL или gRPC.

Рассматривали, gRPC не защищает нас полностью, мы можем расширять модель, но только необязательными полями. А так тут те же проблемы в проектирование, что и у REST, если мы захотим изменить модель без обратной совместимости, например, добавить обязательное поле, то нам нужно обновить модели и у всех клиентов, то есть переработать множество интеграций с нашим сервисом

Мы столкнулись с несколькими Большими Серьёзными Проблемами, которые Требуют Серьёзной Работы, но срезали всюду где смогли и оно запустилось. рассказываем историю успеха.

Во-первых версия API и версия ПО - две малосвязные вещи. Хотя бы потому, что версия у ПО всегда одна (если их больше чем одна, у вас факап в релизном пайплайне) и используется для идентификации артефакта и связного с ним исходного кода. Задача - глядя на артефакт сказать какой версии кода он соответствует. Ещё версия может сигнализировать о масштабе изменений, но эта задача - второстепенная. Первичная - это идентификация артефакта и сырцов.

Версий API может быть несколько, потому что одно и то же приложение может поддерживать несколько мажорных версий API на разных минорных версиях. Искусство управления версиями в API - отдельная религия; лучшее, что я видел - микроверсии (openstack).

Дальше идёт вопрос о поддержке моделей данных и совместимости между несколькими версиями модели, что совершенно третья религия, с совершенно другими соображениями.

А вы всё в одну кучу. latest, forward compatibility, легси-поля...

версия у ПО всегда одна

Это если ПО развернуто в единственном экземпляре. А у каждого заказчика собственная копия, то все становится не так радужно

Вы не на то фокусируетесь. не "ПО может работать в единственной версии", а у данного экзепляра, запущенного на данном сервере должна быть единственная версия. Одна. Известно, из какого коммита собранная. Если у вас ПO по данным strings имеет версию 1.2.3, по мнению --version - 3.2.1, по мнению пакетного менеджера 1.2.4, по мнению опытного взгляда саппорта - вообще собрана из фичебранча мимо мастера, то ой.

А как же проводить канареечный деплой, если мы не можем развернуть одновременно несколько версий?

Вы канареечный деплой производите из разных бинарей? Вы запускаете (условно) три разных бинарных артефакта, ассоциированных с разными версиями исходного кода. У каждого бинарного артефакта своя версия. Каждый процесс запущен со своей версией и понятно, в какой коммит смотреть, когда какая-то из версий обсыпется.

Если же вы делаете канареечный деплой из одного и того же бинарного артефакта, внутри которого мешанина из кода (например, потому что в jar'ник собирают код из разных веток гита и переключение между ними производится внутри по рандому), вот тогда, поздравляю, у вас канареечный деплой из "разных версий". Но когда он упадёт (целиком), то куда смотреть?

Мешанина из кода это совсем плохо, тут либо неумение использовать версионирование, либо что-то не так с архитектурой.

Я говорил про канареечный деплой с разными версиями приложения, ассоциируемых с разными версиями исходного кода. Возможно, вы меня неправильно поняли.

Я-то вас понял. Но мой исходный тезис о том, что бинарный артефакт должен иметь 1-to-1 соответствие с версией кода канареечные деплои (и любые другие виды ci/cd гимнастики) никак не нарушают.

Очень смелое заявление про единственность версии. Прямо из страны лошадок с рожками.

Особенно в ситуации, когда функционал софта не изменяется, сорцы тоже, а версии разные. Тут прямо можно сказать, что в пайплайн релизный кто-то покакал радугой, но жизнь она такая, идентифицировать снепшот исходников — совершенно не проблема по версии. N-to-N в БД придумали давным-давно.

Это не смелое требование, это единственная здравомыслящая модель. Версия может обрастать уточнениями (версия 1.2.3 в гите, пятый maintainer build для ubuntu, собранный с третьей попытки в pipeline такой-то), но на выходе у вас у бинарного артефакта одна версия, которая соответствует одной ревизии кода.

Если у вас один артефакт соответствует разным ревизиям кода - у вас проблемы (кто-то подвигал тег в гите).
Если у вас на артефакте две несовместимые версии (например, вторая не "расширение" первой уточняющими деталями, а, "ну так получилось" при сборке), то какая из них правильная?

Если возникает желание повесить несколько версий на артефакт (потому что он собирается из нескольких гитов), то это либо уродливая версионная многоножка (core-1.3.3-libs-3.4.5-payments-5.7.8), либо у вас бинарный артефакт (со своей версией), который состоит из компонент:

core 1.3.3
libs 3.4.5
payments-5.7.8

и имеет свою версию 1.0.2-pre33.

Если же у вас где-то проскакивает проблема, что, например, вы не знаете, из какого бранча собирали версию 1.0.1 (из мастера или из бранча stable? И из какого коммита?), то у вас фундаментальная проблема передачи проблемы назад.

Версия 1.0.1 при старте написала Segmentation Fault и завершилась. Куда смотреть программисту в поисках бага? Если вы эту связь ломаете, всё, у вас не версия, а продажная девка сиая.

Особенно в ситуации, когда функционал софта не изменяется, сорцы тоже, а версии разные.

Хорошо, если так. Есть (или была) такая контора -- Первый специализированный депозитарий. И вот как-то выпустили они тестовую версию (пусть будет 1.4.2) DTD для XML'ек, которыми мы с ними обменивались. Ну, хорошо, договорился о тесте, поставил, работает. Потом появляется новость -- "у нас новая версия DTD, 1.4.2, скачивайте и радуйтесь, скоро перейдём не неё!". Я сижу на стуле ровно, мне делать ничего не нужно. Через недельку с их стороны XML'ки перестают проходить валидацию -- говорят, не соответствуют DTD. Звоню, а они говорят -- "ставьте 1.4.2". Не нужно, говорю, мне ничего ставить, у меня 1.4.2 ещё со времён тестов стоит. "Ааа!", говорят, "это другая 1.4.2, тестовая, а мы теперь настоящую 1.4.2 выпустили!". На самом деле разговор занял около часа, в течение которого они подразумевали, что один и тот же номер версии может быть у разных версий кода, а я даже представить себе такого не мог. Где-то на 55-й минуте разговора до Остроухой Совы начало постепенно доходить...

Хорошая страшилка для новых разработчиков:)

Да, тут глобально три разных направления, связанные с версионированием и совместимостью в приложениях, API и изменениях структуры БД. Цель была показать необходимость в поддержке прямой и обратной совместимости при изменениях и не важно где они происходят: в приложении, структуре БД или API.

Как же забавно, ностальгически и одновременно грустно читать было эту статью. Казалось бы, 2022 год на дворе - такие то технологии, модные словечки аля Docker, Load Balancer и все такое... А проблемы все те же самые, что мы с коллегами решали на заре моей карьеры лет 13 назад.

Только тогда бэкграунд проблемы был немного другой. Дано: Множество предприятий по нашей необъятной стране, программные модули весом почти "АЖ В ГИГАБАЙТ", на удаленных концах дай бог диалап кое-как работает 2 часа в день. То есть возможности регулярно обновлять модули на предприятиях не было, и в результате каждое могло работать со своей версией.

А самый прикол был в том, что эти самые предприятия должны были синхронизироваться - получать актуальные данные друг от друга, делалось это через единый центральный сервер. Каждый установленный модуль в удобное для сетевой нагрузки время отправлял свои новые данные и забирал другие с сервера.

И там были точно такие же проблемы - одно предприятие использовало прошлогоднюю версию, где какие-то данные лежали в одном поле одной строчки таблицы, а на другом предприятии стояла самая свежая версия, где эти данные были вынесены в отдельную таблицу построчно.

Для решения таких проблем нам приходилось писать специальные преобразователи. Они получали данные от старых версий и преобразовывали их в формат последней версии. И наоборот - для отправки на старые версии преобразовывали данные из нового формата в старый, определенной версии того предприятия, которое запрашивало обновления.

Управление версиями и контролируемое обновление ПО - это сложная задача и никогда у неё не будет простого решения, просто в силу устройства домена проблемы.

Решения становятся всё более изощрёными, философия версий развивается, открывая новые возможности. Но проблема версий - это философская проблема, и решить её можно не более чем решить проблему Ахилеса и черепахи. Можно придумать дифференциальный анализ, квантовую физику, но проблема всё равно остаётся, и из неё можно черпать новые и новые решения.

А расскажите, пожалуйста, про semver в контексте Windows, где используется 4 цифры вместо 3? Да ещё с их поганым RC-файлом, в котором в разных строках версия по разному вбивается) Да ещё если хочется прихранить информацию о том, это dev/alpha/beta/rc/release и с какого git hash оно вообще собиралось? А по хорошему ещё и чтоб сборка была воспроизводимой и подписанной хоть чем-то

Сколько боли в одном комментари😬

У нас 4-я цифра версии — это номер билда. На семантику не влияет, но нужен, например, для повторных патчей (если, скажем, почему-то понадобилось пересобрать из того же коммита) и, заодно, для поиска конкретного коммита (билд-сервер ставит сооответствующий тэг в гите при успешной сборке).

С воспроизводимостью сборки нелегко, но в чём проблемы с подписью (кроме покупки сертификата)? `signtool.exe` из Windows Kit — и вперёд.

Вот я тоже номер билда решил использовать для последней цифры в CI, но... он рано или поздно переполнится, а сбросить его не совсем понятно как на тех же github/gitlab.

Как вы считаете патчи? Было три разраба, пилили фичу, один из них сделал 5 коммитов в рамках 4 тасок, второй три коммита в рамках одной таски, третий 7 мелких багов зафиксил в одном коммите. И параллельно бранчей с фичами штуки 3-4. И как вот это вот всё автоматизировать?

> а сбросить его не совсем понятно как на тех же github/gitlab.

Тут не подскажу, у нас TeamCity, там у `Build counter` есть кнопка `Reset`. Если бы не было, я бы, наверное, через тэги делал — найти последний тэг в ветке, удовлетворяющий маске (скажем, через git tag -l build/* | tail n1), после чего построить номер следующей ревизии, после успешной сборки добавить новый тэг. Для сброса достаточно руками создать тэг со стартовой ревизией.

> Как вы считаете патчи?

У нас коробочные продукты с MS Install, и наши патчи — в основном хотфиксы или супер-срочные мелкие фичи за которые клиент платит и не готов ждать следующего релиза или вынужден сидеть на старом. Каждый патч обычно ограничивается точечной заменой пары-другой файлов, которые делает один, редко два девелопера. Такого, чтобы собирать кучу коммитов от разных девелоперов с разных бранчей, у нас нет — обычные фичи откладываются до следующего релиза, в патчи не идут.

Про подсчёт — первый патч после релиза продукта стартует «генеральную последовательность» патчей (`product-1.2.3-p001.msp`, `product-1.2.3-p002.msp`, ...). Патчи кумулятивные (p003 ⊃ p002 ⊃ p001), ветвления в последовательности избегаются, хотя иногда нужны специальные патчи для некоторых клиентов, там добавляется суффикс кастомера (`product-1.2.3-p001-cust-name.msp`) и начинается новая «ветка» для этого клиента. Номер билда используется только в поле FileVersion файла, чтобы MS Install знал, что файл обновился.

Автоматизируется это дело не очень, приходится руками делать, увы.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий