Skip to main content
Unlisted page
This page is unlisted. Search engines will not index it, and only users having a direct link can access it.

Design Patterns

info

Fluvius Framework implements several proven design patterns to help you build maintainable applications.

Domain-Driven Design (DDD)​

Aggregate Pattern​

Purpose: Encapsulate business logic and enforce invariants.

Implementation:

from fluvius.domain import Aggregate, action

class UserAggregate(Aggregate):
@action(evt_key='user-created', resources=['user'])
async def create_user(self, name: str, email: str):
# Business logic and invariant enforcement
if not email or '@' not in email:
raise ValueError("Invalid email")
return {'name': name, 'email': email}

Benefits:

  • Encapsulates business logic
  • Enforces invariants
  • Clear boundaries
  • Testable in isolation

Bounded Context Pattern​

Purpose: Define clear boundaries between domains.

Implementation:

# User domain (bounded context)
class UserDomain(Domain):
__aggregate__ = UserAggregate
__namespace__ = 'app-user'

# Order domain (different bounded context)
class OrderDomain(Domain):
__aggregate__ = OrderAggregate
__namespace__ = 'app-order'

Benefits:

  • Clear domain boundaries
  • Independent evolution
  • Reduced coupling
  • Team autonomy

Ubiquitous Language Pattern​

Purpose: Use business terminology in code.

Implementation:

# Good: Uses business language
@action(evt_key='order-placed')
async def place_order(self, items: list):
pass

# Bad: Uses technical terms
@action(evt_key='order-created')
async def create_order(self, products: list):
pass

Benefits:

  • Code reflects business
  • Better communication
  • Less translation needed
  • Clearer intent

Command Query Responsibility Segregation (CQRS)​

Command Pattern​

Purpose: Separate commands (writes) from queries (reads).

Implementation:

# Command (write)
command = domain.create_command('create-user', {
'name': 'John Doe',
'email': 'john@example.com'
})
response = await domain.process_command(command)

# Query (read)
user = await domain.statemgr.fetch('user', user_id)
users = await domain.statemgr.find('user', active=True)

Benefits:

  • Independent scaling
  • Optimized models
  • Clear separation
  • Better performance

Command Handler Pattern​

Purpose: Handle commands through aggregates.

Implementation:

class UserAggregate(Aggregate):
@action(evt_key='user-created')
async def create_user(self, name: str, email: str):
# Command handler logic
return {'name': name, 'email': email}

Benefits:

  • Centralized command handling
  • Business logic in one place
  • Easy to test
  • Clear flow

Event Sourcing​

Event Store Pattern​

Purpose: Store all changes as events.

Implementation:

@action(evt_key='user-created', resources=['user'])
async def create_user(self, name: str, email: str):
# Event automatically stored in event store
return {'name': name, 'email': email}

Benefits:

  • Complete audit trail
  • Event replay
  • Time travel
  • Event-driven integration

Event Sourcing Pattern​

Purpose: Rebuild state from events.

Implementation:

# State is rebuilt from events
# Events: [user-created, user-updated, user-deactivated]
# Current state: {name: 'John', email: 'john@example.com', active: False}

Benefits:

  • State reconstruction
  • Historical queries
  • Debugging
  • Audit compliance

Repository Pattern​

State Manager Pattern​

Purpose: Abstract data access.

Implementation:

# Abstract interface
user = await domain.statemgr.fetch('user', user_id)
users = await domain.statemgr.find('user', active=True)

Benefits:

  • Abstract data access
  • Testable
  • Swappable implementations
  • Clean separation

Factory Pattern​

Domain Factory​

Purpose: Create domain instances.

Implementation:

from fluvius.domain.context import SanicContext

ctx = SanicContext.create(namespace='app-user')
domain = UserDomain(ctx)

Benefits:

  • Centralized creation
  • Consistent configuration
  • Dependency injection
  • Easy testing

Command Factory​

Purpose: Create commands.

Implementation:

command = domain.create_command('create-user', {
'name': 'John Doe',
'email': 'john@example.com'
})

Benefits:

  • Type-safe creation
  • Validation
  • Consistent structure
  • Easy to use

Strategy Pattern​

Driver Strategy​

Purpose: Swappable database drivers.

Implementation:

from fluvius.data import PostgreSQLDriver, MongoDBDriver

# Use PostgreSQL
driver = PostgreSQLDriver(connection_string)

# Or MongoDB
driver = MongoDBDriver(connection_string)

Benefits:

  • Swappable implementations
  • Database agnostic
  • Easy testing
  • Flexibility

Storage Strategy​

Purpose: Swappable storage backends.

Implementation:

from fluvius.media import LocalStorage, S3Storage

# Use local storage
storage = LocalStorage(path='/uploads')

# Or S3
storage = S3Storage(bucket='my-bucket')

Benefits:

  • Multiple backends
  • Easy switching
  • Cloud-ready
  • Testable

Observer Pattern​

Event Handlers​

Purpose: React to events.

Implementation:

@event_handler('user-created')
async def send_welcome_email(event):
# React to user-created event
await email_service.send(event.payload['email'])

Benefits:

  • Loose coupling
  • Event-driven
  • Extensible
  • Testable

Middleware Pattern​

Request Middleware​

Purpose: Process requests/responses.

Implementation:

from fluvius.fastapi import middleware

@middleware
async def logging_middleware(request, call_next):
# Log request
response = await call_next(request)
# Log response
return response

Benefits:

  • Cross-cutting concerns
  • Reusable logic
  • Chainable
  • Testable

Dependency Injection​

Context Injection​

Purpose: Inject dependencies through context.

Implementation:

from fluvius.domain.context import SanicContext

ctx = SanicContext.create(namespace='app-user')
domain = UserDomain(ctx) # Context injected

Benefits:

  • Loose coupling
  • Testable
  • Flexible
  • Maintainable

Template Method Pattern​

Aggregate Template​

Purpose: Define aggregate structure.

Implementation:

class Aggregate:
async def process(self, command):
# Template method
self.validate(command)
result = await self.execute(command)
self.generate_event(result)
return result

Benefits:

  • Consistent structure
  • Reusable logic
  • Extensible
  • Clear flow

Builder Pattern​

Query Builder​

Purpose: Build complex queries.

Implementation:

from fluvius.query import QueryBuilder

query = QueryBuilder('user')
.filter(active=True)
.sort('created_at', desc=True)
.limit(10)

results = await query.execute()

Benefits:

  • Fluent interface
  • Composable
  • Readable
  • Flexible

Decorator Pattern​

Action Decorator​

Purpose: Add behavior to methods.

Implementation:

@action(evt_key='user-created', resources=['user'])
async def create_user(self, name: str, email: str):
# Method with action behavior
pass

Benefits:

  • Non-invasive
  • Reusable
  • Composable
  • Clear intent

Singleton Pattern​

Context Singleton​

Purpose: Single context instance per request.

Implementation:

ctx = SanicContext.create(namespace='app-user')
# Single instance per namespace

Benefits:

  • Single instance
  • Shared state
  • Resource efficiency
  • Consistent access

Pattern Combinations​

DDD + CQRS + Event Sourcing​

Fluvius combines these patterns:

# DDD: Aggregate with business logic
class UserAggregate(Aggregate):
# CQRS: Command handler
@action(evt_key='user-created')
async def create_user(self, name: str, email: str):
# Event Sourcing: Event generated
return {'name': name, 'email': email}

Benefits:

  • Best of all patterns
  • Scalable architecture
  • Maintainable code
  • Proven patterns

Best Practices​

1. Use Patterns Appropriately​

Don't over-engineer. Use patterns when they add value.

2. Keep Patterns Simple​

Simple implementations are better than complex ones.

3. Document Patterns​

Document which patterns you use and why.

4. Test Patterns​

Test pattern implementations thoroughly.

Next Steps​