# Core Architecture Deep Dive

Kurier is a unified messaging abstraction layer that simplifies asynchronous communication by treating different message brokers as standardized **Lines**. This document explores the internal design and lifecycle of a message within the system.

## Table of Contents

- [The Kurier Metaphor](#the-kurier-metaphor)
- [1. The Life of a Passenger (Message Flow)](#1-the-life-of-a-passenger-message-flow)
- [2. BaseKurier: The Foundation](#2-basekurier-the-foundation)
- [3. Station: The Brain](#3-station-the-brain)
- [4. Concurrency Model](#4-concurrency-model)

---

## The Kurier Metaphor

To understand the architecture, we use a consistent metaphor:

*   **Lines (`BaseKurier`)**: The transport medium (e.g., RabbitMQ, Kafka, Azure Service Bus). Each "Line" handles its own connection and protocol details.
*   **Passengers (`KurierMessage`)**: The messages being transported. Every message is wrapped in a standardized "envelope" to ensure consistency across different brokers.
*   **Station (`Station`)**: The central hub where messages arrive. It inspects the "Passenger's" ticket (type and metadata) and directs them to the correct **Handlers**.

---

## 1. The Life of a Passenger (Message Flow)

### Phase 1: Publishing
When you call `line.publish_async(MyMessage(...))`:
1.  **Serialization**: The `BaseKurier` class inspects the object (Pydantic, Dataclass, or primitive).
2.  **Enveloping**: It wraps the data into a `KurierMessage` instance.
    *   Generates a **UUIDv7** `correlation_id` (if not already present).
    *   Captures the `object_type_name` (e.g., `OrderCreated`).
    *   Captures the `qualified_object_type_name` (e.g., `myapp.models.OrderCreated`) to prevent collisions.
3.  **Wire Format**: The `Serializer` (defaulting to JSON) converts the envelope into a string or bytes.
4.  **Transport**: The concrete implementation (e.g., `RabbitMQLine`) sends the raw payload to the broker.

### Phase 2: Consumption
When a message arrives from the broker:
1.  **Reception**: The `_run_consumer_loop` of the specific Line receives the raw data.
2.  **Dispatch**: The Line calls `Station.dispatch(self, raw_data)`.
3.  **Envelope Decoding**: The `Station` uses the Line's serializer to reconstruct the `KurierMessage` envelope.
4.  **Routing**: The `Station` looks up registered handlers based on:
    *   The **Line Name** (e.g., handlers specific to the `"orders_queue"` line).
    *   The **Bus Type** (e.g., handlers for *any* RabbitMQ line).
5.  **Reconstruction**: The `Station` converts the `message_payload` back into the original Python type (e.g., validating it as a Pydantic model).
6.  **Execution**: Handlers are invoked. If a handler is synchronous, it is automatically offloaded to a thread-pool to avoid blocking the `asyncio` loop.

---

## 2. BaseKurier: The Foundation

All transports inherit from `BaseKurier`. It provides:

*   **Global Registry**: Every Line instance must have a globally unique name. This allows looking up any line from anywhere in the app using `BaseKurier.from_registry("name")`.
*   **Unified API**: Provides `publish_async`, `publish_batch_async`, and their synchronous equivalents (`publish`, `publish_batch`).
*   **Batching Logic**: Handles chunking large lists of messages and sharing a single `correlation_id` across a batch.

---

## 3. Station: The Brain

The `Station` is a static registry of handlers. Unlike traditional event emitters, it is **Type-Aware**:

```python
@Station.add_handler(BusType.RABBIT_MQ)
async def handle_order(message: OrderCreated):
    # 'message' is already an instance of OrderCreated
    pass
```

The `Station` inspects the **type annotation** of the handler's first argument to know which messages should be routed there.

---

## 4. Concurrency Model

Kurier is **Asyncio-native**. 
*   **Publishing**: `publish_async` is the primary path. The synchronous `publish` is a wrapper that safely handles running coroutines from either sync or async contexts (using `concurrent.futures` when an event loop is already running).
*   **Consumption**: Consumers (like RabbitMQ) typically run in a dedicated daemon thread to maintain a persistent connection, but they dispatch messages back into an `asyncio` event loop to ensure your handlers remain non-blocking.
