14. Dependency management. replace, exclude
Инструменты ручного управления графом зависимостей
В идеальном академическом мире команда go mod tidy всегда безупречно разрешает все зависимости, а авторы сторонних библиотек никогда не допускают багов и не ломают контракты. Но в реальном мире промышленной разработки закон Мёрфи работает безотказно: критическая уязвимость в чужой библиотеке обнаруживается в пятницу вечером перед релизом, мейнтейнер репозитория ушел в отпуск, а ваш продакшн-сервис обязан функционировать без сбоев.
Для таких ситуаций создатели Go предусмотрели инструменты точечного "хирургического вмешательства" в граф зависимостей. Директивы replace и exclude в файле go.mod позволяют инженеру временно или постоянно переопределить решения алгоритма MVS (Minimal Version Selection), перенаправить пути пакетов на собственные форки или исключить сбойные версии.
Директива replace: Перенаправление
Директива replace заставляет Go подставить другой путь или версию вместо объявленной в блоке require. Это работает прозрачно на уровне дерева исходников: код затягивается из нового источника, но во всех файлах вашего проекта пути import остаются прежними, избавляя вас от необходимости массового рефакторинга.
1. Исправление бага в сторонней библиотеке (Forking)
Вы используете библиотеку github.com/old/lib v1.2.3, но в ней обнаружен критический race condition. Автор не принимает pull request, а ждать неделями вы не можете. Вы делаете форк репозитория в свой аккаунт, исправляете дефект и объявляете в go.mod:
module myproject
go 1.22
require github.com/old/lib v1.2.3
// Перенаправляем на ваш форк на GitHub
replace github.com/old/lib => github.com/myname/lib v1.2.4-fix
2. Локальная разработка (Monorepo / WIP)
Вы разрабатываете два взаимосвязанных модуля одновременно: микросервис api и общую библиотеку db. Вы хотите проверить изменения в db локально, не пуша промежуточные коммиты в удаленный Git-репозиторий:
// Указываем Go искать модуль в директории выше
replace github.com/myorg/db => ../db
Это заставляет Go читать исходный код прямо с диска, минуя сетевое скачивание и кэш $GOMODCACHE.
flowchart TD
A["Main Module"] --> B["require lib v1.0"]
B --> C{"replace directive"}
C -- "Без replace" --> D["Download github.com/lib v1.0"]
C -- "С replace" --> E["Read ../local/lib"]
style E fill:#064e3b,stroke:#34d399,stroke-width:2px,color:#f8fafc
Директива exclude: Исключение версий
Директива exclude приказывает алгоритму MVS: "Сделай вид, что эта конкретная версия библиотеки не существует в природе". Это необходимо, если автор выпустил релиз с фатальной регрессией или критической уязвимостью CVE, но из-за правил SemVer алгоритм упорно пытается его выбрать.
// Запрещаем использование сломанной версии
exclude github.com/broken/lib v1.5.0
Если ваш проект (или любая транзитивная зависимость) запрашивает github.com/broken/lib v1.5.0, Go исключит её из рассмотрения и выберет следующую подходящую версию (например, v1.5.1 или откатится к совместимой v1.4.0).
Сценарий использования
Представьте, что библиотека A требует Lib >= v1.0, а самая свежая версия — v1.5.0. Но в v1.5.0 есть уязвимость. Вы можете:
- Поднять требование в
Aдоv1.5.1(если она есть). - Или запретить
v1.5.0черезexclude. Тогда MVS выберетv1.4.0(если она совместима) илиv1.5.1.
Директива retract (Современный подход)
В Go 1.16 появилась директива retract — это цивилизованный "белый флаг" от самих авторов библиотек. Если вы как разработчик пакета случайно выпустили битую сборку или скомпрометировали секреты, удалять тег в Git категорически нельзя (это сломает go.sum у тысяч пользователей). Вместо этого в следующий релиз добавляется блок retract:
// В go.mod библиотеки
retract v1.5.0 // Серьезный баг, отзываем версию
Когда вы запускаете go list -m -u или go get, Go видит пометку retract и автоматически игнорирует такую версию, предупреждая вас. Это более здоровый способ борьбы с плохими релизами, чем exclude в вашем go.mod.
Практика и безопасность
Использование replace для подмены зависимостей (особенно системных или криптографических) — это всегда повышенный риск. Вы берете на себя полную ответственность за код, который исполняется в вашем приложении.
В продакшене replace часто используется для:
- Vendoring: Когда код зависимостей копируется в проект (редко сейчас).
- Private proxies: Перенаправление на внутренний прокси для частных библиотек (хотя это лучше делается через
.netrcилиGOPRIVATE).
Итог
replace— мощный инструмент для форков и локальной разработки, но опасен в продакшене из-за путей файловой системы.exclude— способ запретить использование конкретной версии зависимости.retract— механизм для авторов библиотек, позволяющий "отозвать" сломанную версию.- Используйте эти инструменты как временные меры, пока не будет обновлена зависимость или исправлен апстрим.
Мы научились управлять версиями и форками. Но что делать, если ваша компания использует приватные репозитории, и вы не хотите "светить" их в публичные прокси? В следующей статье мы разберем настройку доступа к приватным модулям: 15. Private modules и proxy.