Немножко про модули в golang
“Before software can be reusable, it first has to be usable.” – Ralph Johnson
Это статья скорее для себя, нежели чем хотелось чем-то поделиться. Идея её написания висела довольно давно, но сейчас сподвигло её написать статья “I want off Mr. Golang’s Wild Ride”. Мысленно можно разделить эту статью на две части: первая, в которой идёт обсуждение объединения или нормализации API между разными операционными системами, и вторая, в которой идёт речь про модули и их реализацию. Russ Cox на своём сайте сделал серию из 11 (!) статей, чтобы объяснить почему модули это не так просто, как казалось. Давайте кратко пройдёмся по истории работы с библиотеками в golang.
Ненавистный GOPATH
Первое, что тебе нужно было сделать прежде, чем начать работу с golang - это установить переменную GOPATH. Она была нужна для того, чтобы команда go get
знала куда складывать пакеты. Сейчас есть значение по умолчанию равное $HOME/go
, что немножко упрощает работу, но не сильно. В качестве неймспейса для всех библиотек используется формат host/namespace/lib
, например: github.com/xo/usql
.
Пробуем поставить утилиту usql
:
% time go get github.com/xo/usql
56.93s user 6.39s system 85% cpu 1:13.98 total
% tree -d -L 3 ~/go/src
├── github.com
│ ├── alecthomas
│ │ ├── chroma
│ │ ├── kingpin
│ │ ├── template
│ │ └── units
│ ├── danwakefield
│ │ └── fnmatch
│ ├── denisenkom
│ │ └── go-mssqldb
│ ├── dlclark
│ │ └── regexp2
│ ├── gohxs
│ │ └── readline
│ ├── golang-sql
│ │ └── civil
│ ├── go-sql-driver
│ │ └── mysql
│ ├── lib
│ │ └── pq
│ ├── mattn
│ │ ├── go-isatty
│ │ ├── go-runewidth
│ │ └── go-sqlite3
│ ├── xo
│ │ ├── dburl
│ │ ├── tblfmt
│ │ ├── terminfo
│ │ ├── usql
│ │ └── xoutil
│ └── zaf
│ └── temp
└── golang.org
└── x
├── crypto
└── sys
% du -h --max-depth=1 ~/go/src/
77M ./github.com
35M ./golang.org
111M .
Утилита предназначена для работы с разными базами данных и ожидаемо ей требуется много библиотек-драйверов для работы, но 111 мегабайт и минута на скачивание выглядит, как перебор. Если вам нужна только сама утилита, всегда можно использовать опцию -u
. В защиту такого подхода, хочу сказать что использование прямого соответствия между импортом библиотеки и пути по которому она находится удобно. Так как язык разработал Google, то сразу был сделан вывод о том, что вся эта система работала поверх монорепозитория внутри компании и такое использование было логично. Но как только язык вышел публично, сразу стало понятно что использовать хост сервиса в пути не самое удобное решение. В случае, если проект был перемещён в другое пространство, ничего страшного не происходит, но вот если поменялся хост, то труба. Об этом подумали несколько людей и началась вторая эпоха.
Средневековый vendor
Когда node.js только набирал популярность (версии 0.8-0.12), шёл довольно большой холивар на тему вендоринга зависимостей. Были люди, которые говорили, что нужно складывать всю папку node_modules в репозиторий и при обновлении комиттить изменения, но в итоге сообщество пришло к тому, что это приносит только лишние расходы и отказались. Как мы все потом знаем, это привело к трагедии Дарта Плегаса leftpad. В Golang решили пойти тем же путём и появилась папка vendor, правда чтобы её использовать необходимо было перезаписать GOPATH в текущую директорую и тогда начинались пляски с тем, что в директории должна была быть папка src, а в .gitignore
шли директории bin/
и pkg/
. Эту проблему надо было автоматизировать и появились инструменты, чтобы с таким подходом было работать удобно, а именно glider, а в последствии dep. Более того, dep
был сделан как официальный эксперимент группы разработки языка, и в итоге именно он дал толчок развитию модулей в go 1.11. Из-за данных инструментов в репозиториях пропали папки vendor
, так как они создавали lock-файлы и можно было воспроизвести среду. А вот проблемы были в том, что каждый инструмент делал свой формат. :(
Современный go.mod
И здесь мы возвращаемся к модулям с которых началась эта статья. В go 1.11 можно было с помощью переменной GO111MODULE=on, а с недавним релизом go 1.14 данное поведение стало по умолчанию. Что нужно сделать для того, чтобы его использовать прекрасно описано в The Go Blog в 4 (!) частях. Здесь бы хотелось сосредоточиться на том, почему это не современная штука.
Проблема №1. Перейти надо всем
Разработчики golang молодцы, что дали 3 минорные версии для того, чтобы авторы библиотек могли перейти на go.mod и судя по тому, что из 21000 открытых issues на гитхабе закрыто 18000 все были нацелены на использование данного инструмента.
Проблема №2. Формат mod в go.mod
Долго бомбило после прочтения PEP 518, что выбрали формат toml. Главное, что его выбрали потому что json слишком вербозный, а yaml имеет слишком сложную спецификацию. В golang решили просто пойти своим путём и сделать формат, который надо парсить. Можете пойти почитать, как он устроен тут. Уверен, что выбор был сделан исходя из принципа упрощения: зачем брать известное, если можно сделать своё простое? Более того, этот файл нельзя трогать руками. Всё взаимодействие с ним идёт через команду go get
, так что частично мы откатились к прежнему решению с GOPATH, просто теперь все пакеты лежат в GOPATH/pkg
.
Проблема №3. Версионирование
Все мы оказывались в ситуации, когда автор библиотеки не удосужился указать версии своих зависимостей и занимаешься бинарным поиском совместимых пакетов. В golang решили пойти дальше и сказать, что изменение мажорной версии должно приводить к изменении путей. Тема хорошая, но реализация полагается на рациональность людей, который будут эту мажорную версию менять. А как мы знаем, у этой проблему есть две крайности: Chrome,который имеет 80 версию на данный момент, и PostgresQL, который долгое время жил на 9.x номенклатуре и утверждал, что вторая цифра - это тоже мажорный релиз. Справился ли подход golang с тем, чтобы пакеты всегда собирались при обновлении пакетов? Нет, для примера вы можете взять обновление пакета grpc-consul-resolver из-за того, что в минорной версии сменился интерфейс, и это библиотека от google.
Проблема №4. Настройки
Ладно, тут я просто напишу GOSUMDB=off, GOPRIVATE=*example.com* и обязательно GO111MODULE=on.
Итого
Немножко грустно от того, что в мире уже десяток популярных языков программирования, а понятной и простой системы по работе с пакетами так до сих пор и изобретают с нуля.
P.s. а может быть и не с нуля, но осадочек остался.
P.p.s. кстати, с модулями не отказались от вендоринга, даже комманду сделали для этого go mod vendor
.