Цели модуля
После модуля студент сможет:
- Различать основные архитектурные стили организации Go-проектов
- Понимать плюсы и минусы каждого подхода
- Осознанно выбирать архитектуру под конкретную задачу
- Объяснять Dependency Rule и направление зависимостей
- Рефакторить flat-проект в слоистую архитектуру
- Обосновывать архитектурный выбор перед командой
2.1 Flat structure
Что это
Все файлы в одной директории или в минимальной структуре. Нет разделения на слои.
Пример
textmyapp/ ├── main.go ├── handlers.go ├── models.go ├── db.go ├── utils.go ├── go.mod └── Makefile
Когда использовать
- CLI-утилиты (один файл или несколько)
- Маленькие скрипты и инструменты
- Прототипы и эксперименты
- Проекты до 500 строк кода
Плюсы
- Максимальная простота
- Не нужно думать о структуре
- Легко начать
Минусы
- Не масштабируется
- Всё связано со всем
- Сложно тестировать
- Невозможно разделить ответственность
- Новый разработчик теряется
Когда пора менять
Сигналы что flat уже не работает:
- Файлы больше 300 строк
- Появились взаимозависимости между "модулями"
- Тесты требуют запуска всей системы
- Два разработчика конфликтуют в одном файле
2.2 Layered architecture (слоистая)
Что это
Код разделён на слои по ответственности. Каждый слой зависит только от слоя ниже.
Классические слои
textHandlers (транспорт) | Services (бизнес-логика) | Repositories (доступ к данным) | Database
Пример структуры
textmyapp/ ├── cmd/ │ └── app/ │ └── main.go ├── handlers/ │ ├── repo_handler.go │ └── user_handler.go ├── services/ │ ├── repo_service.go │ └── user_service.go ├── repositories/ │ ├── repo_repository.go │ └── user_repository.go ├── models/ │ ├── repo.go │ └── user.go ├── go.mod └── Makefile
Направление зависимостей
texthandlers -> services -> repositories -> database
Каждый слой знает только о слое ниже. Handlers не знают о repositories.
Когда использовать
- Небольшие API сервисы (до 10-15 эндпоинтов)
- Команда 1-3 разработчика
- Простая бизнес-логика
- Одна база данных
Плюсы
- Понятная структура
- Разделение ответственности
- Легко тестировать сервисы (мок репозитория)
- Стандартный паттерн, все знают
Минусы
- Сервисы знают о конкретных репозиториях (не об интерфейсах)
- Сложно менять инфраструктуру
- При росте бизнеса сервисы распухают
- Нет чёткой границы домена
Пример кода
go// handlers/repo_handler.go type RepoHandler struct { service *services.RepoService // конкретный тип, не интерфейс } // services/repo_service.go type RepoService struct { repo *repositories.RepoRepository // конкретный тип } // repositories/repo_repository.go type RepoRepository struct { db *sql.DB }
Проблема: если захотим поменять PostgreSQL на MySQL, придётся менять сервисы. В Clean Architecture это решается интерфейсами.
2.3 Clean Architecture
Что это
Архитектура предложенная Robert C. Martin (Uncle Bob). Ключевая идея: зависимости направлены внутрь, к домену.
Круги Clean Architecture
text┌─────────────────────────────────────────────┐ │ Frameworks & Drivers (внешний слой) │ │ HTTP, DB, UI, External Services │ ├─────────────────────────────────────────────┤ │ Interface Adapters │ │ Controllers, Gateways, Presenters │ ├─────────────────────────────────────────────┤ │ Application Business Rules │ │ Use Cases │ ├─────────────────────────────────────────────┤ │ Enterprise Business Rules (ядро) │ │ Entities (Domain Models) │ └─────────────────────────────────────────────┘
Dependency Rule
Зависимости направлены только внутрь. Внешние слои зависят от внутренних, но не наоборот.
Внутренние слои не знают о внешних:
- Domain Models не знают о HTTP, DB, фреймворках
- Use Cases не знают о конкретных реализациях
- Сервисы не знают о PostgreSQL, Redis, Kafka
Структура в шаблоне проекта
textinternal/ ├── models/ # Доменные модели (ядро) ├── repository/ # Интерфейсы (абстракции) ├── service/ # Бизнес-логика ├── usecase/ # Use Cases (сценарии) ├── adapters/ # Реализации интерфейсов │ ├── postgres/ # PostgreSQL adapter │ ├── http_client/ # HTTP client adapter │ └── scheduler/ # Cron adapter ├── handlers/ # HTTP handlers (внешний слой) │ ├── http/ │ └── jobs/ └── app/ # DI и сборка
Направление зависимостей в шаблоне
texthandlers/http (внешний слой) | вызывает v usecase (бизнес-сценарии) | вызывает v service (бизнес-логика) | вызывает v repository (интерфейсы) | реализуется v adapters/postgres (реализации)
Ключевое отличие от Layered
В Layered сервисы зависят от конкретных репозиториев:
go// Layered: сервис знает о конкретной реализации type RepoService struct { repo *postgres.RepoRepository }
В Clean Architecture сервисы зависят от интерфейсов:
go// Clean Architecture: сервис знает только об интерфейсе type RepoService struct { repo repository.RepoRepository // интерфейс }
Это позволяет:
- Подставить mock в тестах без реальной БД
- Поменять PostgreSQL на MySQL без изменения сервисов
- Добавить кэш перед репозиторием
Когда использовать
- Средние и большие сервисы (10+ эндпоинтов)
- Команда 3+ разработчика
- Сложная бизнес-логика
- Нужно тестировать без реальной инфраструктуры
- Планируется эволюция инфраструктуры
Плюсы
- Тестируемость: mock вместо реальной БД
- Гибкость: легко менять инфраструктуру
- Разделение: бизнес-логика изолирована
- Масштабируемость: можно расти
Минусы
- Больше кода (интерфейсы, DTO, мапперы)
- Сложнее для простых проектов
- Нужна дисциплина от команды
- Кривая обучения
2.4 Hexagonal Architecture (Ports and Adapters)
Что это
Архитектура предложенная Alistair Cockburn. Близка к Clean Architecture, но акцент на портах и адаптерах.
Ключевые концепции
Port (порт) - интерфейс определяющий что сервис делает. Adapter (адаптер) - реализация подключающая конкретную технологию.
Два типа портов:
- Primary (Driving): кто вызывает сервис (HTTP handler, CLI, gRPC)
- Secondary (Driven): что сервис вызывает (БД, очередь, внешний API)
Визуализация
textPrimary Adapters Application Secondary Adapters (Driving) (Core) (Driven) HTTP Handler ──┐ ┌── PostgreSQL Adapter CLI Command ──┼──> Ports ──┼── Redis Adapter gRPC Server ──┘ & Core └── Kafka Adapter
Структура
textmyapp/ ├── cmd/ │ └── app/ │ └── main.go ├── core/ │ ├── domain/ │ │ ├── repo.go │ │ └── user.go │ ├── ports/ │ │ ├── in/ # Primary ports (входящие) │ │ │ ├── create_repo.go │ │ │ └── get_repo.go │ │ └── out/ # Secondary ports (исходящие) │ │ ├── repo_repository.go │ │ └── event_publisher.go │ └── services/ │ └── repo_service.go ├── adapters/ │ ├── in/ │ │ ├── http/ │ │ └── grpc/ │ └── out/ │ ├── postgres/ │ └── kafka/ ├── go.mod └── Makefile
Отличие от Clean Architecture
Clean Architecture и Hexagonal очень близки. Разница в акцентах:
| Аспект | Clean Architecture | Hexagonal |
|---|---|---|
| Акцент | Слои и зависимости | Порты и адаптеры |
| Граница | Между слоями | Между ядром и внешним миром |
| Терминология | Use Cases, Entities | Ports, Adapters |
| Когда использовать | Большие системы | Сервисы с множеством интеграций |
На практике в Go проектах термины часто используются как синонимы.
Когда использовать
- Сервис с множеством интеграций (HTTP, gRPC, очередь, БД)
- Нужно легко менять технологии
- Сложная доменная логика
- Высокие требования к тестируемости
2.5 Modular Monolith
Что это
Одно приложение (один деплой), но код разделён на независимые модули по доменным областям. Каждый модуль имеет чёткие границы.
Структура
textmyapp/ ├── cmd/ │ └── app/ │ └── main.go ├── modules/ │ ├── repos/ │ │ ├── handler.go │ │ ├── service.go │ │ ├── repository.go │ │ ├── model.go │ │ └── dto.go │ ├── users/ │ │ ├── handler.go │ │ ├── service.go │ │ ├── repository.go │ │ ├── model.go │ │ └── dto.go │ └── billing/ │ ├── handler.go │ ├── service.go │ ├── repository.go │ ├── model.go │ └── dto.go ├── shared/ │ ├── database/ │ ├── middleware/ │ └── config/ ├── go.mod └── Makefile
Правила Modular Monolith
- Модули не импортируют внутренние пакеты друг друга
- Взаимодействие только через публичные интерфейсы модуля
- Каждый модуль имеет свою таблицу в БД
- Shared-код минимален и стабилен
Когда использовать
- Один продукт, одна команда (до 10 человек)
- Несколько доменных областей
- Не нужно независимое масштабирование
- Простота деплоя критична
Плюсы
- Один деплой (просто)
- Нет сетевых вызовов между модулями (быстро)
- Легко рефакторить (один репозиторий)
- Транзакции между модулями возможны
- Можно эволюционировать в микросервисы
Минусы
- Один процесс - одна точка отказа
- Нет независимого масштабирования
- Требует дисциплины для соблюдения границ
- Может вырасти в Big Ball of Mud без контроля
Эволюция в микросервисы
Modular Monolith - хорошая стартовая точка. При необходимости модуль можно вынести в отдельный микросервис:
textШаг 1: Modular Monolith (все модули в одном процессе) Шаг 2: Выносим модуль billing в отдельный сервис Шаг 3: Заменяем прямой вызов на HTTP/gRPC Шаг 4: Разделяем базу данных
2.6 Microservices
Что это
Каждый сервис - отдельное приложение со своей базой данных. Сервисы общаются через сеть (HTTP, gRPC, очередь).
Структура
textrepo-service/ ├── cmd/app/main.go ├── internal/... ├── go.mod └── Makefile user-service/ ├── cmd/app/main.go ├── internal/... ├── go.mod └── Makefile billing-service/ ├── cmd/app/main.go ├── internal/... ├── go.mod └── Makefile
Когда использовать
- Несколько автономных команд
- Нужно независимое масштабирование
- Разные технологические требования
- Организационная необходимость
Плюсы
- Независимый деплой и масштабирование
- Автономия команд
- Можно использовать разные технологии
- Изоляция сбоев
Минусы
- Операционная сложность (мониторинг, логи, трейсинг)
- Сетевые вызовы (latency, надёжность)
- Распределённые транзакции (Saga)
- Сложность тестирования
- Data consistency (eventual consistency)
Правило
Не начинайте с микросервисов. Начните с Modular Monolith. Выносите в микросервисы когда есть реальная необходимость.
"Microservices are the destination, not the starting point."
2.7 Сравнение архитектур
| Архитектура | Когда использовать | Плюсы | Минусы | Сложность |
|---|---|---|---|---|
| Flat | CLI, утилиты, прототипы | Простота | Не масштабируется | Низкая |
| Layered | Небольшие API | Понятность | Связность слоёв | Средняя |
| Clean Architecture | Средние/большие сервисы | Тестируемость, гибкость | Больше кода | Высокая |
| Hexagonal | Множество интеграций | Изоляция от технологий | Избыточность для простых | Высокая |
| Modular Monolith | Один продукт, одна команда | Простота деплоя | Одна точка отказа | Средняя |
| Microservices | Автономные команды, масштабирование | Независимость | Операционная сложность | Очень высокая |
2.8 Критерии выбора
Факторы выбора архитектуры
-
Размер команды
- 1-2 человека: Flat или Layered
- 3-5 человек: Layered или Clean Architecture
- 5-15 человек: Clean Architecture или Modular Monolith
- 15+ человек: Microservices (если обосновано)
-
Сложность домена
- Простой CRUD: Layered
- Средняя бизнес-логика: Clean Architecture
- Сложный домен с множеством правил: Clean Architecture / Hexagonal
-
Требования к масштабированию
- Один инстанс: Modular Monolith
- Независимое масштабирование компонентов: Microservices
-
Требования к тестируемости
- Минимальные: Flat / Layered
- Высокие (mock без БД): Clean Architecture / Hexagonal
-
Операционные возможности
- Нет DevOps: Modular Monolith
- Есть инфраструктура: Microservices допустимы
Таблица решений
| Ситуация | Рекомендация |
|---|---|
| CLI утилита | Flat |
| Прототип | Flat или Layered |
| Маленький API (до 5 эндпоинтов) | Layered |
| Средний API (5-20 эндпоинтов) | Clean Architecture |
| Большой API (20+ эндпоинтов) | Clean Architecture |
| Один продукт, одна команда | Modular Monolith |
| Несколько команд, независимый деплой | Microservices |
| Множество интеграций | Hexagonal |
Антипаттерны выбора
- Resume Driven Development: выбор технологии ради резюме
- Microservices First: микросервисы с первого дня без необходимости
- Over-engineering: Clean Architecture для простого CRUD
- Under-engineering: Flat для сложного бизнеса
Привязка к шаблону проекта
Архитектура шаблона: Clean Architecture
Шаблон проекта использует Clean Architecture. Это обосновано:
- Средний размер сервиса (10+ эндпоинтов)
- Нужна тестируемость без реальной инфраструктуры
- Планируется эволюция (Serverless -> VDS -> Kubernetes)
- Несколько адаптеров (PostgreSQL, NFS, S3, Lockbox)
Структура шаблона с комментариями
textinternal/ ├── models/ # Ядро: доменные модели (Repo, User, PullRequest) ├── repository/ # Абстракции: интерфейсы репозиториев ├── service/ # Бизнес-логика: правила домена ├── usecase/ # Сценарии: CreateRepo, MergePullRequest ├── adapters/ # Инфраструктура: postgres, nfs, s3, lockbox ├── handlers/ # Транспорт: HTTP, jobs ├── observability/ # Кросс-каттинг: логи, метрики, трейсинг ├── security/ # Кросс-каттинг: HMAC, секреты ├── config/ # Кросс-каттинг: конфигурация └── app/ # Сборка: DI-контейнер
Задание: определите слои
Для каждого файла шаблона определите слой Clean Architecture:
- internal/models/repo.go -> ?
- internal/repository/repo_repository.go -> ?
- internal/service/repo_service.go -> ?
- internal/usecase/create_repo_usecase.go -> ?
- internal/adapters/postgres/repo_repository.go -> ?
- internal/handlers/http/api/v1/handler.go -> ?
Ответы:
- Domain Models (ядро)
- Interface Adapters (абстракция)
- Application Business Rules
- Application Business Rules
- Frameworks & Drivers (внешний слой)
- Frameworks & Drivers (внешний слой)
💱 Компромиссы
- Flat vs Layered:
flatидеален для CLI и прототипов до 500 строк, но всё связано со всем и тесты требуют всей системы; слои дают разделение ответственности и мок репозитория ценой каталоговhandlers/,services/,repositories/и рефакторинга по сигналу «файлы больше 300 строк». - Layered vs Clean Architecture: в слоях сервис зависит от конкретного
*postgres.RepoRepository— смена БД бьёт по бизнес-логике; Clean развязывает их интерфейсомrepository.RepoRepository, но платит интерфейсами, DTO и мапперами — больше кода и дисциплины от команды. - Hexagonal vs Clean Architecture: различие только в акцентах: явные
порты
in//out/окупаются при десятке интеграций (HTTP, gRPC, Kafka, PostgreSQL); для CRUD с одной БД эти слои абстракции избыточны. - Modular Monolith vs Microservices: монолит — один деплой, вызовы
функций и транзакции между модулями
repos/,users/,billing/, но один процесс — одна точка отказа; микросервисы дают независимый деплой и масштабирование, но платят сетевыми вызовами, Saga и eventual consistency. - Over-engineering vs Under-engineering: Clean Architecture для простого CRUD — это Resume Driven Development и раздутый бойлерплейт; Flat для сложного бизнеса не масштабируется и усложняет тестирование.
Практическое задание
Задание 1: Рефакторинг Flat -> Layered
Дан flat-проект:
textmyapp/ ├── main.go # всё в одном файле └── go.mod
Рефакторите в Layered:
textmyapp/ ├── cmd/app/main.go ├── handlers/ ├── services/ ├── repositories/ └── models/
Задание 2: Рефакторинг Layered -> Clean Architecture
Дан Layered-проект. Рефакторите в Clean Architecture:
- Вынесите интерфейсы в internal/repository/
- Реализации в internal/adapters/
- Добавьте usecase слой
- Настройте DI
Задание 3: Сравнение подходов
Для каждой ситуации выберите архитектуру и обоснуйте:
- CLI-утилита для конвертации файлов
- API для блога (5 эндпоинтов, один разработчик)
- API для e-commerce (50 эндпоинтов, 5 разработчиков)
- Система из 3 команд, каждая владеет своим доменом
- Сервис с интеграциями: PostgreSQL, Kafka, S3, внешний API
Задание 4: Dependency Rule
Нарисуйте диаграмму зависимостей для шаблона проекта. Проверьте что нет нарушений Dependency Rule:
- models не импортируют service
- service не импортирует adapters
- usecase не импортирует handlers
Задание 5: Обоснование выбора
Напишите ADR (Architecture Decision Record) для выбора архитектуры вашего проекта:
text# ADR-001: Выбор Clean Architecture ## Статус Accepted ## Контекст - Размер команды: 4 человека - Количество эндпоинтов: 15 - Нужна тестируемость без БД - Планируется миграция Serverless -> VDS ## Решение Используем Clean Architecture потому что... ## Альтернативы - Layered: проще, но не даёт тестируемости - Microservices: избыточно для одной команды ## Последствия - Больше boilerplate кода - Легче менять инфраструктуру - Можно тестировать без реальной БД
Quiz "Проверь себя"
type RepoRepository interface {
GetByID(ctx context.Context, id string) (*models.Repo, error)
}type RepoService struct {
repo *postgres.RepoRepository
}✅ Итоги модуля
Студент должен:
- Различать 6 архитектурных стилей
- Объяснять Dependency Rule
- Осознанно выбирать архитектуру под задачу
- Рефакторить между стилями
- Обосновывать выбор через ADR
- Понимать структуру шаблона проекта
- Ответить на все вопросы Quiz
🔗 Мостик к Модулю 3
Следующий модуль: Модуль 3. Clean Architecture Deep Dive (ключевой модуль) - Dependency Inversion, Use Cases vs Services, DTO vs Domain Models, DI-контейнер, путь запроса через все слои. Привязка к internal/usecase/, internal/service/, internal/repository/, internal/adapters/ шаблона.