Нужно ли выносить бизнес-логику из endpoint в отдельный слой и почему это важно
Что проверяют
Понимаете ли вы, зачем нужен слой, и способны ли отличить случай, когда он окупается, от случая, когда это лишние файлы. Догматичный ответ «всегда выносить, так учит чистая архитектура» ценится не выше, чем вся логика во вьюхе.
Как отвечать
Начните с распределения ролей. Эндпоинт занимается протоколом: разбирает и валидирует вход, проверяет права, вызывает сценарий, превращает результат в схему ответа и код. Сценарий живёт в сервисном слое и ничего не знает про HTTP, работа с хранилищем спрятана за репозиторием.
@router.post("/orders")
def create_order(body: OrderIn, uow: UnitOfWork = Depends(get_uow)) -> OrderOut:
order = orders.create(uow, body.to_command())
return OrderOut.from_domain(order)Дальше назовите выгоду конкретно, а не словами про читаемость. Один и тот же сценарий вызывается из обработчика, из задачи очереди и из консольной команды. Тест на него не поднимает приложение и не ходит через тестовый клиент, поэтому его пишут на каждую ветку правил, а не на happy path. Доменная ошибка бросается своим исключением и превращается в код ответа одним обработчиком, вместо raise HTTPException в тридцати местах.
И сразу обозначьте меру: тонкой ручке, которая читает строку по идентификатору и отдаёт её, отдельный слой не нужен. Граница проводится там, где появились правила: проверки состояния, переходы статусов, несколько источников данных, транзакция на несколько операций.
Где спотыкаются
Тащат в сервис Request, Depends и HTTPException. Формально слой есть, но он намертво привязан к фреймворку, и из задачи очереди его уже не позвать.
Второе: делают анемичный переходник, который один в один вызывает репозиторий. Кода стало вдвое больше, правила по-прежнему размазаны по обработчикам, и команда справедливо считает слой бюрократией.
Третье: теряют границу транзакции. Коммит уезжает внутрь репозитория, сценарий из трёх шагов падает на втором, и в базе остаётся половина операции.