суббота, 25 января 2020 г.

Go модули: v2 и далее

По мере развития успешного проекта и добавления новых требований прошлые функции и проектные решения могут перестать иметь смысл. Разработчики могут захотеть объединить полученные уроки, удалив устаревшие функции, переименовав типы или разбив сложные пакеты на управляемые части. Такие изменения требуют усилий со стороны нижестоящих пользователей для переноса своего кода в новый API, поэтому их не следует вносить без тщательного учета того, что преимущества перевешивают затраты.

Для проектов, которые все еще являются экспериментальными - в основной версии v0 - пользователи ожидают случайных критических изменений. Для проектов, которые объявлены стабильными - в основной версии v1 или выше - в новой основной версии должны быть внесены критические изменения. В этом посте рассматривается семантика основных версий, как создавать и публиковать новые основные версии и как поддерживать несколько основных версий модуля.

Основные версии и пути модулей

Модули формализовали важный принцип в Go, правило совместимости импорта:

Если старый пакет и новый пакет имеют 
одинаковый путь импорта,
новый пакет должен быть обратно совместим 
со старым пакетом.

По определению, новая основная версия пакета не имеет обратной совместимости с предыдущей версией. Это означает, что новая основная версия модуля должна иметь путь к модулю, отличному от предыдущей версии. Начиная с версии v2 основная версия должна отображаться в конце пути к модулю (объявлена в выражении модуля в файле go.mod). Например, когда авторы модуля github.com/googleapis/gax-go разработали v2, они использовали новый путь к модулю github.com/googleapis/gax-go/v2. Пользователи, которые хотели использовать v2, должны были изменить свои импорт пакетов и требования к модулю на github.com/googleapis/gax-go/v2.

Потребность в суффиксах основной версии - это одно из отличий модулей Go от большинства других систем управления зависимостями. Суффиксы необходимы для решения проблемы diamond зависимости. Перед модулями Go gopkg.in позволял сопровождающим пакетов следовать тому, что мы теперь называем правилом совместимости импорта. С gopkg.in, если вы зависите от пакета, который импортирует gopkg.in/yaml.v1, и другого пакета, который импортирует gopkg.in/yaml.v2, конфликт не возникает, потому что два пакета yaml имеют разные пути импорта - они используют суффикс версии, как с модулями Go. Поскольку gopkg.in использует ту же методологию суффикса версии, что и модули Go, команда Go принимает .v2 в gopkg.in/yaml.v2 в качестве действительного суффикса основной версии. Это особый случай совместимости с gopkg.in: для модулей, размещенных в других доменах, требуется суффикс косой черты, например /v2.

Основные версии стратегий

Рекомендуемая стратегия заключается в разработке модулей v2+ в каталоге с именем суффикса основной версии.

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

Этот подход совместим с инструментами, которые не знают о модулях: пути к файлам в репозитории совпадают с путями, ожидаемыми go get в режиме GOPATH. Эта стратегия также позволяет разрабатывать все основные версии вместе в разных каталогах.

Другие стратегии могут хранить основные версии в отдельных ветках. Однако, если исходный код v2+ находится в ветке хранилища по умолчанию (обычно master), инструменты, не поддерживающие версию - включая команду go в режиме GOPATH - могут не различать основные версии.

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

Публикация v2 и далее

Этот пост использует github.com/googleapis/gax-go в качестве примера:

$ 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
)
$

Чтобы начать разработку v2 на github.com/googleapis/gax-go, мы создадим новый каталог v2/ и скопируем в него наш пакет.

$ mkdir v2
$ cp *.go v2/
building file list ... done
call_option.go
gax.go
header.go
invoke.go
tools.go

sent 10588 bytes  received 130 bytes  21436.00 bytes/sec
total size is 10208  speedup is 0.95
$

Теперь создадим файл go.mod v2, скопировав текущий файл go.mod и добавив суффикс v2/ к пути модуля:

$ cp go.mod v2/go.mod
$ go mod edit -module github.com/googleapis/gax-go/v2 v2/go.mod
$

Обратите внимание, что версия v2 рассматривается как отдельный модуль от версий v0 / v1: оба могут сосуществовать в одной сборке. Итак, если ваш модуль v2+ имеет несколько пакетов, вы должны обновить их, чтобы использовать новый путь импорта /v2: в противном случае ваш модуль v2+ будет зависеть от вашего модуля v0 / v1. Например, чтобы обновить все ссылки на github.com/my/project на github.com/my/project/v2, вы можете использовать find и sed:

$ find . -type f \
    -name '*.go' \
    -exec sed -i -e 's,github.com/my/project,github.com/my/project/v2,g' {} \;
$

Теперь у нас есть модуль v2, но мы хотим поэкспериментировать и внести изменения перед публикацией релиза. До тех пор, пока мы не выпустим v2.0.0 (или любую версию без суффикса перед релизом), мы можем разрабатывать и вносить критические изменения в зависимости от нового API. Если мы хотим, чтобы пользователи могли экспериментировать с новым API, прежде чем мы официально сделаем его стабильным, мы можем опубликовать предварительную (pre-release) версию v2:

$ git tag v2.0.0-alpha.1
$ git push origin v2.0.0-alpha.1
$

Если мы довольны нашим v2 API и уверены, что нам не нужны какие-либо другие критические изменения, мы можем пометить v2.0.0:

$ git tag v2.0.0
$ git push origin v2.0.0
$

На данный момент есть две основные версии для поддержки. Обратно совместимые изменения и исправления ошибок приведут к появлению новых минорных и исправлений (например, v1.1.0, v2.0.1 и т. д.).

Вывод

Значительные изменения версии приводят к накладным расходам на разработку и обслуживание и требуют инвестиций от последующих пользователей для перехода. Чем больше проект, тем больше эти накладные расходы. Основное изменение версии должно произойти только после определения веской причины. Как только убедительная причина была найдена для серьезного изменения, рекомендуется разработать несколько основных версий в основной ветке, потому что она совместима с более широким спектром существующих инструментов.

Прерывание изменений в модуле v1+ всегда должно происходить в новом модуле vN+1. Когда выпущен новый модуль, это означает дополнительную работу для сопровождающих и пользователей, которым необходимо перейти на новый пакет. Поэтому сопровождающие должны проверить свои API-интерфейсы перед выпуском стабильного релиза и тщательно обдумать, действительно ли необходимы критические изменения после v1.


Читайте также:


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

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