| """ |
| domain/interfaces/services/message_broker.py |
| βββββββββββββββββββββββββββββββββββββββββββββ |
| MessageBroker β abstract contract for async pub/sub messaging. |
| |
| This interface deliberately does NOT mention RabbitMQ, Kafka, or Redis. |
| Swapping the broker is done by creating a new implementation class β |
| zero changes required in Domain or Application layers. |
| """ |
| from __future__ import annotations |
|
|
| from abc import ABC, abstractmethod |
| from typing import Any, Callable, Coroutine |
|
|
|
|
| |
| MessageHandler = Callable[[dict[str, Any]], Coroutine[Any, Any, None]] |
|
|
|
|
| class MessageBroker(ABC): |
| """ |
| Async pub/sub messaging contract. |
| |
| Lifecycle: |
| await broker.connect() |
| await broker.publish(...) |
| await broker.consume(...) # runs until cancelled |
| await broker.disconnect() |
| """ |
|
|
| @abstractmethod |
| async def connect(self) -> None: |
| """ |
| Establish a connection to the message broker. |
| |
| Should be idempotent β calling connect() on an already-connected |
| broker should be a no-op (or re-use the existing connection). |
| """ |
| ... |
|
|
| @abstractmethod |
| async def disconnect(self) -> None: |
| """ |
| Gracefully close the broker connection and release resources. |
| """ |
| ... |
|
|
| @abstractmethod |
| async def publish( |
| self, |
| queue_name: str, |
| message: dict[str, Any], |
| *, |
| persistent: bool = True, |
| ) -> None: |
| """ |
| Publish a message to the specified queue. |
| |
| Args: |
| queue_name: Name of the destination queue/topic. |
| message: Payload to send β must be JSON-serialisable. |
| persistent: If ``True``, the broker should persist the message |
| to disk (survives broker restart). Default is True. |
| """ |
| ... |
|
|
| @abstractmethod |
| async def consume( |
| self, |
| queue_name: str, |
| handler: MessageHandler, |
| *, |
| auto_ack: bool = False, |
| ) -> None: |
| """ |
| Start consuming messages from the specified queue. |
| |
| This method blocks (runs indefinitely) until the task is cancelled. |
| For each message received, ``handler(payload_dict)`` is awaited. |
| |
| Args: |
| queue_name: Name of the source queue/topic. |
| handler: Async callable invoked with the deserialized message payload. |
| auto_ack: If ``True``, acknowledge messages automatically before |
| processing (at-most-once). If ``False`` (default), |
| ack only after handler completes successfully (at-least-once). |
| """ |
| ... |
|
|
| @abstractmethod |
| async def is_connected(self) -> bool: |
| """Return ``True`` if the broker connection is active.""" |
| ... |
|
|