Чтобы поделиться кодом, нужно создать библиотеку и разместить её в самостоятельном репозитории. Но иногда возникает необходимость хранить библиотеку вместе с сервисом, который её использует. Среди Go-разработчиков существует мнение, что экспортируемые библиотеки стоит хранить в директории pkg.  История этой директории берёт начало со времён ранних релизов Go, когда модули стандартной библиотеки находились в $GOROOT/src/pkg. Впоследствии директория pkg была удалена, но многие проекты, такие как Kubernetes, повторили у себя данную файловую структуру. С тех пор pkg закрепилась в файловой структуре Go-проектов. Когда же лучше хранить библиотечный код в одном репозитории с сервисом?  При разработке в open source, если не хочется плодить множество отдельных репозиториев. В процессе дробления монолита на микросервисы. Монолит экспортирует часть своего кода, но при этом остаётся его владельцем. Это позволяет создавать в монолите pull request, который модифицирует одновременно и основной код монолита, и код библиотеки. При шеринге своим API. Например, можно хранить в pkg сгенерированный на основе .proto-файла клиент сервиса. Это позволит вашим клиентам удобнее обращаться к вам по gRPC-протоколу. Как оказалось, при экспорте библиотеки из сервиса может возникнуть множество нюансов. В этой статье мы разберём, как сделать внешнюю библиотеку максимально удобной как для сервиса, который её экспортирует, так и для импортёров.  Я не буду подробно рассматривать функционал Workspaces, появившийся в Go 1.18. Используя его, удобно работать с несколькими репозиториями одновременно. Это может быть альтернативой предлагаемому в статье подходу: вы выносите библиотеку в отдельный репозиторий и продолжаете работать с ней в вашем сервисе при помощи Workspaces. У такого подхода есть свои плюсы и минусы. Мы же сосредоточимся на работе с несколькими файлами go.mod в рамках одного репозитория. Дисклеймер: В конце статьи кратко описаны все необходимые шаги. Также вы можете посмотреть готовое решение в моём GitHub-репозитории. А все советы из статьи можно найти в Wiki про Golang-модули. Первое решение, которое приходит на ум, — просто перенести экспортируемый код в папку pkg. Так мы даём понять импортёрам, что делимся данным кодом. Наш сервис может продолжать пользоваться библиотекой как раньше — она просто переехала в другую папку. Однако при таком решении возникают две проблемы. Сервис, который захочет импортировать библиотеку, будет вынужден импортировать весь наш сервис, со всеми его зависимостями. Это может привести к конфликтам версий. Поддерживать разросшиеся зависимости будет затруднительно. Давайте попробуем проиллюстрировать эту проблему. Создадим новый проект и импортируем в него библиотеку mylib. Теперь заглянем в go.sum. Там мы видим множество зависимостей, например github.com/spf13/cobra. Большинство из этих зависимостей не требуются для работы с mylib. Проблема в том, что мы импортируем не только библиотеку mylib, но и весь сервис go-pkg-example. “You wanted a banana but what you got was a gorilla holding the banana and the entire jungle“ Joe Armstrong При таком решении версия библиотеки и версия нашего сервиса будут совпадать. Но если сервис, например, повысит версию с 1.0.0 до 2.0.0, то код библиотеки в pkg не изменится. Нужно искать способ версионировать библиотеку и сервис по отдельности. Чтобы решить проблемы предыдущего подхода, нужно сделать экспортируемую библиотеку самостоятельным Golang-модулем. Это позволит избавиться от лишних зависимостей для наших клиентов: при импорте библиотеки будут подтягиваться только её собственные зависимости. Эту библиотеку теперь можно версионировать отдельно от сервиса. Для этого создадим git tag, который начинается с пути до библиотеки, а заканчивается её версией. Поскольку наш сервис начал экспортировать библиотеку, следует соблюдать принципы семантического версионирования. Если в процессе разработки в библиотеку внесены изменения, нарушающие обратную совместимость API, то нужно увеличить мажорную версию библиотеки. Более подробно про семантическое версионирование читайте в спецификации. Также обратите внимание на то, что в теге между путём до экспортируемой библиотеки и её версией используется разделитель “/”. Из-за этого могут возникнуть проблемы, если путь к вашей библиотеке заканчивается директорией, которая выглядит как версия библиотеки, например “pkg/grpc-api/v1” или “pkg/mylib/v1”. Избегайте такого именования, если используете данный подход. Теперь наши клиенты могут добавить библиотеку к себе в зависимости, прописав требуемую версию. При этом подтянутся только зависимости библиотеки mylib, а не всего сервиса. Если вы повторили локально пример из прошлой главы, то вместо данной команды измените ваш go.mod. Теперь посмотрите на содержимое файла go.sum. Как видите, лишние зависимости были удалены. У такого решения всё ещё имеется недостаток. Поскольку библиотека стала модулем, наш собственный сервис тоже должен импортировать её через свой go.mod. Мы не можем в рамках одного pull request модифицировать и код библиотеки, и код сервиса. Мы потеряли большинство преимуществ хранения библиотеки в репозитории с сервисом. Схожего результата можно было бы добиться простым выносом библиотечного кода в отдельный репозиторий. К счастью, и у этой проблемы есть решение. При помощи директивы replace в файле go.mod мы можем подсказать системе модулей, что хотим использовать локальный код нашей библиотеки, а не подтягивать её как версионированную зависимость. Теперь мы можем продолжать работать с экспортируемыми библиотеками так, как работали бы с локальными пакетами. При этом клиенты могут импортировать эти библиотеки, используя их версионирование и подтягивания только необходимые зависимости. Переносим экспортируемый код в директорию pkg. Делаем в директории c библиотекой Golang-модуль и создаём git-теги, включающие в себя путь до экспортируемых модулей и их версии. Добавляем директиву replace для экспортируемых библиотек в go.mod нашего сервиса. Для примера смотрите мой репозиторий, каждый шаг представлен в нём отдельным коммитом.  Как видите, существует удобный способ шерить модули, в котором: экспортирующий сервис продолжает работать с библиотечным кодом как со своим собственным,  клиенты могут пользоваться версионированием и избежать лишних зависимостей.  Если у вас есть примеры использования экспортируемых модулей, делитесь в комментариях. Статья про историю и использование pkg. Обзор Go Workspaces.  Golang разработчик