Еще один вариант архитектуры проекта на Go
Подход к организации сервисов с разделением на уровни ответственности
Общая структура проекта
1
2
3
4
5
6
7
8
9
cmd/ # точка входа в приложение
config/ # конфигурация
internal/ # основная бизнес-логика
entities/ # доменные сущности и интерфейсы
usecases/ # сценарии (application layer)
infrastructure/ # адаптеры, работа с БД, внешние сервисы
interfaces/ # входные интерфейсы (HTTP, gRPC, CLI и т.д.)
services/ # вспомогательные пакеты (опционально)
pkg/ # переиспользуемые библиотеки
Разбор по директориям
cmd/ — точка инициализации приложения
Здесь находятся исполняемые пакеты. Каждый сервис/микросервис обычно имеет свою поддиректория:
1
2
3
cmd/
app/ # main.go — запуск API
worker/ # main.go — запуск воркера
Внутри — только инициализация зависимостей и запуск. Бизнес-логики здесь быть не должно.
config/ — конфигурация
Хранение yaml/json файлов, переменных окружения, а также структуры для конфигов.
Обычно используется вместе с библиотеками вроде viper или envconfig.
internal/entities/ — доменные сущности
Содержит чистые структуры и интерфейсы, описывающие предметную область.
Пример:
1
2
3
4
5
6
7
// internal/entities/team.go
package entities
type Team struct {
ID int64
Name string
}
Тут нет логики работы с БД или HTTP — только модели и интерфейсы.
internal/usecases/ — бизнес-правила и сценарии
Здесь описываются интерфейсы взаимодействия между сущностями и реализация сценариев.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// internal/usecases/team_usecase.go
package usecases
import "myapp/internal/entities"
type TeamRepository interface {
Create(team entities.Team) error
GetByID(id int64) (*entities.Team, error)
}
type TeamUseCase struct {
repo TeamRepository
}
func NewTeamUseCase(repo TeamRepository) *TeamUseCase {
return &TeamUseCase{repo: repo}
}
func (uc *TeamUseCase) RegisterTeam(name string) (*entities.Team, error) {
team := entities.Team{Name: name}
if err := uc.repo.Create(team); err != nil {
return nil, err
}
return &team, nil
}
UseCase не знает, где хранятся данные (БД, память, внешний сервис) и как именно приходит запрос (HTTP, gRPC).
internal/infrastructure/ — реализация адаптеров
Тут лежат конкретные реализации интерфейсов для работы с внешними системами:
- Репозитории для БД (PostgreSQL, Redis и т.д.)
- Адаптеры для сторонних API
- Логгеры, очереди, кэш
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// internal/infrastructure/postgres/team_repo.go
package postgres
import (
"database/sql"
"myapp/internal/entities"
"myapp/internal/usecases"
)
type TeamRepo struct {
db *sql.DB
}
func NewTeamRepo(db *sql.DB) *TeamRepo {
return &TeamRepo{db: db}
}
var _ usecases.TeamRepository = (*TeamRepo)(nil)
func (r *TeamRepo) Create(team entities.Team) error {
_, err := r.db.Exec(`INSERT INTO teams (name) VALUES ($1)`, team.Name)
return err
}
func (r *TeamRepo) GetByID(id int64) (*entities.Team, error) {
row := r.db.QueryRow(`SELECT id, name FROM teams WHERE id = $1`, id)
var team entities.Team
if err := row.Scan(&team.ID, &team.Name); err != nil {
return nil, err
}
return &team, nil
}
internal/interfaces/ — внешние интерфейсы
Слой взаимодействия с пользователем или другими сервисами: HTTP, gRPC, CLI.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// internal/interfaces/http/handlers/team_handler.go
package http
import (
"encoding/json"
"net/http"
"myapp/internal/usecases"
)
type TeamHandler struct {
uc *usecases.TeamUseCase
}
func NewTeamHandler(uc *usecases.TeamUseCase) *TeamHandler {
return &TeamHandler{uc: uc}
}
func (h *TeamHandler) CreateTeam(w http.ResponseWriter, r *http.Request) {
var req struct{ Name string }
_ = json.NewDecoder(r.Body).Decode(&req)
team, err := h.uc.RegisterTeam(req.Name)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(team)
}
internal/services/ (опционально)
Используется, если между usecases и инфраструктурой нужно добавить слой вспомогательной логики (например, сервис валидации, работа с кэшем).
pkg/ — переиспользуемые библиотеки
Здесь могут находиться хелперы, утилиты или пакеты, которые теоретически можно вынести в отдельный репозиторий и использовать в других проектах.
Например: кастомный логгер, middleware для HTTP, библиотека для работы с конфигами.
Преимущества подхода
- Разделение ответственности — код проще читать и поддерживать.
- Тестируемость — usecases довольно просто покрыть unit-тестами, подменяя интерфейсы.
- Гибкость — можно менять БД или протокол общения (HTTP → gRPC) без переписывания остальной логики.
- Масштабируемость — структура подходит как для небольших сервисов, так и для больших систем.
Итог
Такая структура помогает строить Go-приложения по некоторым из принципов чистой архитектуры и DDD. Она подходит для проектов, где важна долгосрочная поддержка и развитие.
