The Go Blog
Go Modules: v2 и Beyond
Введение
Эта статья является частью серии.
- Часть 1 — Использование Go Modules
- Часть 2 — Переход на Go Modules
- Часть 3 — Публикация Go Modules
- Часть 4 — Go Modules: v2 и Beyond (эта статья)
- Часть 5 — Сохранение совместимости модулей
Примечание: Для документации по разработке модулей, см. Разработка и публикация модулей.
По мере того как успешный проект становится зрелым и добавляются новые требования, прошлые функции и решения могут перестать иметь смысл. Разработчики могут захотеть интегрировать полученный опыт, удалив устаревшие функции, переименовав типы или разделив сложные пакеты на управляемые части. Подобные изменения требуют усилий от пользователей, которые должны перенести свой код на новое API, поэтому они не должны вноситься без тщательного анализа, чтобы преимущества превосходили затраты.
Для экспериментальных проектов — находящихся на основном уровне v0 — пользователи ожидают периодические изменения, которые могут нарушать совместимость. Для проектов, объявленных стабильными — на основном уровне v1 или выше — изменения, нарушающие совместимость, должны быть реализованы в новой основной версии. Эта статья рассматривает семантику основных версий, как создать и опубликовать новую основную версию, а также как поддерживать несколько основных версий модуля.
Основные версии и пути модулей
Модули формализовали важный принцип в Go, который называется правило совместимости импортов:
<code>Если у старого и нового пакета один и тот же путь импорта, новый пакет должен быть обратно совместим с предыдущим. </code>
По определению, новая основная версия пакета не является обратно совместимой с предыдущей версией. Это означает, что новая основная версия модуля должна иметь другой путь модуля, чем предыдущая версия. Начиная с v2, основная версия должна быть указана в конце пути модуля (объявляется в инструкции module в файле go.mod). Например, когда авторы модуля github.com/googleapis/gax-go разрабатывали v2, они использовали новый путь модуля github.com/googleapis/gax-go/v2. Пользователи, которые хотели использовать v2, должны были изменить свои импорты пакетов и требования модулей на github.com/googleapis/gax-go/v2.
Необходимость в суффиксах основных версий — один из способов, которым Go Modules отличаются от большинства других систем управления зависимостями. Суффиксы необходимы для решения
проблемы диаманта зависимостей.
До появления Go Modules, gopkg.in позволял авторам пакетов следовать тому, что мы теперь называем правилом совместимости импортов. С gopkg.in, если вы зависите от пакета, импортирующего gopkg.in/yaml.v1, и другого пакета, импортирующего gopkg.in/yaml.v2, конфликта не возникает, потому что два пакета yaml имеют разные пути импорта — они используют суффикс версии, как и Go Modules.
Поскольку gopkg.in использует ту же методологию суффиксов версий, что и Go Modules, команда Go принимает .v2 в gopkg.in/yaml.v2 как допустимый суффикс основной версии. Это специальный случай для совместимости с gopkg.in: модули, размещённые на других доменах, должны использовать суффикс в виде косой черты, например /v2.
Стратегии основных версий
Рекомендуемая стратегия заключается в разработке v2+ модулей в каталоге, названном в соответствии с суффиксом основной версии.
<code>github.com/googleapis/gax-go @ master branch /go.mod → module github.com/googleapis/gax-go /v2/go.mod → module github.com/googleapis/gax-go/v2 </code>
Этот подход совместим с инструментами, которые не осведомлены о модулях: пути к файлам внутри репозитория соответствуют путям, ожидаемым go get в режиме GOPATH. Эта стратегия также позволяет разрабатывать все основные версии вместе в различных каталогах.
Другие стратегии могут хранить основные версии на отдельных ветках. Однако, если исходный код v2+ находится на основной ветке репозитория (обычно master), инструменты, которые не учитывают версии — включая команду go в режиме GOPATH — могут не различать основные версии.
Примеры в этой статье будут следовать стратегии подкаталога основной версии, поскольку она обеспечивает наибольшую совместимость. Рекомендуется, чтобы авторы модулей следовали этой стратегии, пока у них есть пользователи, разрабатывающие в режиме GOPATH.
Публикация v2 и последующих версий
В этой статье используется github.com/googleapis/gax-go в качестве примера:
<code>$ pwd /tmp/gax-go $ ls CODE_OF_CONDUCT.md call_option.go internal CONTRIBUTING.md gax.go invoke.go LICENSE go.mod tools.go README.md go.sum RELEASING.md header.go $ cat go.mod module github.com/googleapis/gax-go go 1.9 require ( github.com/golang/protobuf v1.3.1 golang.org/x/exp v0.0.0-20190221220918-438050ddec5e golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b google.golang.org/grpc v1.19.0 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 ) $ </code>
Чтобы начать разработку v2 github.com/googleapis/gax-go, мы создадим новый каталог v2/ и скопируем туда наш пакет.
<code>$ mkdir v2 $ cp -v *.go v2 'call_option.go' -> 'v2/call_option.go' 'gax.go' -> 'v2/gax.go' 'header.go' -> 'v2/header.go' 'invoke.go' -> 'v2/invoke.go' $ </code>
Теперь создадим файл go.mod для версии v2, скопировав текущий файл go.mod и добавив суффикс /v2 к пути модуля:
<code>$ cp go.mod v2/go.mod $ go mod edit -module github.com/googleapis/gax-go/v2 v2/go.mod $ </code>
Обратите внимание, что версия v2 рассматривается как отдельный модуль по сравнению с версиями v0 / v1: обе могут сосуществовать в одной сборке. Поэтому, если ваш v2+ модуль имеет несколько пакетов, вы должны обновить их для использования нового пути импорта /v2: в противном случае ваш v2+ модуль будет зависеть от вашего v0 / v1 модуля. Например, чтобы обновить все ссылки на github.com/my/project до github.com/my/project/v2, можно использовать find и sed:
<code>$ find . -type f \
-name '*.go' \
-exec sed -i -e 's,github.com/my/project,github.com/my/project/v2,g' {} \;
$
</code>
Теперь у нас есть модуль v2, но мы хотим провести эксперименты и внести изменения до публикации релиза. Пока мы не выпустим v2.0.0 (или любую версию без суффикса пре-релиза), мы можем разрабатывать и вносить ломающие изменения по мере принятия решений о новом API. Если мы хотим, чтобы пользователи могли экспериментировать с новым API до того, как мы официально сделаем его стабильным, мы можем опубликовать пре-релизную версию v2:
<code>$ git tag v2.0.0-alpha.1 $ git push origin v2.0.0-alpha.1 $ </code>
Как только мы будем довольны нашим v2 API и будем уверены, что не нуждаемся в других ломающих изменениях, мы можем пометить v2.0.0:
<code>$ git tag v2.0.0 $ git push origin v2.0.0 $ </code>
В этот момент теперь нужно поддерживать две основные версии. Обратно совместимые изменения и исправления ошибок приведут к новым минорным и патч-релизам (например, v1.1.0, v2.0.1 и т.д.).
Заключение
Изменения основных версий приводят к дополнительной нагрузке на разработку и сопровождение и требуют инвестиций от пользователей для миграции. Чем больше проект, тем выше эта нагрузка. Изменение основной версии должно происходить только после выявления убедительной причины. После того как убедительная причина для ломающего изменения была выявлена, мы рекомендуем разрабатывать несколько основных версий в ветке master, поскольку это совместимо с более широким спектром существующих инструментов.
Ломающие изменения в модуле v1+ должны всегда происходить в новом, vN+1 модуле. Когда выпускается новый модуль, это означает дополнительную работу для сопровождающих и для пользователей, которым нужно перейти на новый пакет. Сопровождающие поэтому должны проверять свои API перед выпуском стабильной версии и тщательно оценивать, действительно ли ломающие изменения необходимы сверх v1.
Следующая статья: Go Turns 10
Предыдущая статья: Working with Errors in Go 1.13
Blog Index