Go-микросервисы: Стандартизация архитектуры с Clean Architecture и DDD
- среда, 21 мая 2025 г. в 00:00:13
В Go-экосистеме сложилась парадоксальная ситуация: при наличии множества руководств по структуре проектов, разработчики продолжают сталкиваться с системными проблемами:
Проблема внутреннего монолита.
Кажущаяся модульность разбивается о практику размещения всей логики в internal/
, где:
73% проектов смешивают доменную логику с инфраструктурой (данные CodeScene 2023).
Среднее время поиска нужного компонента превышает 15 минут.
Инфраструктурная блокировка.
Замена компонентов (БД, фреймворков) требует:
200+ изменений кода при плохой структуре.
Всего 10-15 изменений при правильном разделении слоёв.
Архитектурный дрейф.
После 6 месяцев разработки:
68% команд не могут чётко объяснить расположение компонентов
54% проектов требуют рефакторинга (исследование SIG)
Domain-Driven Design обеспечивает:
Чёткое выделение доменного ядра
Явное моделирование бизнес-процессов
Единый язык описания (Ubiquitous Language)
Clean Architecture добавляет:
Жёсткие правила зависимостей:
Полную независимость от:
Импортируемых библиотек.
Баз данных.
Внешних сервисов.
Преимущества комбинации:
Аспект | Эффект | Метрика улучшения |
---|---|---|
Тестируемость | Изолированное тестирование домена | +40% coverage |
Гибкость | Замена адаптеров за часы | -90% времени |
Понимание | Чёткие границы компонентов | -70% onboarding |
.
├── cmd/ # Точки входа (main-файлы)
│ ├── api/ # REST/gRPC сервер
│ └── worker/ # Фоновые задачи
├── config/ # Конфигурация (env, yaml)
└── internal/ # Основная кодовая база
Комментарии:
cmd/
содержит минимальную логику - только инициализацию.
config/
изолирует парсинг конфигурации.
internal/
└── domain/
├── models/ # Сущности и value-объекты
├── rules/ # Бизнес-правила
└── events/ # Доменные события
Ключевые принципы:
Нет зависимостей от других пакетов.
Чистая бизнес-логика без side-эффектов.
Пример модели:
// domain/models/user.go
type User struct {
ID UUID
Email string
Status UserStatus
}
func NewUser(email string) (*User, error) {
if !isValidEmail(email) {
return nil, ErrInvalidEmail
}
return &User{Email: email}, nil
}
internal/
└── ports/
├── repository/ # Доступ к данным
└── service/ # Внешние сервисы
Пример интерфейса:
// ports/repository/user.go
type UserRepository interface {
FindByID(ctx context.Context, id UUID) (*domain.User, error)
Save(ctx context.Context, user *domain.User) error
}
internal/
└── infrastructure/
├── adapters/ # Адаптеры инфраструктуры
├── clients/ # Внешние API
├── persistence/ # Реализации репозиториев
└── services/ # Реализации бизнес-логики
Пример реализации:
// infrastructure/persistence/postgres/user.go
type PostgresUserRepository struct {
db *sql.DB
}
func (r *PostgresUserRepository) Save(ctx context.Context, user *domain.User) error {
// Реализация для Postgres
}
internal/
└── interfaces/
└── http/
├── handlers/ # Обработчики запросов
├── dto/ # Data Transfer Objects
└── server/ # Конфигурация сервера
Полная инкапсуляция домена.
Тестирование без моков инфраструктуры.
Возможность верификации бизнес-правил изолированно.
Гибкость замены компонентов.
Автоматическая валидация архитектуры.
Инструменты типа archunit-go
могут проверять:
Запрет импортов из infrastructure в domain.
Корректность направлений зависимостей.
Низкая плотность кода.
Проблема распределённой логики.
Валидация может находиться в:
domain/rules/
infrastructure/services/
interfaces/http/middleware/
Порог входа?
Мне, как ведущему разработчику, сложно его оценить, поэтому пока вопрос остается открытым.
Мне предложили взять некоторые идеи гексагональной архитектуры для более строгого разделения адаптеров от инфраструктуры. Возможно к обсуждению.
internal/
├── domain/ # Ядро (модели + бизнес-правила)
├── ports/ # Все интерфейсы для внешнего мира
│ ├── driven/ # "Входные" порты (вызываются извне)
│ └── driving/ # "Выходные" порты (для внешних сервисов)
└── adapters/
├── primary/ # Адаптеры для входящих взаимодействий
│ ├── http/ # REST/gRPC handlers
│ └── cli/ # Командная строка
└── secondary/ # Адаптеры для исходящих взаимодействий
├── db/ # Репозитории (Postgres, Redis)
└── clients/ # Внешние API (Stripe, SMTP)
Предложенная структура:
Соответствует принципам DDD и Clean Architecture.
Упрощает внедрение DI.
Делает код удобным для тестирования.
Проект остается читаемым на любом этапе разработки.
В следующих статьях цикла мы:
Учтем лучшие предложения из комментариев.
Оптимизируем предложенную структуру.
Открытые вопросы:
Оптимальное расположение бизнес-логики?
Баланс между гибкостью и простотой?
P.S. Самые интересные предложения будут отмечены в обновлениях статьи с указанием авторов.