SOLID Principles, Clean Architecture & Best Practices
Every component follows Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion principles
Clear separation of concerns with Core, Infrastructure, Services, and Models layers
Domain events with proper validation, routing, and error handling
Proper DI container with service registry for testability and maintainability
Health checks, metrics collection, and performance monitoring built-in
Comprehensive exception handling with retry logic and resilience patterns
Each class has one reason to change
class UserService:
# Only handles user domain logic
Open for extension, closed for modification
class IEventHandler(ABC):
# Easy to add new handlers
Subtypes must be substitutable
publisher: IMessagePublisher
# Any implementation works
Many specific interfaces vs one general
IMessagePublisher # Publishing
IMessageConsumer # Consuming
Depend on abstractions, not concretions
def __init__(self, publisher: IEventPublisher):
self.publisher = publisher
EventFactory creates validated events
EventFactory.create_event(EventType.USER_CREATED, **data)
Event handlers observe and react to domain events
registry.register_handler("user.created", handler)
Service registry manages dependencies
registry.get_service("user_service")
Different exchange types use different routing strategies
DirectExchange | FanoutExchange | TopicExchange
BaseEvent defines common structure
class UserCreatedEvent(BaseEvent):
RabbitMQ adapter implements messaging interfaces
class RabbitMQPublisher(IMessagePublisher):
class IEventPublisher(ABC):
"""Interface for publishing domain events"""
@abstractmethod
async def publish_event(
self,
event_type: str,
event_data: Dict[str, Any],
correlation_id: Optional[str] = None
) -> None:
pass
class IEventHandler(ABC):
"""Interface for handling domain events"""
@abstractmethod
async def handle(
self,
event_data: Dict[str, Any],
metadata: MessageMetadata
) -> None:
pass
@abstractmethod
def can_handle(self, routing_key: str) -> bool:
pass
class UserEventHandler(IEventHandler):
"""Handles user-related events following SRP"""
def can_handle(self, routing_key: str) -> bool:
return routing_key in ["user.created", "user.updated"]
async def handle(self, event_data: Dict[str, Any], metadata: MessageMetadata):
event_type = event_data.get("event_type")
if event_type == "user.created":
await self._handle_user_created(event_data, metadata)
elif event_type == "user.updated":
await self._handle_user_updated(event_data, metadata)
async def _handle_user_created(self, event_data, metadata):
# Business logic for user creation
event = UserCreatedEvent(**event_data)
await self._create_user_profile(event)
await self._send_welcome_email(event)
class UserCreatedEvent(BaseEvent):
"""User creation event with validation"""
event_type: str = Field(default="user.created", const=True)
user_id: str
email: str
first_name: str
last_name: str
registration_source: Optional[str] = "web"
@validator("email")
def validate_email(cls, v):
if "@" not in v:
raise ValueError("Invalid email format")
return v.lower()
class EventFactory:
"""Factory for creating validated events"""
@classmethod
def create_event(cls, event_type: EventType, **kwargs) -> BaseEvent:
event_class = cls._event_mapping.get(event_type)
if not event_class:
raise ValueError(f"Unknown event type: {event_type}")
return event_class(**kwargs)
# Publishing events
await producer_service.user_registered(
user_id="user123",
email="john@example.com",
first_name="John",
last_name="Doe",
correlation_id="req-456"
)
# Service setup with DI
connection_manager = RabbitMQConnectionManager(settings)
publisher = RabbitMQPublisher(connection_manager)
event_publisher = EventPublisher(publisher)
producer_service = ProducerService(event_publisher)
# Register handlers
registry.register_handler("user.created", user_handler)
registry.register_handler("order.placed", order_handler)
# Health monitoring
health = await health_monitor.check_system_health()
print(f"System status: {health.status}")