Backend Entity Development Skill
Guides creation of backend entities following the Backcast coding standards and EVCS (Entity Versioning Control System) patterns.
Quick Start
When prompted to create a new backend entity:
- Determine Entity Type - Ask clarifying questions about versioning and branching needs
- Generate Model - Create SQLAlchemy model with proper base class and mixins
- Create Schemas - Generate Pydantic schemas (Create, Update, Public)
- Implement Service - Create service layer with appropriate base service
- Add Repository - Implement repository if custom queries needed
- Write Tests - Create unit and integration tests
Entity Type Decision Guide
| Question | Simple | Versionable | Branchable |
|---|---|---|---|
| Need version history? | No | Yes | Yes |
| Need branching/change orders? | No | No | Yes |
| Soft delete required? | No | Yes | Yes |
| Audit trail required? | No | Yes | Yes |
Quick Decision Tree
Need version history?
├─ No → Simple Entity (SimpleEntityBase)
└─ Yes → Need branching/change orders?
├─ No → Versionable Entity (EntityBase + VersionableMixin)
└─ Yes → Branchable Entity (EntityBase + VersionableMixin + BranchableMixin)
Common Entity Types by Domain
Projects & Planning
- Projects → Branchable
- WBE (Work Breakdown Elements) → Branchable
- Milestones → Branchable
- Project Templates → Simple
Finance
- Cost Elements → Branchable
- Budget Allocations → Branchable
- Payment Records → Versionable
- Exchange Rates → Simple
User Management
- Users → Versionable
- Departments → Versionable
- User Preferences → Simple
- Access Logs → Versionable
Implementation Checklist
1. Model (app/models/domain/[entity].py)
- Import correct base class (SimpleEntityBase, EntityBase)
- Import mixins (VersionableMixin, BranchableMixin) if needed
- Use
Mapped[]type annotations for all columns - Use
mapped_column()with proper constraints - Define
__tablename__as lowercase plural - Add proper indexes on foreign keys and query columns
- Use UUID type from
app.models.types(FG_UUID for FK, PG_UUID for PK)
2. Schemas (app/models/schemas/[entity].py)
- Import
BaseModelandConfigDict - Set
model_config = ConfigDict(strict=True)on all schemas - Create:
[Entity]Create,[Entity]Update,[Entity]Public - Include all fields except auto-generated timestamps
- Use proper types (str, int, Decimal, UUID, datetime)
- Mark optional fields with
| Nonefor Update schemas
3. Service (app/services/[entity]_service.py)
- Extend
SimpleService[TSimple],TemporalService[TVersionable], orBranchableService[TBranchable] - Override CRUD methods only if custom logic needed
- Always accept
actor_id: UUIDfor state changes - Return domain objects, not ORM rows
- Raise
ValueErrorfor business logic violations - Add Google-style docstrings for public methods
4. Repository (app/repositories/[entity]_repository.py)
- Extend base repository class
- Only create if custom queries needed
- Use
select()andexec()for queries - Use proper async/await patterns
5. API Routes (app/api/v1/[entity].py)
- Create router with proper prefix
- Add operation_id for each route
- Use
RoleCheckerdependency for permissions - Convert service errors to
HTTPException - Return proper response models
- Use
Annotatedfor dependency injection
6. Tests
- Unit tests for service layer business logic
- Integration tests for database operations
- Test fixture for entity creation
- 80%+ coverage required
- Use
pytest.mark.asynciofor async tests
Code Examples by Entity Type
Versionable Entity Examples
- Model:
backend/app/models/domain/user.py- User with bitemporal versioning - Schema:
backend/app/models/schemas/user.py - Service:
backend/app/services/user_service.py
Branchable Entity Examples
- Model:
backend/app/models/domain/cost_element.py- Cost allocation with branching - Schema:
backend/app/models/schemas/cost_element.py - Service:
backend/app/services/cost_element_service.py
Other Examples
- WBE (Branchable):
backend/app/models/domain/wbe.py - Project (Branchable):
backend/app/models/domain/project.py - Department (Versionable):
backend/app/models/domain/department.py
Common Pitfalls
❌ Using Any type
# Bad
def process_data(data: Any) -> Any:
return data
# Good
def process_data(data: ProjectCreate) -> Project:
return Project(**data.model_dump())
❌ Accessing SQLAlchemy objects after session operations
# Bad - May cause MissingGreenlet errors
wbe = await CreateVersionCommand(...).execute(session)
# ... some SQL operations ...
wbe_id = created_wbe.wbe_id # Object may have expired
# Good - Capture IDs immediately
wbe = await CreateVersionCommand(...).execute(session)
wbe_id = wbe.wbe_id # Capture ID before object expires
❌ Generating timestamps multiple times
# Bad - May create empty ranges
await self._check_overlap(session, datetime.now(UTC), branch)
await self._close_version(session, version, datetime.now(UTC))
# Good - Generate once
timestamp = datetime.now(UTC)
await self._check_overlap(session, timestamp, branch)
await self._close_version(session, version, timestamp)
❌ Returning ORM rows from API
# Bad
return results.scalars().all()
# Good
projects = results.scalars().all()
return [ProjectPublic.model_validate(p) for p in projects]
Out of Scope
This skill does NOT:
- Handle frontend TypeScript types (use frontend-entity-dev skill)
- Create API route documentation (auto-generated via FastAPI)
- Manage database migrations (use Alembic directly)
- Implement authentication/authorization logic
Quality Gates
Before completion, ensure:
- MyPy strict mode passes (zero errors)
- Ruff linting passes (zero errors)
- All tests pass (pytest)
- Test coverage ≥80%
- Docstrings on all public methods
Related Documentation
- Entity Classification Guide - Full decision tree and examples
- Backend Coding Standards - Complete standards reference
- EVCS Implementation Guide - EVCS patterns and recipes
Converted and distributed by TomeVault — claim your Tome and manage your conversions.