Всем привет. В преддверии старта курса «Разработчик Golang» подготовили для вас еще один интересный перевод.

Модули — это способ борьбы с зависимостями в Go. Изначально представленные в качестве эксперимента, модули предполагают вывести на поле в качестве нового стандарта для управления пакетами с версии 1.13.
Я нахожу эту тему достаточно необычной для новичков, пришедших с других языков, и поэтому я решил собрать здесь некоторые соображения и советы, чтобы помочь другим, таким же как я, получить представление об управлении пакетами в Go. Мы начнем с общего знакомства, а затем перейдем к менее очевидным аспектам, включая использование папки vendor, использование модулей с Docker в разработке, зависимости инструментов и т. д.
Если вы уже знакомы с модулями Go и знаете Wiki, как свои пять пальцев, эта статья, вероятно, не будет для вас очень полезной. Но для остальных, однако, она может сэкономить несколько часов проб и ошибок.
Так что если вам по пути, запрыгивайте и наслаждайтесь поездкой.

Если в ваш проект уже интегрировано управление версиями, вы можете просто запустить
Или указать путь к модулю вручную. Это что-то вроде имени, URL и пути импорта для вашего пакета:
Эта команда создаст файл
Запустите
Обратите внимание, что хотя вы не можете указать диапазон версий с помощью go get, то что вы здесь определяете, это не конкретная, а минимальная версия. Как мы увидим позже, есть способ изящно актуализировать зависимости в соответствии с semver.
Теперь наш файл
Суффикс
Поскольку мы еще нигде в нашем проекте не импортировали этот пакет, он был помечен как
В зависимости от текущего состояния вашего репозитория, она либо удалит неиспользуемый модуль, либо удалит комментарий
Если какая-либо зависимость сама по себе не имеет
В глобальном плане цель
Следите также за тем, чтобы после добавления зависимости был создан файл
Отчасти потому, что

Команды
По умолчанию все наши пакеты из всех проектов загружаются в каталог
Вы можете использовать
Но вы не можете обновиться так до мажорных версий. Код, включаемый в модули Go, должен технически соответствовать следующим правилам:
По-видимому, это сделано для того, чтобы разные версии пакетов могли быть импортированы в одной сборке (см. diamond dependency problem).
В двух словах, Go ожидает, что вы будете очень осмотрительны при внесении мажорных версий.
Вы можете указать необходимый модуль для своего собственного форка или даже локального пути к файлу, используя директиву
Результат:
Вы можете удалить строку вручную или запустить:
Исторически весь код Go хранился в одном гигантском монорепозитории, потому что именно так Google организовывает свою кодовую базу, и это сказывается на дизайне языка.
Модули Go — это своего рода отступление от этого подхода. Вам больше не нужно хранить все свои проекты в
Тем не менее, технически все ваши загруженные зависимости все еще помещаются в

Обычно это не проблема для других языков, но это то, с чем я впервые столкнулся при работе с кодовой базой Go.
К счастью, есть несколько (недокументированных) способов решения этой проблемы.
На первый взгляд это может показаться нелогичным, но если вы запускаете Go из контейнера, вы можете переопределить его GOPATH, чтобы он указывал на каталог проекта для того, чтобы пакеты были доступны из хоста:
Популярные IDE должны иметь возможность установить GOPATH на уровне проекта (рабочей области):

Единственный недостаток этого подхода — отсутствие взаимодействия со средой выполнения Go на хост-компьютере. Вы должны выполнять все команды Go внутри контейнера.
Еще один способ — скопировать зависимости вашего проекта в папку
Следует сразу отметить: мы НЕ разрешаем Go прямую загрузку материалов в папку vendor: с модулями это невозможно. Мы просто копируем уже загруженные пакеты.
К тому же, если вы отвендорите свои зависимости, как в примере выше, затем очистите
Более того, в этой недавно созданной vendor-папке отсутствует много вещей:

Типичный файл Docker Compose выглядит следующим образом (обратите внимание на привязки томов):
Обратите внимание, что я НЕ комичу эту vendor -папку в систему контроля версий или не собираюсь использовать ее в продакшене. Это строго локальный сценарий разработки, который обычно можно найти в некоторых других языках.
Однако, когда я читаю комментарии от некоторых мейнтейнеров Go и некотроые предложения, связанные с частичным вендорингом (ЧЕ?), у меня складывается впечатление, что изначально эта фича предназначалась не для этого юзкейса.
Один из комментаторов на reddit помог мне пролить свет на это:
Обычно люди вендорят свои зависимости по таким причинам, как желание иметь герметичные сборки без доступа к сети, а также наличия копии готовых зависимостей в случае отказа github или исчезновения репозитория, и возможность более легкого аудита изменений в зависимостях с использованием стандартных инструментов VCS и т. д.
Да, не похоже на что-либо из того, что может меня заинтересовать.
Согласно команде Go, вы можете запросто подключить вендоринг, установив переменную среды

На самом деле, единственное место где вам нужно подключить вендоринг — это ваше IDE:

После нескольких проб и ошибок я пришел к следующей процедуре для добавления вендорных зависимостей в этом подходе.
Вы можете потребовать зависимость с помощью
Затем импортируйте его куда-нибудь в своем коде:
Наконец, отвендорите ваши зависимости заново:
Существует ожидающее рассмотрения предложение разрешить go mod vendor принимать определенные шаблоны модулей, которые могут решить (а могут и не решить) некоторые из проблем связанные с этим рабочим процессом.
Этот подход лучше взаимодействует с хост-системой, но он довольно запутан, когда дело доходит до редактирования ваших зависимостей.
Лично я думаю, что переопределение GOPATH является более чистым подходом, поскольку он не жертвует функциональность
Нам может понадобиться установить некоторые инструменты на основе Go, которые не импортируются, а используются как часть среды разработки проекта. Простым примером такого инструмента является CompileDaemon, который может наблюдать за вашим кодом на предмет изменений и перезапускать ваше приложение.
Официально рекомендуемый подход заключается в добавлении
Ну вот и все. Я надеюсь, что вы не будете так же озадачены, как я, когда я впервые начал использовать модули Go. Вы можете посетить Go Modules wiki для получения более подробной информации.
Успеть на курс.

Модули — это способ борьбы с зависимостями в Go. Изначально представленные в качестве эксперимента, модули предполагают вывести на поле в качестве нового стандарта для управления пакетами с версии 1.13.
Я нахожу эту тему достаточно необычной для новичков, пришедших с других языков, и поэтому я решил собрать здесь некоторые соображения и советы, чтобы помочь другим, таким же как я, получить представление об управлении пакетами в Go. Мы начнем с общего знакомства, а затем перейдем к менее очевидным аспектам, включая использование папки vendor, использование модулей с Docker в разработке, зависимости инструментов и т. д.
Если вы уже знакомы с модулями Go и знаете Wiki, как свои пять пальцев, эта статья, вероятно, не будет для вас очень полезной. Но для остальных, однако, она может сэкономить несколько часов проб и ошибок.
Так что если вам по пути, запрыгивайте и наслаждайтесь поездкой.

Быстрый запуск
Если в ваш проект уже интегрировано управление версиями, вы можете просто запустить
go mod init
Или указать путь к модулю вручную. Это что-то вроде имени, URL и пути импорта для вашего пакета:
go mod init github.com/you/hello
Эта команда создаст файл
go.mod
, который одновременно определяет требования проекта и лочит зависимости на их правильные версии (в качестве аналогии для вас, это как package.json
и package-lock.json
, объединенные в один файл):module github.com/you/hello
go 1.12
Запустите
go get
, чтобы добавить новую зависимость в ваш проект:Обратите внимание, что хотя вы не можете указать диапазон версий с помощью go get, то что вы здесь определяете, это не конкретная, а минимальная версия. Как мы увидим позже, есть способ изящно актуализировать зависимости в соответствии с semver.
# use Git tags
go get github.com/go-chi/chi@v4.0.1
# or Git branch name
go get github.com/go-chi/chi@master
# or Git commit hash
go get github.com/go-chi/chi@08c92af
Теперь наш файл
go.mod
выглядит следующим образом:module github.com/you/hello
go 1.12
require github.com/go-chi/chi v4.0.2+incompatible // indirect
Суффикс
+incompatible
добавляется ко всем пакетам, которые еще не настроены под модули Go или нарушают их правила управления версиями.Поскольку мы еще нигде в нашем проекте не импортировали этот пакет, он был помечен как
// indirect
. Мы можем привести это в порядок с помощью следующей команды:go mod tidy
В зависимости от текущего состояния вашего репозитория, она либо удалит неиспользуемый модуль, либо удалит комментарий
// indirect
.Если какая-либо зависимость сама по себе не имеет
go.mod
(например, она еще не настроена под модули), тогда все ее зависимости будут записаны в родительский файл go.mod
(как вариант, ваш файл go.mod)
вместе с комментарием // indirect
, чтобы указать, что они там не от прямого импорта в ваш модуль.В глобальном плане цель
go mod tidy
состоит также в добавлении любых зависимостей, необходимых для других комбинаций ОС, архитектур и тегов сборки. Обязательно запускайте ее перед каждым релизом.Следите также за тем, чтобы после добавления зависимости был создан файл
go.sum
. Вам может показаться, что это lock-файл. Но на самом деле go.mod
уже предоставляет достаточно информации для на 100% воспроизводимых сборок. Файл go.sum
создается в проверочных целях: он содержит ожидаемые криптографические контрольные суммы содержимого отдельных версий модуля.Отчасти потому, что
go.sum
не является lock-файлом, он сохраняет записанные контрольные суммы для версии модуля даже после того, как вы перестанете использовать этот модуль. Это позволяет проверять контрольные суммы, если вы позже возобновите его использование, что обеспечивает дополнительную безопасность.
Только что мигрировали mkcert в модули (с vendor/ для обратной совместимости) и все прошло гладко
https://github.com/FiloSottile/mkcert/commit/26ac5f35395fb9cba3805faf1a5a04d260271291
$ GO111MODULE=on go1.11rc1 mod init $ GO111MODULE=on go1.11rc1 mod vendor $ git add go.mod go.sum vendor $ git rm Gopkg.lock Gopkg.toml Makefile
FAQ: Должен ли я коммититьgo.sum
в git?
A: Определенно да. С ним обладателям ваших источников не нужно доверять другим репозиториям GitHub и владельцам пользовательских путей импорта. Уже на пути к нам нечто получше, ну а пока это та же модель, что и хэши в lock-файлах.
Команды
go build
и go test
, автоматически загрузят все отсутствующие зависимости, хотя вы можете сделать это явно с помощью go mod download
, чтобы предварительно заполнить локальные кэши, которые могут оказаться полезными для CI.По умолчанию все наши пакеты из всех проектов загружаются в каталог
$GOPATH/pkg/mod
. Мы обсудим это подробнее позже.Обновление версий пакетов
Вы можете использовать
go get -u
или go get -u=patch
для обновления зависимостей до последней минорной версии или патча соответственно.Но вы не можете обновиться так до мажорных версий. Код, включаемый в модули Go, должен технически соответствовать следующим правилам:
- Соответствовать semver (пример тега VCS v1.2.3).
- Если модуль версии v2 или выше, мажорная версия модуля должна быть включена как
/vN
в конце пути модуля, используемого в файлеgo.mod
, и в пути импорта пакета:
import "github.com/you/hello/v2"
По-видимому, это сделано для того, чтобы разные версии пакетов могли быть импортированы в одной сборке (см. diamond dependency problem).
В двух словах, Go ожидает, что вы будете очень осмотрительны при внесении мажорных версий.
Замена импортированных модулей
Вы можете указать необходимый модуль для своего собственного форка или даже локального пути к файлу, используя директиву
replace
:go mod edit -replace github.com/go-chi/chi=./packages/chi
Результат:
module github.com/you/hello
go 1.12
require github.com/go-chi/chi v4.0.2+incompatible
replace github.com/go-chi/chi => ./packages/chi
Вы можете удалить строку вручную или запустить:
go mod edit -dropreplace github.com/go-chi/chi
Попроектное управление зависимостями
Исторически весь код Go хранился в одном гигантском монорепозитории, потому что именно так Google организовывает свою кодовую базу, и это сказывается на дизайне языка.
Модули Go — это своего рода отступление от этого подхода. Вам больше не нужно хранить все свои проекты в
$GOPATH
.Тем не менее, технически все ваши загруженные зависимости все еще помещаются в
$GOPATH/pkg/mod
. Если вы используете Docker-контейнеры при локальной разработке, это может стать проблемой, поскольку зависимости хранятся вне проекта. По умолчанию они просто не видны в вашей IDE.
Обычно это не проблема для других языков, но это то, с чем я впервые столкнулся при работе с кодовой базой Go.
К счастью, есть несколько (недокументированных) способов решения этой проблемы.
Вариант 1. Установите GOPATH внутри каталога вашего проекта.
На первый взгляд это может показаться нелогичным, но если вы запускаете Go из контейнера, вы можете переопределить его GOPATH, чтобы он указывал на каталог проекта для того, чтобы пакеты были доступны из хоста:
version: '3.7'
services:
app:
command: tail -f /dev/null
image: golang:1.12.6-stretch
environment:
# Все ваши зависимости будут расположены прямо здесь - /code/.go/pkg/mod
- GOPATH=/code/.go
ports:
- 8000:8000
volumes:
- ./:/code:cached
working_dir: /code
Популярные IDE должны иметь возможность установить GOPATH на уровне проекта (рабочей области):

Единственный недостаток этого подхода — отсутствие взаимодействия со средой выполнения Go на хост-компьютере. Вы должны выполнять все команды Go внутри контейнера.
Вариант 2: Вендоринг ваших зависимостей
Еще один способ — скопировать зависимости вашего проекта в папку
vendor
:go mod vendor
Следует сразу отметить: мы НЕ разрешаем Go прямую загрузку материалов в папку vendor: с модулями это невозможно. Мы просто копируем уже загруженные пакеты.
К тому же, если вы отвендорите свои зависимости, как в примере выше, затем очистите
$GOPATH/pkg/mod
, а затем попробуйте добавить несколько новых зависимостей в ваш проект, вы увидите следующее:- Go перестроит кэш загрузки для всех пакетов по
$GOPATH/pkg/mod/cache
. - Все загруженные модули будут скопированы в
$GOPATH/pkg/mod
. - И, наконец, Go скопирует эти модули в
vendor
папку, удаляя примеры, тесты и некоторые другие файлы, от которых вы напрямую не зависите.
Более того, в этой недавно созданной vendor-папке отсутствует много вещей:

Типичный файл Docker Compose выглядит следующим образом (обратите внимание на привязки томов):
version: '3.7'
services:
app:
command: tail -f /dev/null
image: golang:1.12.6-stretch
ports:
- 8000:8000
volumes:
# Это кэш модулей go, без него вам придется повторно загружать все зависимости после перезапуска контейнера
- modules:/go/pkg/mod/cache
- ./:/code:cached
working_dir: /code
volumes:
modules:
driver: local
Обратите внимание, что я НЕ комичу эту vendor -папку в систему контроля версий или не собираюсь использовать ее в продакшене. Это строго локальный сценарий разработки, который обычно можно найти в некоторых других языках.
Однако, когда я читаю комментарии от некоторых мейнтейнеров Go и некотроые предложения, связанные с частичным вендорингом (ЧЕ?), у меня складывается впечатление, что изначально эта фича предназначалась не для этого юзкейса.
Один из комментаторов на reddit помог мне пролить свет на это:
Обычно люди вендорят свои зависимости по таким причинам, как желание иметь герметичные сборки без доступа к сети, а также наличия копии готовых зависимостей в случае отказа github или исчезновения репозитория, и возможность более легкого аудита изменений в зависимостях с использованием стандартных инструментов VCS и т. д.
Да, не похоже на что-либо из того, что может меня заинтересовать.
Согласно команде Go, вы можете запросто подключить вендоринг, установив переменную среды
GOFLAGS=-mod=vendor
. Я не рекомендую так делать. Использование флагов просто сломает go get
без предоставления каких-либо других преимуществ для вашего ежедневного рабочего процесса:
На самом деле, единственное место где вам нужно подключить вендоринг — это ваше IDE:

После нескольких проб и ошибок я пришел к следующей процедуре для добавления вендорных зависимостей в этом подходе.
Шаг 1. Требование
Вы можете потребовать зависимость с помощью
go get
:go get github.com/rs/zerolog@v1.14.3
Шаг 2. Импорт
Затем импортируйте его куда-нибудь в своем коде:
import (
_ "github.com/rs/zerolog"
)
Шаг 3. Вендоринг
Наконец, отвендорите ваши зависимости заново:
go mod vendor
Существует ожидающее рассмотрения предложение разрешить go mod vendor принимать определенные шаблоны модулей, которые могут решить (а могут и не решить) некоторые из проблем связанные с этим рабочим процессом.
go mod vendor
уже автоматически требует пропущенные импорты, поэтому шаг 1 является необязательным в этом рабочем процессе (если вы не хотите указывать ограничения версии). Однако, без шага 2 она не подхватит загруженный пакет.Этот подход лучше взаимодействует с хост-системой, но он довольно запутан, когда дело доходит до редактирования ваших зависимостей.
Лично я думаю, что переопределение GOPATH является более чистым подходом, поскольку он не жертвует функциональность
go get
. Тем не менее, я хотел показать обе стратегии, потому что папка vendor может быть привычнее для людей, пришедших с других языков, таких как PHP, Ruby, Javascript и т. д. Как вы можете увидеть из махинаций, описанных в этой статье, это не особенно хороший выбор для Go.Зависимости инструментов
Нам может понадобиться установить некоторые инструменты на основе Go, которые не импортируются, а используются как часть среды разработки проекта. Простым примером такого инструмента является CompileDaemon, который может наблюдать за вашим кодом на предмет изменений и перезапускать ваше приложение.
Официально рекомендуемый подход заключается в добавлении
tools.go
файла(имя не имеет значения) со следующим содержанием:// +build tools
package tools
import (
_ "github.com/githubnemo/CompileDaemon"
)
- Ограничение
// +build tools
не позволяет вашим обычным сборкам фактически импортировать ваш инструмент. - Выражение import позволяет командам go точно записывать информацию о версии ваших инструментов в файл
go.mod
вашего модуля.
Ну вот и все. Я надеюсь, что вы не будете так же озадачены, как я, когда я впервые начал использовать модули Go. Вы можете посетить Go Modules wiki для получения более подробной информации.
Успеть на курс.