Как разделяют логику приложения по слоям в Go?
Что проверяют
Умеете ли вы строить приложение на Go так, чтобы бизнес-логика не зависела от HTTP и базы, и понимаете ли, где в Go проходят границы между слоями. Следом обычно спрашивают, где вы объявляете интерфейсы и как тестируете сервисный слой.
Как отвечать
Типичное деление на три слоя:
- транспорт (handler): разбирает запрос
net/http, gRPC или сообщение из очереди, валидирует вход, вызывает сервис и превращает результат в ответ с кодом. Бизнес-правил не содержит; - сервис (usecase): правила предметной области, оркестрация, границы транзакций. Принимает и возвращает доменные структуры, про JSON и SQL не знает;
- репозиторий (storage): работа с базой через
database/sql,pgxилиsqlc, маппинг строк в доменные типы.
Главная идиома Go: интерфейс объявляет потребитель, а не реализация. Сервис описывает у себя маленький UserRepo с одним-двумя методами, а пакет postgres просто реализует их без явного implements. Отсюда правило «принимайте интерфейсы, возвращайте структуры». Зависимости передаются через конструктор NewService(repo UserRepo, log *slog.Logger), а собирается граф в main, без DI-контейнера.
context.Context идёт первым аргументом через все слои. Ошибки заворачиваются через fmt.Errorf с %w, доменные ошибки вроде ErrNotFound объявляет сервис, а handler проверяет их через errors.Is и мапит в 404.
Код приложения кладут в internal/: компилятор запрещает импортировать его извне модуля. Каталог pkg/ нужен только для кода, который вы реально отдаёте наружу. Для тестов сервиса хватает ручной заглушки или сгенерированного мока через mockery.
Где спотыкаются
Объявляют интерфейс рядом с реализацией и сразу с двадцатью методами. Потребителю нужны один-два, а жирный интерфейс мешает подменять зависимость в тестах.
Протаскивают *http.Request или *sql.Tx в сервис. Тогда бизнес-логика снова завязана на транспорт и базу, и без них её не протестировать.
Переносят в Go привычки из Java: пакеты models, utils, interfaces, каталог на каждый слой. В Go циклические импорты запрещены компилятором, и группировать код по фиче обычно удобнее, чем по типу файла.