2.1 KiB
2.1 KiB
CRUD Template
Summary
Every resource follows the same four-file slice. Repetition is intentional: it makes the codebase predictable and generatable.
The slice
domain/<resource>/
models.py # pure Pydantic model (no DB fields)
ports/
db.py # document model + repository factory
service.py # service class + factory
converters.py # (optional) transforms
models.py
A framework-free Pydantic model describing the business shape.
class Item(BaseModel):
name: str
quantity: int = 0
ports/db.py
The persistence model extends the business model and adds database concerns. The repository factory selects the right repository flavour.
class ItemDocument(Item, BaseDocument): # BaseDocument adds id/created_at/...
pass
def create_item_repo(*, owner_repo=None) -> ItemRepository:
return repo_factory(
collection="items",
document=ItemDocument,
populate_rules=[PopulateRule(field="owner", collection="users")],
cache=None, # or a CachedBaseRepository config
)
ports/service.py
The service is built through the DI library, injecting the repository. The
generic CRUD base provides create/read/update/patch/delete/search/....
class ItemService(CrudsServiceAdaptor[Item, ItemDocument]):
model = Item
schema = ItemDocument
def get_item_service(repo) -> ItemService:
return build(ItemService, instances={"repo": repo})
Naming conventions
| Concept | Convention |
|---|---|
| collection | plural snake case (items) |
| document | <Resource>Document |
| repository factory | create_<resource>_repo |
| service | <Resource>Service |
| service factory | get_<resource>_service |
| resource key | <resource>_svc |
| operationId | list_items, create_item, ... |
Optional files
converters.py— pure functions mapping between domain types.ports/enrichment.py— adapts a pipeline into an async callable the service can invoke (used when a resource is populated by a pipeline).