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. Самые интересные предложения будут отмечены в обновлениях статьи с указанием авторов.