Как бизнес-логика выбирает реализацию репозитория
Что проверяют
Правильный ответ начинается со слова «никак»: бизнес-логика не выбирает реализацию, она знает только интерфейс. Интервьюер проверяет, понимаете ли вы инверсию зависимостей в стиле Go и где в программе находится точка сборки. Следом часто спрашивают про транзакцию, которая затрагивает несколько репозиториев.
Как отвечать
- интерфейс у потребителя: в Go интерфейс объявляют в пакете, который его использует, а не рядом с реализацией. Сервис заказов описывает ровно нужные ему методы:
type OrderRepo interface { Get(ctx context.Context, id int64) (Order, error); Save(ctx context.Context, o Order) error }; - неявная реализация:
postgres.OrderRepoнигде не заявляет, что реализует интерфейс, достаточно совпадения методов. Проверку на этапе компиляции добавляют строкойvar _ service.OrderRepo = (*OrderRepo)(nil); - внедрение через конструктор:
NewOrderService(repo OrderRepo) *OrderService. Сервис получает зависимость готовой и конфиг не читает; - composition root: реализация выбирается в
mainили в отдельном пакете сборки приложения. По конфигу создаётсяpgxpool.Poolиpostgres.NewOrderRepo(pool), в тестах подставляется фейк в памяти или мок изmockgenлибоmockery; - инструменты: большинству проектов хватает ручной сборки в
main.wireгенерирует такой код на этапе компиляции,fxсобирает граф в рантайме.
Сильный ход: сказать про транзакции. Если операция пишет в два репозитория, транзакцию передают через context.Context или вводят TxManager с методом WithinTx(ctx, func(ctx context.Context) error) error, чтобы сервис не импортировал pgx.
Где спотыкаются
Пишут в сервисе if cfg.DB == "postgres" { repo = ... }. Бизнес-логика начинает знать о хранилищах, и каждая новая реализация требует правки сервиса.
Кладут интерфейс на двадцать методов рядом с реализацией в пакете repository. Потребителю нужно три, а мок приходится писать на все двадцать.
Возвращают из репозитория ошибки драйвера вроде pgx.ErrNoRows, и сервис начинает их проверять. Абстракция протекает: репозиторий должен переводить их в свои, например в ErrNotFound, а сервис проверять через errors.Is.