Введение: Проблемы современных Go-проектов
В Go-экосистеме сложилась парадоксальная ситуация: при наличии множества руководств по структуре проектов, разработчики продолжают сталкиваться с системными проблемами:
-
Проблема внутреннего монолита.
Кажущаяся модульность разбивается о практику размещения всей логики вinternal/, где:-
73% проектов смешивают доменную логику с инфраструктурой (данные CodeScene 2023).
-
Среднее время поиска нужного компонента превышает 15 минут.
-
-
Инфраструктурная блокировка.
Замена компонентов (БД, фреймворков) требует:-
200+ изменений кода при плохой структуре.
-
Всего 10-15 изменений при правильном разделении слоёв.
-
-
Архитектурный дрейф.
После 6 месяцев разработки:-
68% команд не могут чётко объяснить расположение компонентов
-
54% проектов требуют рефакторинга (исследование SIG)
-
Теоретическая основа: DDD + Clean Architecture: Синтез подходов
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. Самые интересные предложения будут отмечены в обновлениях статьи с указанием авторов.
ссылка на оригинал статьи https://habr.com/ru/articles/911018/
Добавить комментарий