Clean architecture в Go: границы и интерфейсы

О лекции

Сущности и сценарии не знают HTTP и SQL: зависимости направлены к центру. В Go интерфейс объявляет потребитель на границе, а не «у каждой структуры пакет с моком».

Проектирование сервисов architecture собеседование

Чистая архитектура, гексагональная, ports and adapters — один рисунок разными словами. В центре домен и сценарии. Снаружи адаптеры: HTTP, Postgres, Kafka, CLI.

Правило зависимостей: внутренние пакеты не импортируют фреймворки и драйверы. В Go это видно по графу import.

Зачем это бэкенду: сценарий «оформить заказ» тестируется без Docker и без gin. Хендлер остаётся тонким: разобрать запрос, вызвать use case, перевести ошибку в статус.

Пример. Смену БД или брокера не размазывают по двенадцати хендлерам. Меняете адаптер. Сценарий PlaceOrder остаётся тем же.

Правило зависимостей

Use case зависит от порта «сохранить заказ», не от *sql.DB. Реализация порта живёт в adapter и импортирует домен, а не наоборот.

HTTP-хендлер не содержит SQL и не знает про колонки. Миграции и драйвер — детали инфраструктуры.

Тест сценария подставляет фейковый порт в памяти. Тест адаптера бьёт в Postgres и не дублирует всю бизнес-логику. Путать эти два уровня — источник медленных «юнитов», которые поднимают Docker.

Как это выглядит в Go

Интерфейс объявляет потребитель. Пакет use case, рядом со структурой сценария. Пакет postgres реализует его неявно.

Не тащите большой интерфейс «весь склад» в домен, если сценарию нужны два метода. Один адаптер навсегда без теста — лишний слой.

package order

import "context"

type Repository interface {
	Save(ctx context.Context, o Order) error
	ByID(ctx context.Context, id ID) (Order, error)
}

type Place struct{ Repo Repository }

func (p Place) Do(ctx context.Context, o Order) error {
	if err := o.Validate(); err != nil {
		return err
	}
	return p.Repo.Save(ctx, o)
}

Порт объявлен у сценария. Адаптер реализует его снаружи и не протекает в домен.

Толстый хендлер

SQL внутри HTTP-хендлера смешивает транспорт с правилами домена. Протестировать «нельзя оформить пустой заказ» без сервера и без БД уже нельзя. gRPC-хендлер с той же болезнью не лучше: транспорт всё равно не место для инварианта.

Типичная ошибка

Объявить интерфейс в пакете postgres на 50 методов заранее и заставить домен импортировать инфраструктуру. Это инверсия правила зависимостей. Вторая крайность — абстракция ради абстракции: один адаптер, ноль тестов, три слоя DTO на каждый запрос.

На собеседовании

Зависимости к центру. Интерфейс живёт у потребителя. Хендлер тонкий. Сценарий тестируется фейковым портом. SQL остаётся в адаптере. Hexagonal и ports-and-adapters — тот же рисунок.