Версионирование библиотек-сателлитов

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

Хранение интеграций в отдельных репозиториях даёт несколько преимуществ:

  1. Отдельные маленькие кодовые базы, в которых гораздо проще разобраться новым контрибьюторам, чем в одной огромной репе;
  2. Основной проект не тащит за собой транзитивные зависимости, необходимые интеграциям;
  3. Независимые релизы.

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

В то же время боль от сиамского версионирования вполне ощутима и затрагивает всех: пользователей, контрибьюторов и мейнтейнеров. В ходе развития проекта обязательно возникнет ситуация, когда:

  1. выход новой версии будет задерживаться из-за бага в мастере / болезни или отпуска кор-контрибьютора / большооооого рефакторинга;
  2. одновременно с этим в сателлите правится баг или дописывается несколько нужных фич.

На этом моменте обычно начинается цирк с конями. Создаются такие 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 с нужной мне фичей, пинганул мейнтейнера, в тот же день обновил у себя минорную версию. Красота!