Версионирование библиотек-сателлитов
Успешные библиотеки общего назначения имеют свойство обрастать сателлитами ― небольшими либами для интеграции с другими инструментами. Например, у реактивных стримов это коннекторы к базам данных и AMQP, у библиотек для сериализации ― связки с веб-фреймворками.
Хранение интеграций в отдельных репозиториях даёт несколько преимуществ:
- Отдельные маленькие кодовые базы, в которых гораздо проще разобраться новым контрибьюторам, чем в одной огромной репе;
- Основной проект не тащит за собой транзитивные зависимости, необходимые интеграциям;
- Независимые релизы.
И вот о третьем пункте почему-то постоянно забывают, либо сознательно от него отказываются. В итоге версионирование маленькой библиотечки-коннектора намертво приковывается к версионированию основного проекта. Вроде бы этот подход позволяет пользователю сателлита всегда точно знать, какая версия основной библиотеки добавлена в его проект. Но это сомнительное преимущество, потому что версию основы можно просто прописать в релиз ноуты.
В то же время боль от сиамского версионирования вполне ощутима и затрагивает всех: пользователей, контрибьюторов и мейнтейнеров. В ходе развития проекта обязательно возникнет ситуация, когда:
- выход новой версии будет задерживаться из-за бага в мастере / болезни или отпуска кор-контрибьютора / большооооого рефакторинга;
- одновременно с этим в сателлите правится баг или дописывается несколько нужных фич.
На этом моменте обычно начинается цирк с конями. Создаются такие issue, мейнтейнеров дёргают в гиттере, все понимают, что версионирование пора разрывать. Но это уже не безболезненный процесс, потому что пользователи привыкли к совпадению версий и релиз обязательно вызовет массу вопросов. Был случай, когда мейнтейнер проекта вместо 2.0.0
предлагал выпустить 20.0.0
, чтобы явно обозначить различие между версиями основы и сателлита.
Что с этим делать? Можно просто версионировать сателлиты полностью независимо. Если всё же хочется иметь связь с основным проектом, то в x.y.z
держать общими x
и y
, а минорные версии инкрементировать в свободном режиме. Тогда для версии основы 1.5.4
может быть версия сателлита 1.5.17
. Из версии понятно, что мы получаем транзитивно, а релизы при этом не задерживаются.
Пример плохого версионирования: коннектор моникса к кафке. Пытались релизить monix и monix-kafka одновременно, но недавно отказались от этого. В феврале приняли мой PR, на первое мая сделали подарок с полноценным релизом фичи. До этого пришлось просить мейнтейнера выпустить хотя бы SNAPSHOT-релиз и держать у себя в build.sbt всратую строчку "io.monix" %% "monix-kafka-1x" % "1.0.0-RC2-SNAPSHOT-48d9da7-SNAPSHOT"
. Ну и текущая версия библиотеки пошла вразрез предыдущей логике версионирования ¯\_(ツ)_/¯
.
Пример хорошего версионирования: интеграция enumeratum с circe. Используют подход с общими x.y
. Нашёл забытый всеми PR с нужной мне фичей, пинганул мейнтейнера, в тот же день обновил у себя минорную версию. Красота!