handbookНаписать

lessons/002-extract-repository/step-3/README.md · тема · 4 языка · 28 тестов

Шаг 3: репозиторий в домене ​

Заказ становится сущностью Order с правилом отмены, доступ к заказам - интерфейсом OrderRepository, а SQL уходит в одну из его реализаций.

Урок: Выделение репозитория. Предыдущий шаг: шлюз таблицы.

Что было ​

SQL собран в OrdersTable, но контроллер получает от него ряды, знает имена колонок и сам решает, можно ли отменить заказ. Тестировать контроллер можно только вместе с базой.

Что меняем ​

Три перемены, каждая под зелёными тестами.

Сущность. Order с полями id, customerId, totalCents и статусом. Сумма проверяется при создании, отмена - метод cancel(), который бросает OrderCannotBeCancelled (в Go возвращает ErrCannotCancel), если заказ уже не новый. Правило, которое раньше пряталось между SELECT и UPDATE, теперь читается в четыре строки.

Интерфейс в домене. OrderRepository описывает коллекцию заказов на языке задачи: nextId, get, save, forCustomer. В нём нет ни SQL, ни рядов, ни Database. Интерфейс принадлежит домену: его форму диктует то, что нужно контроллеру, а не то, что умеет база.

Две реализации. SqlOrderRepository - бывший шлюз, который теперь переводит ряды в Order в одном месте (toOrder) и обратно в save: есть ряд - UPDATE, нет - INSERT. InMemoryOrderRepository хранит копии заказов в памяти, чтобы изменения без save не протекали в хранилище, как и в настоящей базе.

Контроллер зависит только от интерфейса. Он достаёт заказ, просит его отмениться, сохраняет и переводит исключения домена в HTTP-статусы: 422 и 409.

Как это собирается ​

Composition root - единственное место, которое знает, что репозиторий SQL: Database, над ней SqlOrderRepository, над ним OrdersController. В приложении это контейнер или main. Тесты собирают контроллер с любой реализацией: каждый сценарий прогоняется дважды, с памятью и с SQL, и ответы обязаны совпасть. Так реализация в памяти не может тихо разойтись с настоящей.

Сценарий целиком с выводом: пример на PHP, он же на Go, Python и TypeScript в панели кода. Вывод тот же, что на первом шаге.

Что стало лучше ​

  • Контроллер не знает ни SQL, ни колонок, ни рядов. Его методы - это сценарий: достать, изменить, сохранить, ответить.
  • Правило отмены живёт в Order и работает одинаково из контроллера, консольной команды или обработчика очереди.
  • Контроллер тестируется с InMemoryOrderRepository: без базы, миграций и контейнеров.
  • В Go все приведения any собраны в toOrder. Остальной код работает с типизированным Order.

Итог ​

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

Цена тоже видна: вместо одного класса четыре (сущность, интерфейс, две реализации), а save теперь делает лишний SELECT, чтобы выбрать между INSERT и UPDATE. В настоящей базе это решает upsert или Unit of Work, который помнит, какие объекты новые. Если правил в домене нет, остановиться на втором шаге честнее.

Подробнее о приёме: Репозиторий.

Запуск: make test T=lessons/002-extract-repository/step-3

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