golang

Go-микросервисы: Стандартизация архитектуры с Clean Architecture и DDD

  • среда, 21 мая 2025 г. в 00:00:13
https://habr.com/ru/articles/911018/
Рисунок 0. Как я писал статью на Хабр
Рисунок 0. Как я писал статью на Хабр

Введение: Проблемы современных Go-проектов

В Go-экосистеме сложилась парадоксальная ситуация: при наличии множества руководств по структуре проектов, разработчики продолжают сталкиваться с системными проблемами:

  1. Проблема внутреннего монолита.
    Кажущаяся модульность разбивается о практику размещения всей логики в internal/, где:

    • 73% проектов смешивают доменную логику с инфраструктурой (данные CodeScene 2023).

    • Среднее время поиска нужного компонента превышает 15 минут.

  2. Инфраструктурная блокировка.
    Замена компонентов (БД, фреймворков) требует:

    • 200+ изменений кода при плохой структуре.

    • Всего 10-15 изменений при правильном разделении слоёв.

  3. Архитектурный дрейф.
    После 6 месяцев разработки:

    • 68% команд не могут чётко объяснить расположение компонентов

    • 54% проектов требуют рефакторинга (исследование SIG)


Теоретическая основа: DDD + Clean Architecture: Синтез подходов

Domain-Driven Design обеспечивает:

  • Чёткое выделение доменного ядра

  • Явное моделирование бизнес-процессов

  • Единый язык описания (Ubiquitous Language)

Рисунок 1. Как выглядят правила зависимостей.
Рисунок 1. Как выглядят правила зависимостей.

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/    # Конфигурация сервера

Критический анализ

Преимущества:

  • Полная инкапсуляция домена.

    • Тестирование без моков инфраструктуры.

    • Возможность верификации бизнес-правил изолированно.

  • Гибкость замены компонентов.

Рисунок 1. Иллюстрация работы адаптера.
Рисунок 1. Иллюстрация работы адаптера.
  • Автоматическая валидация архитектуры.
    Инструменты типа archunit-go могут проверять:

    • Запрет импортов из infrastructure в domain.

    • Корректность направлений зависимостей.

Недостатки и спорные моменты.

  1. Низкая плотность кода.

  2. Проблема распределённой логики.

    Валидация может находиться в:

    • domain/rules/

    • infrastructure/services/

    • interfaces/http/middleware/

  3. Порог входа?

    Мне, как ведущему разработчику, сложно его оценить, поэтому пока вопрос остается открытым.

Гексагональная архитектура.

Мне предложили взять некоторые идеи гексагональной архитектуры для более строгого разделения адаптеров от инфраструктуры. Возможно к обсуждению.

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

Исходный код.