17. Workspaces. go work

До появления Go 1.18 работа с несколькими модулями одновременно (например, когда вы параллельно разрабатываете внутреннюю библиотеку и сервис, который её использует) была настоящей головной болью инженеров. Единственным способом связать их локально была директива replace в go.mod, указывающая путь к соседней директории на диске. Это приводило к тому, что файл go.mod засорялся временными локальными путями, которые разработчики регулярно забывали удалить перед git commit и ломали CI/CD пайплайны всей команде.

Механизм Workspaces (рабочие области) и файл go.work навсегда решили эту проблему, предоставив чистый, элегантный и изолированный способ работы с монорепозиториями и мульти-модульными проектами.


Проблема: Мульти-модульная разработка

Представьте типичную структуру современного проекта:

Text
my-project/
├── service/      # Основной микросервис (модуль)
│   └── go.mod
└── lib/          # Внутренняя shared-библиотека (модуль)
    └── go.mod

Сервис service импортирует github.com/myorg/lib. Если вы хотите внести правку в lib и тут же протестировать её в service без публикации промежуточных релизов на GitHub, раньше приходилось писать в service/go.mod:

Go
replace github.com/myorg/lib => ../lib

Если вы забывали убрать эту строку перед git push, удаленный сборщик падал с ошибкой: директории ../lib по этому пути в изолированном контейнере сборки попросту не существовало.


Решение: go.work

Workspaces позволяют определить внешний контекст для локальной разработки, вообще не трогая файлы go.mod внутри модулей.

Файл go.work создается на один уровень выше — в корневой директории репозитория. Он сообщает компилятору: "Все перечисленные модули образуют единую рабочую область и должны разрешаться локально друг в друга".

Go
go 1.22

use (
    ./service
    ./lib
)

Теперь, когда код внутри service импортирует github.com/myorg/lib, Go проверяет файл go.work, видит, что этот модуль включен в блок use, и прозрачно читает исходный код прямо из соседней папки ./lib, полностью игнорируя сетевой прокси и версии в go.mod.

flowchart TD
    A["go build service"] --> B["Поиск go.work"]
    B --> C{"Модуль в списке use?"}

    C -- "Да" --> D["Локальное разрешение"]
    D --> E["Чтение из ./lib"]

    C -- "Нет" --> F["Стандартное разрешение"]
    F --> G["Go Proxy или Vendor"]

    style D fill:#064e3b,stroke:#34d399,color:#f8fafc
    style F fill:#7f1d1d,stroke:#f87171,color:#f8fafc
  

Команды управления

Управление рабочей областью происходит через штатные CLI-команды тулчейна:

  • Инициализация:

    Bash
    go work init ./service ./lib
    

    Создает файл go.work и добавляет в него указанные пути модулей.

  • Добавление модуля:

    Bash
    go work use ./newmodule
    

    Добавляет новую строку use в файл. Если директории нет на диске, строка будет закомментирована.

  • Синхронизация:

    Bash
    go work sync
    

    Сверяет зависимости всех модулей workspace и проталкивает необходимые обновления в файлы go.mod, гарантируя консистентность версий.


Связь с Vendor и CI/CD

Workspaces прекрасно интегрируются с вендорингом. Команда go work vendor создаст единую директорию vendor/ в корне проекта, собрав туда транзитивные зависимости всех модулей, перечисленных в use.

Это критически важно для CI/CD в монорепозиториях. Вы можете настроить пайплайн так, чтобы он запускал сборку с флагом -workfile=off (принудительно игнорировать workspace) для проверки независимости модулей, или использовать go.work для сборки всего монорепозитория целиком.


Переменная GOWORK

Вы можете управлять файлом рабочей области через переменную окружения GOWORK:

Bash
# Принудительно использовать конкретный файл
export GOWORK=/path/to/custom.work

# Отключить workspace mode (вернуться к стандартному поведению)
export GOWORK=off

Это незаменимо в сложных сборочных скриптах, где на одном раннере нужно изолировать этапы сборки разных сервисов.


Итог

  1. Workspaces (go.work) — стандартный инструмент для локальной разработки мульти-модульных проектов и монорепозиториев.
  2. Они навсегда устранили необходимость во временных хаках с директивами replace внутри go.mod.
  3. Управляются через команды go work init/use/sync.
  4. Безопасны для коммита в монорепозиториях, но должны быть добавлены в .gitignore при ad-hoc отладке внешних библиотек.

Мы настроили структуру проекта и управление зависимостями. Теперь перейдем к обеспечению качества кода. В следующей статье мы разберем главный инструмент статического анализа в индустрии: 18. Линтеры. golangci lint.