Структура проекта Go: практики и шаблоны
Структурируйте проекты на Go для масштабируемости и ясности
Эффективная структура проекта на Go является фундаментальной для долгосрочного сопровождения, командной работы и масштабируемости. В отличие от фреймворков, которые навязывают жесткую структуру каталогов, Go предлагает гибкость — но вместе со свободой приходит ответственность за выбор паттернов, соответствующих конкретным потребностям вашего проекта.

Понимание философии Go в отношении структуры проекта
Минималистичная философия дизайна Go распространяется и на организацию проектов. Язык не предписывает конкретную структуру, доверяя разработчикам принимать обоснованные решения. Этот подход привел к тому, что сообщество разработало несколько проверенных паттернов: от простых плоских структур для небольших проектов до сложной архитектуры для корпоративных систем.
Ключевой принцип — простота прежде всего, сложность только при необходимости. Многие разработчики попадают в ловушку избыточной инженерии начальной структуры, создавая глубоко вложенные директории и преждевременные абстракции. Начинайте с того, что нужно сегодня, и рефакторите проект по мере его роста.
Когда следует использовать директорию internal/ вместо pkg/?
Директория internal/ служит определенной цели в дизайне Go: она содержит пакеты, которые не могут быть импортированы внешними проектами. Компилятор Go принудительно соблюдает это ограничение, что делает internal/ идеальным местом для частой логики приложения, бизнес-правил и утилит, не предназначенных для повторного использования за пределами вашего проекта.
Директория pkg/, с другой стороны, сигнализирует о том, что код предназначен для внешнего потребления. Размещайте код здесь только если вы создаете библиотеку или повторно используемые компоненты, которые другие должны импортировать. Многим проектам вообще не нужна pkg/ — если ваш код не используется внешними системами, хранение его в корне или в internal/ выглядит чище. При создании повторно используемых библиотек рассмотрите возможность использования Go generics, чтобы создавать типобезопасные компоненты.
Стандартная структура проекта Go
Наиболее признанным паттерном является golang-standards/project-layout, хотя важно отметить, что это не официальный стандарт. Вот как выглядит типичная структура:
myproject/
├── cmd/
│ ├── api/
│ │ └── main.go
│ └── worker/
│ └── main.go
├── internal/
│ ├── auth/
│ ├── storage/
│ └── transport/
├── pkg/
│ ├── logger/
│ └── crypto/
├── api/
│ └── openapi.yaml
├── config/
│ └── config.yaml
├── scripts/
│ └── deploy.sh
├── go.mod
├── go.sum
└── README.md
Многие команды хранят небольшой общий пакет логирования в internal/ (например, internal/logx), чтобы бинарные файлы в cmd/ настраивали один логгер при запуске; pkg/logger/ в приведенной выше схеме уместна только тогда, когда этот код предназначен для импорта другими модулями. Для производственной настройки log/slog (JSON-строки, маскирование, поля контекста и трассировки, а также сигналы, хорошо работающие со стеками мониторинга), см. Структурированное логирование в Go с slog для наблюдаемости и оповещений.
Директория cmd/
Директория cmd/ содержит точки входа вашего приложения. Каждая поддиректория представляет отдельный исполняемый бинарный файл. Например, cmd/api/main.go собирает ваш API-сервер, а cmd/worker/main.go может собирать процессор фоновых задач.
Лучшая практика: Держите файлы main.go минималистичными — только для настройки зависимостей, загрузки конфигурации и запуска приложения. Вся существенная логика должна находиться в пакетах, которые импортирует main.go.
// cmd/api/main.go
package main
import (
"log"
"myproject/internal/server"
"myproject/internal/config"
)
func main() {
cfg, err := config.Load()
if err != nil {
log.Fatal(err)
}
srv := server.New(cfg)
if err := srv.Start(); err != nil {
log.Fatal(err)
}
}
Директория internal/
Здесь находится частый код вашего приложения. Компилятор Go предотвращает импорт пакетов из internal/ любыми внешними проектами, что делает их идеальными для:
- Бизнес-логики и доменных моделей
- Прикладных сервисов
- Внутренних API и интерфейсов
- Репозиториев базы данных (для выбора правильного ORM см. наше сравнение ORM для PostgreSQL в Go)
- Логики аутентификации и авторизации
Организуйте internal/ по функциям или доменам, а не по техническим слоям. Вместо internal/handlers/, internal/services/, internal/repositories/, предпочитайте internal/user/, internal/order/, internal/payment/, где каждый пакет содержит свои обработчики, сервисы и репозитории.
Следует ли начинать со сложной структуры директорий?
Однозначно нет. Если вы создаете небольшой инструмент или прототип, начните с:
myproject/
├── main.go
├── go.mod
└── go.sum
По мере роста проекта и выявления логических группировок вводите директории. Вы можете добавить пакет db/, когда логика работы с базой данных станет существенной, или пакет api/, когда обработчиков HTTP станет слишком много. Позвольте структуре возникать естественно, а не навязывайте ее заранее.
Плоские против вложенных структур: поиск баланса
Одной из самых распространенных ошибок в структуре проектов на Go является чрезмерная вложенность. Go предпочитает мелкие иерархии — обычно на один или два уровня глубины. Глубокая вложенность увеличивает когнитивную нагрузку и делает импорты громозкими.
Каковы самые распространенные ошибки?
Избыточная вложенность директорий: Избегайте структур вида internal/services/user/handlers/http/v1/. Это создает ненужную сложность навигации. Вместо этого используйте internal/user/handler.go.
Общие имена пакетов: Имена вроде utils, helpers, common или base являются запахами кода (code smells). Они не передают конкретную функциональность и часто становятся свалкой для несвязанного кода. Используйте описательные имена: validator, auth, storage, cache.
Циклические зависимости: Когда пакет A импортирует пакет B, а B импортирует A, у вас возникает циклическая зависимость — ошибка компиляции в Go. Это обычно сигнализирует о плохом разделении обязанностей. Введите интерфейсы или вынесите общие типы в отдельный пакет.
Смешивание обязанностей: Держите обработчики HTTP сфокусированными на HTTP, репозитории баз данных — на доступе к данным, а бизнес-логику — в пакетах сервисов. Размещение бизнес-правил в обработчиках затрудняет тестирование и связывает вашу доменную логику с HTTP.
ДDD (Domain-Driven Design) и Гексагональная архитектура
Для крупных приложений, особенно микросервисов, Domain-Driven Design (DDD) с Гексагональной архитектурой обеспечивает четкое разделение обязанностей.
Как структурировать микросервис, следуя принципам DDD?
Гексагональная архитектура организует код в концентрические слои, где зависимости направлены внутрь:
internal/
├── domain/
│ └── user/
│ ├── entity.go # Доменные модели и значения-объекты
│ ├── repository.go # Интерфейс репозитория (порт)
│ └── service.go # Доменные сервисы
├── application/
│ └── user/
│ ├── create_user.go # Случай использования: создание пользователя
│ ├── get_user.go # Случай использования: получение пользователя
│ └── service.go # Оркестрация прикладных сервисов
├── adapter/
│ ├── http/
│ │ └── user_handler.go # HTTP-адаптер (REST-концы)
│ ├── postgres/
│ │ └── user_repo.go # Адаптер базы данных (реализует порт репозитория)
│ └── redis/
│ └── cache.go # Адаптер кэша
└── api/
└── http/
└── router.go # Конфигурация маршрутов и middleware
Доменный слой (domain/): Основная бизнес-логика, сущности, значения-объекты и интерфейсы доменных сервисов. Этот слой не имеет зависимостей от внешних систем — никаких импортов HTTP или баз данных. Он определяет интерфейсы репозиториев (порты), которые реализуют адаптеры.
Прикладной слой (application/): Случаи использования, которые оркестрируют доменные объекты. Каждый случай использования (например, “создать пользователя”, “обработать платеж”) — это отдельный файл или пакет. Этот слой координирует доменные объекты, но не содержит бизнес-правил сам по себе.
Слой адаптеров (adapter/): Реализует интерфейсы, определенные внутренними слоями. HTTP-обработчики преобразуют запросы в доменные объекты, репозитории баз данных реализуют персистентность, очереди сообщений обрабатывают асинхронную коммуникацию. Этот слой содержит весь код, специфичный для фреймворков и инфраструктуры.
Слой API (api/): Маршруты, middleware, DTO (объекты передачи данных), версионирование API и спецификации OpenAPI.
Эта структура гарантирует, что ваша основная бизнес-логика остается тестируемой и независимой от фреймворков, баз данных или внешних сервисов. Вы можете заменить PostgreSQL на MongoDB или REST на gRPC, не затрагивая доменный код. Если вы внедряете CQRS в эту структуру, Реализация CQRS в Go показывает, как слой application/ естественным образом отображается на отдельные пакеты обработчиков команд и запросов, сохраняя сторону команд строгой, а сторону запросов ориентированной на DTO.
Практические паттерны для различных типов проектов
Небольшой CLI-инструмент
Для приложений командной строки вам понадобится структура, поддерживающая несколько команд и подкоманд. Рассмотрите использование фреймворков, таких как Cobra для структуры команд и Viper для управления конфигурацией. Наше руководство по созданию CLI-приложений в Go с Cobra & Viper подробно охватывает этот паттерн.
mytool/
├── main.go
├── command/
│ ├── root.go
│ └── version.go
├── go.mod
└── README.md
REST API сервис
При создании REST API на Go эта структура четко разделяет обязанности: обработчики занимаются HTTP, сервисы содержат бизнес-логику, а репозитории управляют доступом к данным. Для комплексного руководства, охватывающего подходы стандартной библиотеки, фреймворки, аутентификацию, паттерны тестирования и лучшие практики, готовые к продакшену, см. наше полное руководство по созданию REST API в Go. Для структурированного JSON-логирования, корреляции запросов и трассировок, а также форматов логов, поддерживающих оповещения, см. Структурированное логирование в Go с slog для наблюдаемости и оповещений.
myapi/
├── cmd/
│ └── api/
│ └── main.go
├── internal/
│ ├── config/
│ ├── middleware/
│ ├── user/
│ │ ├── handler.go
│ │ ├── service.go
│ │ └── repository.go
│ └── product/
│ ├── handler.go
│ ├── service.go
│ └── repository.go
├── pkg/
│ └── httputil/
├── go.mod
└── README.md
Monorepo с несколькими сервисами
myproject/
├── cmd/
│ ├── api/
│ ├── worker/
│ └── scheduler/
├── internal/
│ ├── shared/ # Общие внутренние пакеты
│ ├── api/ # Код, специфичный для API
│ ├── worker/ # Код, специфичный для воркера
│ └── scheduler/ # Код, специфичный для планировщика
├── pkg/ # Общие библиотеки
├── go.work # Файл рабочего пространства Go
└── README.md
Тестирование и документация
Размещайте файлы тестов рядом с тестируемым кодом, используя суффикс _test.go:
internal/
└── user/
├── service.go
├── service_test.go
├── repository.go
└── repository_test.go
Этот стандарт держит тесты близко к реализации, облегчая их поиск и поддержку. Для интеграционных тестов, затрагивающих несколько пакетов, создайте отдельную директорию test/ в корне проекта. Для комплексного руководства по написанию эффективных модульных тестов, включая табличные тесты, моки, анализ покрытия и лучшие практики, см. наше руководство по структуре и лучшим практикам модульного тестирования в Go.
Документация должна находиться в:
README.md: Обзор проекта, инструкции по настройке, базовое использованиеdocs/: Подробная документация, архитектурные решения, ссылки на APIapi/: Спецификации OpenAPI/Swagger, определения protobuf
Для REST API генерация и предоставление документации OpenAPI с Swagger необходимы для обнаружения API и опыта разработчика. Наше руководство по добавлению Swagger в ваш Go API охватывает интеграцию с популярными фреймворками и лучшие практики.
Управление зависимостями с помощью Go Modules
Каждый проект на Go должен использовать Go Modules для управления зависимостями. Для комплексного справочника по командам Go и управлению модулями проверьте наш Шпаргалку по Go. Инициализируйте с помощью:
go mod init github.com/yourusername/myproject
Это создает go.mod (зависимости и версии) и go.sum (контрольные суммы для проверки). Храните эти файлы в системе контроля версий для воспроизводимых сборок.
Регулярно обновляйте зависимости:
go get -u ./... # Обновить все зависимости
go mod tidy # Удалить неиспользуемые зависимости
go mod verify # Проверить контрольные суммы
Ключевые выводы
-
Начинайте с простого, развивайтесь естественно: Не усложняйте начальную структуру. Добавляйте директории и пакеты, когда этого требует сложность.
-
Предпочитайте плоские иерархии: Ограничивайте вложенность одним или двумя уровнями. Плоская структура пакетов Go улучшает читаемость.
-
Используйте описательные имена пакетов: Избегайте общих имен, таких как
utils. Называйте пакеты в соответствии с их функцией:auth,storage,validator. -
Четко разделяйте обязанности: Держите обработчики сфокусированными на HTTP, репозитории — на доступе к данным, а бизнес-логику — в пакетах сервисов.
-
Используйте
internal/для приватности: Используйте его для кода, который не должен импортироваться внешними системами. Бóльшая часть кода приложения должна находиться здесь. -
Применяйте архитектурные паттерны при необходимости: Для сложных систем Гексагональная архитектура и DDD обеспечивают четкие границы и тестируемость.
-
Позвольте Go направлять вас: Следуйте идиомам Go, а не импортируйте паттерны из других языков. У Go своя философия простоты и организации.
Полезные ссылки
- Шпаргалка по Go
- Создание REST API в Go: Полное руководство
- Создание CLI-приложений в Go с Cobra & Viper
- Модульное тестирование в Go: Структура и лучшие практики
- Структурированное логирование в Go с slog для наблюдаемости и оповещений
- Добавление Swagger в ваш Go API
- Обобщения в Go: Случаи использования и паттерны
- Сравнение ORM для PostgreSQL в Go: GORM против Ent против Bun против sqlc
Другие связанные статьи
- Стандартная структура проекта Go — Руководство по структуре проекта от сообщества
- Справочник по Go Modules — Официальная документация по модулям Go
- Гексагональная архитектура в Go — Фреймворк гексагональной архитектуры корпоративного уровня
- Domain-Driven Design в Go — Реализация DDD, готовая к продакшену
- Конвенции структуры проекта Go — Дополнительные паттерны и примеры
- Effective Go — Официальное руководство по лучшим практикам Go
- Центр архитектуры приложений — Дизайн API, структура кода и паттерны интеграции