handbookНаписать

Выделение репозитория (Extract Repository) ​

Контроллер заказов пишет SQL сам. За три шага SQL уезжает в шлюз таблицы, а потом за интерфейс репозитория, и контроллер начинает тестироваться без базы.

Исходная задача ​

Небольшой API заказов: создать заказ, показать его, отменить и показать список заказов покупателя. Отменить можно только новый заказ. Ответ контроллера - Response со статусом и текстом: 201 order 1 created, 404 order 7 not found, 409 order 1 is cancelled, cannot cancel.

Базой служит игрушечная Database в памяти: таблицы - списки рядов, query понимает ровно три формы SQL (SELECT ... WHERE col = ?, INSERT, UPDATE ... SET col = ? WHERE col = ?) и nextId как последовательность. В приложении на её месте PDO, database/sql или драйвер, но для урока важно одно: это внешняя система со строковым языком запросов и рядами без типов на выходе. Код Database одинаковый во всех шагах, меняется только то, что над ней.

Что болит ​

  • В контроллере три слоя сразу: HTTP-ответы, SQL-строки и бизнес-правило "отменить можно только новый заказ". Правило нельзя прочитать, не продираясь через запросы.
  • Один и тот же SELECT * FROM orders WHERE id = ? написан дважды, а имена колонок (total_cents, customer_id) разбросаны по всем методам.
  • Контроллер работает с рядами: словарь колонка - значение. В Go это map[string]any и приведение типов в каждом методе.
  • Проверить контроллер можно только вместе с базой. Здесь база игрушечная и быстрая, в проекте это контейнер с Postgres и медленные тесты.

План ​

  1. Шаг 1: SQL прямо в контроллере - фиксируем поведение тестами, ничего не меняя.
  2. Шаг 2: шлюз таблицы - все SQL-строки уезжают в OrdersTable, контроллер зовёт её методы, но всё ещё получает ряды.
  3. Шаг 3: репозиторий в домене - появляется сущность Order с правилом отмены и интерфейс OrderRepository; SQL-реализация и реализация в памяти взаимозаменяемы, контроллер тестируется с любой.

Тесты во всех шагах одни и те же: пять сценариев (создать и показать, отказ на нулевую сумму, 404, отмена только нового заказа, список по покупателю) с одинаковыми ожидаемыми ответами. Меняется только строка, которая собирает контроллер. На третьем шаге те же сценарии прогоняются дважды, с каждым репозиторием.

Итог ​

Контроллер стал переводчиком между HTTP и доменом: достал заказ, попросил его отмениться, сохранил, ответил. SQL живёт в SqlOrderRepository, правило - в Order, а интерфейс OrderRepository говорит на языке задачи: get, save, forCustomer, а не SELECT и UPDATE. Реализация в памяти делает тесты контроллера быстрыми, а общие сценарии для двух реализаций следят, чтобы она не врала.

Второй шаг сам по себе тоже полезен: если правил в домене почти нет и контроллер просто перекладывает ряды в JSON, шлюза таблицы хватает и до репозитория можно не доходить.

Подробнее о приёме: Репозиторий. Как ряды превращаются в объекты, когда маппинг становится сложным: Преобразователь данных.

© 2014-2026 Тимур ТурдыевСайт и справочник работают на VPS от AdminVPS