Вайб-кодинг (vibe coding). Паттерны проектирования

- -
- 100%
- +
По-Фаулеру это хороший первый шаг: небольшой, локальный и поведенчески безопасный [75] [79].
Пример 2: выделение seam ПроблемаЧастая беда legacy FastAPI — прямые вызовы email, Kafka, Redis, внешних API или audit-функций внутри бизнес-метода. Такие зависимости мешают изолированному тестированию и делают код хрупким [74] [80].
Промпт к ИИ
Найди в этом FastAPI service внешние зависимости, мешающие тестированию.
Предложи seam для изоляции email и audit-логирования.
Сделай один маленький шаг: заверни побочные эффекты в интерфейс NotificationPort и AuditPort, не меняя поведения сервиса [74] [81].
После
class NotificationPort (Protocol): def order_approved (self, email: str) -> None:… class AuditPort (Protocol): def order_approved (self, order_id: int, user_id: int) -> None:… class OrderService: def __init__ ( self, db: Session, notifications: NotificationPort, audit: AuditPort, ): self. db = db self.notifications = notifications self.audit = audit def approve (self, order_id: int, user: User): order = self.db.query(Order).filter(Order.id == order_id).first () if not order: raise OrderNotFound () if order.status!= «pending»: raise InvalidOrderStatus () if user.role!= «manager»: raise PermissionDenied () order.status = «approved» order. approved_by = user.id self.db.commit () self.db.refresh (order) self.notifications.order_approved(order.customer_email) self.audit.order_approved(order.id, user.id) return orderТеперь появился seam: в тестах можно подставить fake-реализации портов и проверить только бизнес-поведение без реальных внешних эффектов [74] [80].
Это очень фаулеровский ход: не спорить с legacy-кодом в лоб, а сначала открыть место для безопасной подмены зависимостей [74] [75].
Пример 3: слишком много Depends
В реальных FastAPI legacy-проектах endpoint может принимать слишком много зависимостей, а затем просто прокидывать их дальше. Это признак того, что композиция зависимостей находится не в том слое [82] [76].
До
@router.get (»/ {order_id}») async def get_order ( order_id: int, session: Session = Depends (get_db), actor_service: ActorService = Depends (get_actor_service), event_service: EventService = Depends (get_event_service), item_service: ItemService = Depends (get_item_service), comment_service: CommentService = Depends (get_comment_service), ): service = OrderService ( session=session, actor_service=actor_service, event_service=event_service, item_service=item_service, comment_service=comment_service, ) return service.get_order (order_id)Промпт к ИИ
Упрости FastAPI endpoint в legacy-модуле.
Не меняй поведение.
Вынеси сборку сложного OrderService в dependency provider, чтобы endpoint зависел только от одного сервиса [82] [76] [77].
После
def get_order_service ( session: Session = Depends (get_db), actor_service: ActorService = Depends (get_actor_service), event_service: EventService = Depends (get_event_service), item_service: ItemService = Depends (get_item_service), comment_service: CommentService = Depends (get_comment_service), ) -> OrderService: return OrderService ( session=session, actor_service=actor_service, event_service=event_service, item_service=item_service, comment_service=comment_service, ) @router.get (»/ {order_id}») async def get_order ( order_id: int, service: OrderService = Depends (get_order_service), ): return service.get_order (order_id)Такой шаг уменьшает связность HTTP-слоя и делает endpoint чище без большого переписывания архитектуры [76] [77].
Это не «идеальная архитектура за раз», а маленькое улучшение формы кода, что хорошо соответствует каталогу рефакторингов Фаулера [79] [75].
Пример 4: постепенное покрытие тестами
Когда legacy FastAPI-код плохо тестируется, не надо сразу пытаться писать идеальные unit-тесты на всё. Сначала имеет смысл зафиксировать текущее поведение на уровне API или service-метода, а уже потом дробить систему дальше [81] [83].
Промпт к ИИ
Напиши characterization tests для этого FastAPI endpoint.
Цель — зафиксировать текущее поведение, включая странные ветки и edge-cases.
Не исправляй баги, только опиши и покрой текущее поведение тестами через TestClient или через сервисный слой, если это дешевле [81] [83].
Это полезно, потому что characterization tests снижают риск «рефакторинга вслепую», особенно если бизнес-логика уже не до конца понятна команде [81] [75].
Готовые промпты
Вот хорошие промпты именно для FastAPI legacy:
— «Проанализируй этот endpoint и выдели HTTP-логику, бизнес-логику, доступ к данным и побочные эффекты. Предложи один минимальный рефакторинг без изменения поведения» [75] [76].
— «Сгенерируй characterization tests для этого FastAPI route, чтобы зафиксировать текущее поведение перед рефакторингом» [81] [83].
— «Найди seam для изоляции внешнего API/почты/очереди, чтобы сервис можно было тестировать без реальных интеграций» [74] [80].
— «Вынеси бизнес-логику из fat endpoint в service-слой, сохрани сигнатуру маршрута и response shape» [76] [77].
— «Упрости dependency graph: endpoint должен зависеть от одного сервиса, а не от пяти отдельных зависимостей» [82] [76].
— «Предложи пошаговый план рефакторинга этого FastAPI-модуля в 3—5 маленьких шагов, каждый с тестом и возможностью отката» [84] [75].
Как применять на практике
Самый рабочий способ такой:
— Вставляешь один endpoint или один service-класс.
— Просишь ИИ сначала объяснить текущую структуру.
— Затем просишь тесты на текущее поведение.
— Потом — один рефакторинг за раз.
— После каждого шага запускаешь тесты и только потом переходишь дальше [75] [83].
Если хочешь, я могу следующим сообщением сделать полный разбор одного реального FastAPI legacy endpoint: до → промпты → после → тесты.
⁂
Пример рефакторинга FastAPI на чистую архитектуру в стиле lobeHub
Да: в стиле LobeHub это обычно означает разнести FastAPI-приложение по слоям API → Application/Use Cases → Domain → Infrastructure, чтобы endpoint был тонким, бизнес-правила жили вне FastAPI, а зависимости смотрели внутрь, а не наружу [89] [90] [91].
Что значит «в стиле LobeHub»
В описании skill fastapi-clean-architecture на LobeHub акцент сделан на четком разделении слоев, dependency injection, repository pattern и тестируемости, где API-слой отвечает за маршруты и валидацию, Domain — за сущности и правила, а Infrastructure — за БД и внешние адаптеры [89] [90].
Это хорошо сочетается с фаулеровским подходом: не переписывать всё в один заход, а постепенно выделять use cases, порты и адаптеры, сохраняя поведение системы [92] [93].
Типичный legacy FastAPI
Обычно legacy FastAPI-модуль выглядит так: route сам читает данные из SQLAlchemy, сам проверяет права, сам реализует бизнес-логику и сам вызывает внешние сервисы. Такой код трудно тестировать и он жестко связан с FastAPI и ORM [94] [91].
Упрощенный пример «до»:
@router.post (»/users/ {user_id} /deactivate»)
def deactivate_user (
user_id: int,
db: Session = Depends (get_db),
current_user: User = Depends (get_current_user),
):
user = db.query(UserModel).filter(UserModel.id == user_id).first ()
if not user:
raise HTTPException (404, «User not found»)
if current_user.role!= «admin»:
raise HTTPException (403, «Forbidden»)
if user.status == «inactive»:
raise HTTPException (400, «Already inactive»)
user.status = «inactive»
db.commit ()
send_email (user. email, «Your account was deactivated»)
return {«id»: user.id, «status»: user.status}
Здесь смешаны presentation, application logic, persistence и integration side effects, то есть почти все слои одновременно [94] [91].
Куда двигаться
Для clean architecture в стиле LobeHub целевая структура обычно такая:

Ключевой принцип: бизнес-правила не должны зависеть от FastAPI, а репозитории и адаптеры реализуют абстракции, определенные ближе к домену или application-слою [89] [91].
Пошаговый рефакторинг
Начинать лучше не с «перестроим весь проект», а с одного вертикального среза — одного endpoint и связанных с ним правил [92] [97].
Практически последовательность такая:
— Зафиксировать текущее поведение тестами.
— Вынести бизнес-операцию в use case.
— Вынести доступ к данным в repository interface + implementation.
— Изолировать внешние вызовы через порт.
— Оставить router только как адаптер HTTP ↔ use case [98] [89] [91].
Пример «после»
Domain
from dataclasses import dataclass from typing import Protocol, Optional @dataclass class User: id: int email: str status: str class UserRepository (Protocol): def get_by_id (self, user_id: int) -> Optional [User]:… def save (self, user: User) -> None:… class NotificationPort (Protocol): def send_deactivated (self, email: str) -> None:…В domain остаются сущность и абстракции, без FastAPI, SQLAlchemy и внешних SDK [89] [90].
Application
class UserNotFoundError (Exception): pass class ForbiddenError (Exception): pass class AlreadyInactiveError (Exception): pass class DeactivateUserUseCase: def __init__ (self, users: UserRepository, notifications: NotificationPort): self.users = users self.notifications = notifications def execute (self, actor_role: str, user_id: int) -> User: user = self.users.get_by_id (user_id) if not user: raise UserNotFoundError () if actor_role!= «admin»: raise ForbiddenError ()Конец ознакомительного фрагмента.
Текст предоставлен ООО «Литрес».
Прочитайте эту книгу целиком, купив полную легальную версию на Литрес.
Безопасно оплатить книгу можно банковской картой Visa, MasterCard, Maestro, со счета мобильного телефона, с платежного терминала, в салоне МТС или Связной, через PayPal, WebMoney, Яндекс.Деньги, QIWI Кошелек, бонусными картами или другим удобным Вам способом.


