handbookНаписать

lessons/002-extract-repository/step-1/README.md · тема · 4 языка · 23 теста

Шаг 1: SQL прямо в контроллере ​

Исходный код: контроллер заказов сам пишет SQL, разбирает ряды и решает, можно ли отменить заказ.

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

Что было ​

OrdersController получает Database и в каждом методе делает всё сам. create проверяет сумму, берёт nextId('orders') и пишет INSERT. show и cancel начинают с одного и того же SELECT * FROM orders WHERE id = ?. cancel смотрит на колонку status и решает, можно ли отменять, а потом пишет UPDATE. listByCustomer выбирает ряды по customer_id и склеивает их в строку.

Database - игрушка в памяти, которая понимает три формы SQL. Она одинаковая во всех шагах урока и в рефакторинге не участвует.

Что меняем ​

Пока ничего. Это точка отсчёта: код работает, и сначала его поведение фиксируется тестами. Тесты обращаются только к публичным методам контроллера и сверяют Response целиком, статус и текст. О SQL и рядах они ничего не знают, поэтому переживут любой рефакторинг под контроллером.

Сценариев пять: созданный заказ виден в show, нулевая сумма даёт 422, несуществующий заказ даёт 404 и в show, и в cancel, отменённый заказ второй раз не отменяется (409) и показывается как cancelled, список покупателя содержит только его заказы.

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

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

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

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

Ничего, но у этого кода есть честное достоинство: каждый метод читается сверху вниз, и видно, какой запрос уходит в базу. Для CRUD без правил так пишут часто, и это нормально.

Что ещё болит ​

  • Правило "отменить можно только новый заказ" спрятано между SELECT и UPDATE. Его нельзя переиспользовать в другом месте, например в фоновой задаче, без копирования SQL.
  • Запрос по id написан дважды, имена колонок повторяются в каждом методе. Переименовать колонку - пройти по всему контроллеру.
  • Контроллер знает форму ряда. В Go это видно лучше всего: rows[0]["status"].(string) и отдельная ветка на случай, если там не строка.
  • Тест контроллера невозможен без базы. Подменить её нечем: контроллер зависит от конкретной Database и от её языка запросов.

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

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