phase7: command pattern infrastructure (CommandHandler, UnitOfWork, RequestContext) + example CreateContactCommand
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""Example command: CreateContact using the Command Pattern.
|
||||
|
||||
This is a reference implementation for new modules.
|
||||
Existing contact_service.py is NOT changed — this is an alternative path.
|
||||
|
||||
Usage:
|
||||
@router.post("/contacts-v2")
|
||||
async def create_contact_v2(
|
||||
body: CreateContactDTO,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(require_permission("contacts:write")),
|
||||
):
|
||||
ctx = RequestContext(
|
||||
user_id=uuid.UUID(current_user["user_id"]),
|
||||
tenant_id=uuid.UUID(current_user["tenant_id"]),
|
||||
is_system_admin=current_user.get("is_system_admin", False),
|
||||
permissions=set(current_user.get("permissions", [])),
|
||||
)
|
||||
cmd = CreateContactCommand(
|
||||
firstname=body.firstname,
|
||||
surname=body.surname,
|
||||
email=body.email,
|
||||
)
|
||||
handler = CreateContactHandler()
|
||||
return await handler.execute(cmd, ctx, db)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from app.core.commands import CommandHandler, RequestContext, UnitOfWork
|
||||
from app.models.contact import Contact
|
||||
|
||||
|
||||
@dataclass
|
||||
class CreateContactCommand:
|
||||
"""Command to create a new contact."""
|
||||
firstname: str
|
||||
surname: str
|
||||
email: str | None = None
|
||||
phone: str | None = None
|
||||
company: str | None = None
|
||||
|
||||
|
||||
class CreateContactHandler(CommandHandler[CreateContactCommand, dict[str, Any]]):
|
||||
"""Handler for CreateContactCommand.
|
||||
|
||||
Demonstrates the Command Pattern:
|
||||
1. Authorization check (ctx.require)
|
||||
2. Domain operation (create Contact)
|
||||
3. Outbox event (crm.contact.created.v1)
|
||||
4. Audit log (contact.created)
|
||||
5. Single commit via UoW
|
||||
"""
|
||||
|
||||
async def handle(self, cmd: CreateContactCommand, ctx: RequestContext, uow: UnitOfWork) -> dict[str, Any]:
|
||||
# 1. Authorization
|
||||
ctx.require("contacts:write")
|
||||
|
||||
# 2. Domain operation
|
||||
contact = Contact(
|
||||
tenant_id=ctx.tenant_id,
|
||||
firstname=cmd.firstname,
|
||||
surname=cmd.surname,
|
||||
email_1=cmd.email,
|
||||
phone_1=cmd.phone,
|
||||
company=cmd.company,
|
||||
owner_id=ctx.user_id,
|
||||
created_by=ctx.user_id,
|
||||
updated_by=ctx.user_id,
|
||||
)
|
||||
uow.add(contact)
|
||||
|
||||
# 3. Outbox event (standardized envelope)
|
||||
uow.outbox_add(
|
||||
event_name="crm.contact.created.v1",
|
||||
aggregate_id=contact.id, # Will be set after flush
|
||||
aggregate_type="contact",
|
||||
payload={
|
||||
"firstname": cmd.firstname,
|
||||
"surname": cmd.surname,
|
||||
"email": cmd.email,
|
||||
},
|
||||
)
|
||||
|
||||
# 4. Audit log
|
||||
uow.audit_record(
|
||||
action="create",
|
||||
entity_id=contact.id,
|
||||
entity_type="contact",
|
||||
changes={"firstname": cmd.firstname, "surname": cmd.surname, "email": cmd.email},
|
||||
)
|
||||
|
||||
# 5. Return dict (will be populated after flush in commit)
|
||||
return {
|
||||
"id": str(contact.id),
|
||||
"firstname": contact.firstname,
|
||||
"surname": contact.surname,
|
||||
"email_1": contact.email_1,
|
||||
}
|
||||
Reference in New Issue
Block a user