AgentSkillsCN

backend-dev

在后端/ 目录中创建、修改或审查任何文件时使用。提供分层架构规范、事务模式、命名约定、DTO 模板,以及 FastAPI + SQLAlchemy 后端的代码评审 checklist。

SKILL.md
--- frontmatter
name: backend-dev
description: Use when creating, modifying, or reviewing any files in the backend/ directory. Provides layered architecture rules, transaction patterns, naming conventions, DTO templates, and code review checklist for the FastAPI + SQLAlchemy backend.

Backend Development Skill

Backend architecture rules and patterns for Meal Genie. For code examples, reference existing implementations in the codebase.

Architecture

code
Routes (app/api/)           → HTTP concerns: validation, error mapping
    ↓
Services (app/services/)    → Business logic: orchestration, transactions
    ↓
Repositories (app/repositories/) → Data access: queries, CRUD
    ↓
Models (app/models/)        → Schema: columns, relationships

Every route injects current_user via dependency. Services and repos receive (session, user_id) for tenant isolation.

Layer Rules (Never Violate)

RuleWrongRight
Business logic locationComplex logic in routeMove to service
Model imports in routesfrom app.models import RecipeUse DTOs for responses
HTTP errors in servicesraise HTTPException(404)raise RecipeNotFoundError()
Commits in repositoriesself.session.commit()Service commits, repo uses flush()
Raw SQL in servicessession.execute("SELECT...")Call repository method
Response typesreturn recipe.__dict__return RecipeResponseDTO.from_model(recipe)

Transaction Pattern

python
# Service handles transactions
class ThingService:
    def __init__(self, session: Session, user_id: int):
        self.session = session
        self.repo = ThingRepo(session, user_id)

    def create(self, dto: ThingCreateDTO) -> Thing:
        try:
            thing = self.repo.create(dto)
            self.session.commit()
            return thing
        except Exception:
            self.session.rollback()
            raise

# Repository does NOT commit
class ThingRepo:
    def create(self, dto: ThingCreateDTO) -> Thing:
        thing = Thing(**dto.model_dump())
        self.session.add(thing)
        self.session.flush()  # Get ID without committing
        return thing

Exception Pattern

Services define domain exceptions. Routes map them to HTTP status codes.

python
# In service
class RecipeNotFoundError(Exception): pass
class DuplicateRecipeError(Exception): pass

# In route
except RecipeNotFoundError:
    raise HTTPException(status_code=404, detail="Recipe not found")
except DuplicateRecipeError as e:
    raise HTTPException(status_code=409, detail=str(e))

Naming Conventions

LayerFileClass
Modelshopping_item.pyShoppingItem
DTOshopping_item_dtos.pyShoppingItemCreateDTO, ShoppingItemResponseDTO
Repositoryshopping_item_repo.pyShoppingItemRepo
Serviceshopping_item_service.pyShoppingItemService
Routeshopping_items.py (plural)router = APIRouter()

DTO Suffixes

PurposeSuffixNotes
CreationCreateDTORequired fields
UpdatesUpdateDTOAll Optional fields
ResponsesResponseDTOHas from_model() classmethod
List viewsCardDTOLightweight subset
Query paramsFilterDTOIncludes pagination

New Feature Workflow

Create in this order:

  1. Model (app/models/) — SQLAlchemy 2.0 style with Mapped annotations
  2. Migrationalembic revision --autogenerate -m "description"
  3. DTOs (app/dtos/) — Create, Update, Response, optionally Card/Filter
  4. Repository (app/repositories/) — Takes (session, user_id)
  5. Service (app/services/) — Takes (session, user_id), creates repo internally
  6. Route (app/api/) — Uses Depends(get_current_user)
  7. Register in main.py

Always include user_id FK with ondelete="CASCADE" and index=True on domain models.

Key Patterns (Reference Existing Code)

PatternReference File
Model with relationshipsapp/models/recipe.py
Repository with eager loadingapp/repositories/recipe_repo.py
Service with transactionsapp/services/recipe_service.py
Route with error handlingapp/api/recipes.py
Full DTO setapp/dtos/recipe_dtos.py
AI serviceapp/ai/services/meal_genie_service.py

SQLAlchemy 2.0 Style

python
# Required
from sqlalchemy.orm import Mapped, mapped_column

# Field definitions
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)

# Relationships
recipes: Mapped[List["Recipe"]] = relationship(back_populates="user", cascade="all, delete-orphan")

Query Patterns

python
# Eager loading (avoid N+1)
stmt = select(Recipe).options(joinedload(Recipe.ingredients)).where(Recipe.id == id)
result = self.session.scalars(stmt).unique().first()

# Always .unique() after joinedload on collections

Domain Constraints

FeatureLimitEnforced In
Planner entries15 maxplanner_service.py
Side recipes per meal3 maxmeal_service.py
Meal tags20 max, 50 chars eachmeal_dtos.py

AI Module Structure

code
app/ai/
├── config/           # Prompts, model settings
├── dtos/             # AI-specific request/response
└── services/         # AI service implementations

AI services return DTOs with success: bool and either result or error: str.

Quick Audit Checklist

Before completing backend work:

  • Routes only handle HTTP (no business logic)?
  • Services handle transactions (commit/rollback)?
  • Repos never commit (only flush)?
  • Domain exceptions in services, HTTPException only in routes?
  • All API bodies use DTOs?
  • Type hints on all signatures?
  • Eager loading where needed?
  • Migration created for model changes?
  • user_id FK with CASCADE and index?