Монорепозиторий с Go: организация кода на практике
Монорепозиторий (monorepo) — это подход к управлению исходным кодом, при котором все проекты, библиотеки и сервисы хранятся в одном репозитории. В мире Go этот подход стал особенно популярен благодаря встроенной поддержке модулей и гибкой системе пакетов. В этой статье мы разберем, как организовать монорепозиторий в Go на практике, рассмотрим типовую структуру, примеры кода и лучшие практики.
Зачем нужен монорепозиторий в Go?
Монорепозиторий решает несколько ключевых задач:
- Упрощение управления зависимостями — все внутренние пакеты находятся в одном месте, их легко обновлять и рефакторить.
- Единая история изменений — атомарные коммиты, которые затрагивают сразу несколько сервисов, работают без лишних синхронизаций.
- Ускорение CI/CD — можно настроить пайплайны, которые собирают только изменившиеся части проекта.
- Повышение качества кода — проще внедрять единые стандарты, линтеры и код-ревью.
Структура монорепозитория Go: базовая схема
Рассмотрим типовую структуру монорепозитория на Go. Для примера возьмем проект, состоящий из двух микросервисов (api и worker) и общей библиотеки lib:
my-monorepo/
├── go.work
├── go.work.sum
├── apps/
│ ├── api/
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── handler.go
│ └── worker/
│ ├── go.mod
│ ├── main.go
│ └── process.go
├── lib/
│ ├── go.mod
│ ├── auth/
│ │ └── auth.go
│ └── db/
│ └── db.go
├── scripts/
│ ├── build.sh
│ └── test.sh
└── Makefile
Ключевые элементы:
go.work— файл рабочей области Go (Workspace), который связывает все модули.apps/— директория с приложениями (микросервисами, CLI-утилитами и т.д.).lib/— общие библиотеки, которые используются всеми приложениями.scripts/— вспомогательные скрипты для сборки, тестирования и деплоя.
Настройка Go Workspace (go.work)
Go 1.18+ предоставляет механизм workspace, который позволяет работать с несколькими модулями одновременно. Создадим файл go.work в корне:
go 1.22
use (
./apps/api
./apps/worker
./lib
)
Теперь при разработке можно запускать команды из корня проекта, и Go будет видеть все модули как единое целое. Например, чтобы запустить тесты для всех модулей:
go test ./...
А чтобы собрать конкретный сервис:
cd apps/api && go build -o bin/api .
Пример кода: общая библиотека и микросервис
Создадим простую библиотеку для аутентификации в lib/auth/auth.go:
package auth
import "errors"
var (
ErrInvalidToken = errors.New("invalid token")
)
// ValidateToken проверяет токен и возвращает ID пользователя.
func ValidateToken(token string) (string, error) {
if token == "" {
return "", ErrInvalidToken
}
// Здесь может быть реальная проверка JWT или вызов внешнего сервиса
return "user-123", nil
}
Теперь используем эту библиотеку в микросервисе apps/api/handler.go:
package main
import (
"encoding/json"
"net/http"
"my-monorepo/lib/auth"
)
type UserResponse struct {
UserID string `json:"user_id"`
}
func UserHandler(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
userID, err := auth.ValidateToken(token)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
resp := UserResponse{UserID: userID}
json.NewEncoder(w).Encode(resp)
}
Обратите внимание: импорт "my-monorepo/lib/auth" работает благодаря go.work. Go автоматически разрешает пути внутри рабочей области.
Управление зависимостями внутри монорепозитория
Каждый модуль имеет свой go.mod. Например, apps/api/go.mod может выглядеть так:
module my-monorepo/apps/api
go 1.22
require (
my-monorepo/lib v0.0.0
github.com/gorilla/mux v1.8.1
)
replace my-monorepo/lib => ../../lib
Директива replace указывает Go, что библиотека находится локально. Это стандартный подход для монорепозиториев. При использовании go.work replace-директивы можно опустить, но для CI/CD их лучше сохранить.
Для автоматического обновления всех модулей используйте команду:
go work sync
Она синхронизирует версии зависимостей между всеми модулями в рабочей области.
CI/CD в монорепозитории Go
Монорепозиторий позволяет эффективно настроить пайплайны. Пример скрипта scripts/build.sh:
#!/bin/bash
set -e
# Определяем, какие модули изменились
CHANGED=$(git diff --name-only HEAD~1 | grep '^apps/' | cut -d'/' -f2 | sort -u)
for app in $CHANGED; do
echo "Building $app..."
cd apps/$app
go build -o bin/$app .
cd ../..
done
Этот скрипт собирает только те приложения, которые были изменены в последнем коммите. В реальном проекте можно добавить параллельную сборку, кэширование и отправку артефактов в реестр.
Лучшие практики организации монорепозитория Go
- Используйте единую версию Go — укажите одинаковую версию в
go.modвсех модулей и в