Error Handling
info
Learn how to handle errors effectively in Fluvius Framework.
Aggregate Error Handling
Validation Errors
from fluvius.domain import Aggregate, action
class UserAggregate(Aggregate):
@action(evt_key='user-created')
async def create_user(self, name: str, email: str):
# Validate input
if not name or len(name) < 2:
raise ValueError("Name must be at least 2 characters")
if not email or '@' not in email:
raise ValueError("Invalid email address")
# Business logic
pass
Business Rule Errors
@action(evt_key='account-withdrawn')
async def withdraw(self, amount: float):
# Check business rules
if self._state.balance < amount:
raise ValueError("Insufficient funds")
if amount <= 0:
raise ValueError("Amount must be positive")
# Process withdrawal
pass
Command Processing Errors
Error Handling
try:
command = domain.create_command('create-user', {...})
response = await domain.process_command(command)
except ValueError as e:
# Handle validation error
logger.error(f"Validation error: {e}")
return {"error": str(e)}
except Exception as e:
# Handle other errors
logger.exception("Command processing error")
return {"error": "Internal error"}
API Error Handling
HTTP Exception Handling
from fastapi import HTTPException
@app.post("/api/users")
async def create_user(request: CreateUserRequest):
try:
command = domain.create_command('create-user', {...})
response = await domain.process_command(command)
return response
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.exception("Error creating user")
raise HTTPException(status_code=500, detail="Internal server error")
Custom Exception Handler
from fastapi import Request
from fastapi.responses import JSONResponse
class DomainError(Exception):
def __init__(self, message: str, status_code: int = 400):
self.message = message
self.status_code = status_code
@app.exception_handler(DomainError)
async def domain_error_handler(request: Request, exc: DomainError):
return JSONResponse(
status_code=exc.status_code,
content={"error": exc.message}
)
Error Types
Validation Errors
from pydantic import ValidationError
try:
user = UserState(name="", email="invalid")
except ValidationError as e:
# Handle validation error
pass
Not Found Errors
from fastapi import HTTPException
user = await domain.statemgr.fetch('user', user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
Permission Errors
from fastapi import HTTPException
if not has_permission(user, 'user', 'update'):
raise HTTPException(status_code=403, detail="Permission denied")
Error Logging
Structured Logging
import logging
logger = logging.getLogger(__name__)
try:
response = await domain.process_command(command)
except Exception as e:
logger.error("Command processing failed", extra={
"command": command.name,
"error": str(e),
"user_id": request.state.user.id
})
raise
Error Recovery
Retry Logic
import asyncio
async def process_with_retry(command, max_retries=3):
for attempt in range(max_retries):
try:
return await domain.process_command(command)
except Exception as e:
if attempt == max_retries - 1:
raise
await asyncio.sleep(2 ** attempt) # Exponential backoff
Best Practices
1. Use Specific Exceptions
# Good: Specific exception
raise ValueError("Invalid email")
# Bad: Generic exception
raise Exception("Error")
2. Include Context
# Good: Include context
raise ValueError(f"Invalid email: {email}")
# Bad: No context
raise ValueError("Invalid email")
3. Log Errors
try:
response = await domain.process_command(command)
except Exception as e:
logger.exception("Error processing command")
raise
4. Return User-Friendly Messages
try:
response = await domain.process_command(command)
except ValueError as e:
# Return user-friendly message
return {"error": "Please check your input and try again"}
Next Steps
- Learn about Testing
- Explore Deployment
- Check New Module