Skip to content

Commit 4f0e116

Browse files
author
Вадим Козыревский
committed
Update documentation
1 parent f90ec8c commit 4f0e116

15 files changed

Lines changed: 326 additions & 40 deletions

docs/bootstrap/advanced.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
[:octicons-arrow-left-24: Back to Overview](index.md)
1010

11+
- :material-code-tags: **Commands / Requests Handling**
12+
13+
Core concepts for commands, queries, and request handlers.
14+
15+
[:octicons-arrow-right-24: Read More](../request_handler.md)
16+
1117
</div>
1218

1319
---

docs/bootstrap/di_containers.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
[:octicons-arrow-left-24: Back to Overview](index.md)
1010

11+
- :material-puzzle: **Dependency Injection**
12+
13+
Container setup, scopes, and resolving handlers and dependencies.
14+
15+
[:octicons-arrow-right-24: Read More](../di.md)
16+
1117
</div>
1218

1319
---

docs/bootstrap/event_mediator.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
[:octicons-arrow-left-24: Back to Overview](index.md)
1010

11+
- :material-bell-ring: **Events Handling**
12+
13+
Event types, flow, parallel processing, and handlers.
14+
15+
[:octicons-arrow-right-24: Read More](../event_handler/index.md)
16+
1117
</div>
1218

1319
---

docs/bootstrap/index.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222

2323
[:octicons-arrow-right-24: Read More](event_mediator.md)
2424

25+
- :material-sitemap: **Saga Mediator**
26+
27+
Orchestrated sagas with step streaming, storage, and recovery.
28+
29+
[:octicons-arrow-right-24: Read More](saga_mediator.md)
30+
2531
- :material-message-processing: **Message Brokers**
2632

2733
Configure Kafka, RabbitMQ, and custom brokers for event publishing.
@@ -53,6 +59,7 @@ The `bootstrap` utilities simplify the initial configuration of your CQRS applic
5359
- **Dependency Injection Container** — Resolves handlers and their dependencies (see [Dependency Injection](../di.md))
5460
- **Request Mapping** — Maps commands and queries to their handlers (see [Request Handlers](../request_handler.md))
5561
- **Event Mapping** — Maps domain events to their handlers (see [Event Handling](../event_handler/index.md))
62+
- **Saga Mapping** — Maps saga context types to saga classes (see [Saga Pattern](../saga/index.md))
5663
- **Message Broker** — Configures event publishing (see [Event Producing](../event_producing.md))
5764
- **Middlewares** — Adds logging and custom middlewares
5865
- **Event Processing** — Configures parallel event processing
@@ -65,20 +72,22 @@ The `bootstrap` utilities simplify the initial configuration of your CQRS applic
6572

6673
## Mediator Types
6774

68-
The `python-cqrs` package provides three types of mediators:
75+
The `python-cqrs` package provides four types of mediators:
6976

7077
| Mediator Type | Use Case | Bootstrap Function |
7178
|---------------|----------|-------------------|
72-
| **`RequestMediator`** | Standard commands and queries | `bootstrap.bootstrap()` |
73-
| **`StreamingRequestMediator`** | Streaming requests with incremental results | `bootstrap.bootstrap_streaming()` |
74-
| **`EventMediator`** | Processing events from message brokers | `bootstrap.bootstrap()` (events module) |
79+
| **`RequestMediator`** | Standard commands and queries | `cqrs.requests.bootstrap.bootstrap()` |
80+
| **`StreamingRequestMediator`** | Streaming requests with incremental results | `cqrs.requests.bootstrap.bootstrap_streaming()` |
81+
| **`EventMediator`** | Processing events from message brokers | `cqrs.events.bootstrap.bootstrap()` |
82+
| **`SagaMediator`** | Orchestrated sagas with step streaming and recovery | `cqrs.saga.bootstrap.bootstrap()` |
7583

7684
!!! note "Mediator Comparison"
7785
Each mediator type has its own bootstrap function and configuration options. Choose based on your use case:
7886

7987
- **RequestMediator**: Most common, handles standard CQRS operations
8088
- **StreamingRequestMediator**: For real-time progress updates (SSE, WebSockets)
8189
- **EventMediator**: For consuming events from Kafka/RabbitMQ
90+
- **SagaMediator**: For distributed transactions with steps and compensation
8291

8392
<details>
8493
<summary><strong>When to use each mediator?</strong></summary>
@@ -87,6 +96,7 @@ The `python-cqrs` package provides three types of mediators:
8796
<li><strong>RequestMediator</strong>: Use for standard HTTP API endpoints (GET, POST, PUT, DELETE)</li>
8897
<li><strong>StreamingRequestMediator</strong>: Use when you need to stream results back to clients (file processing, batch operations)</li>
8998
<li><strong>EventMediator</strong>: Use in FastStream consumers to process events from message brokers</li>
99+
<li><strong>SagaMediator</strong>: Use when coordinating multiple steps across services with automatic compensation on failure</li>
90100
</ul>
91101

92102
</details>
@@ -96,6 +106,7 @@ The `python-cqrs` package provides three types of mediators:
96106
- **[Request Mediator](request_mediator.md)** — Standard mediator for commands and queries
97107
- **[Streaming Request Mediator](streaming_mediator.md)** — For incremental processing and SSE
98108
- **[Event Mediator](event_mediator.md)** — For processing events from message brokers
109+
- **[Saga Mediator](saga_mediator.md)** — Orchestrated sagas with storage and recovery
99110
- **[Message Brokers](message_brokers.md)** — Kafka, RabbitMQ, and custom brokers
100111
- **[Middlewares](middlewares.md)** — Request interception and modification
101112
- **[DI Containers](di_containers.md)** — Dependency injection configuration

docs/bootstrap/message_brokers.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
[:octicons-arrow-left-24: Back to Overview](index.md)
1010

11+
- :material-message-processing: **Event Producing**
12+
13+
Publishing events to Kafka, RabbitMQ, and custom brokers.
14+
15+
[:octicons-arrow-right-24: Read More](../event_producing.md)
16+
1117
</div>
1218

1319
---

docs/bootstrap/middlewares.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
[:octicons-arrow-left-24: Back to Overview](index.md)
1010

11+
- :material-code-tags: **Commands / Requests Handling**
12+
13+
Request pipeline and handler execution where middlewares apply.
14+
15+
[:octicons-arrow-right-24: Read More](../request_handler.md)
16+
1117
</div>
1218

1319
---

docs/bootstrap/request_mediator.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
[:octicons-arrow-left-24: Back to Overview](index.md)
1010

11+
- :material-code-tags: **Commands / Requests Handling**
12+
13+
Commands, queries, handlers, and request/response mapping.
14+
15+
[:octicons-arrow-right-24: Read More](../request_handler.md)
16+
1117
</div>
1218

1319
## Overview

docs/bootstrap/saga_mediator.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# Saga Mediator
2+
3+
<div class="grid cards" markdown>
4+
5+
- :material-home: **Back to Bootstrap Overview**
6+
7+
Return to the Bootstrap overview page with all configuration options.
8+
9+
[:octicons-arrow-left-24: Back to Overview](index.md)
10+
11+
- :material-sitemap: **Saga Pattern**
12+
13+
Flow, storage, recovery, and compensation.
14+
15+
[:octicons-arrow-right-24: Read More](../saga/index.md)
16+
17+
</div>
18+
19+
## Overview
20+
21+
The `SagaMediator` runs orchestrated sagas: it resolves the saga by context type, creates a transaction, and streams step results. It is bootstrapped via `cqrs.saga.bootstrap.bootstrap()` with a saga map, DI container, optional saga storage, and optional event mapping for domain events emitted by steps.
22+
23+
### Basic Configuration
24+
25+
```python
26+
import di
27+
import cqrs
28+
from cqrs.saga import bootstrap
29+
from cqrs.saga.storage.memory import MemorySagaStorage
30+
31+
def saga_mapper(mapper: cqrs.SagaMap) -> None:
32+
mapper.bind(OrderContext, OrderSaga)
33+
34+
storage = MemorySagaStorage()
35+
36+
mediator = bootstrap.bootstrap(
37+
di_container=di.Container(),
38+
sagas_mapper=saga_mapper,
39+
saga_storage=storage,
40+
)
41+
```
42+
43+
### With Domain Events
44+
45+
Steps can emit domain events; the mediator uses an event emitter and event map (same as request bootstrap). Register handlers via `domain_events_mapper`:
46+
47+
```python
48+
def events_mapper(mapper: cqrs.EventMap) -> None:
49+
mapper.bind(InventoryReservedEvent, InventoryReservedEventHandler)
50+
51+
mediator = bootstrap.bootstrap(
52+
di_container=di.Container(),
53+
sagas_mapper=saga_mapper,
54+
domain_events_mapper=events_mapper,
55+
saga_storage=storage,
56+
)
57+
```
58+
59+
### With Message Broker
60+
61+
By default the event emitter uses `DevnullMessageBroker`. To publish events to Kafka or RabbitMQ, pass `message_broker`:
62+
63+
```python
64+
from cqrs.message_brokers import kafka
65+
from cqrs.adapters.kafka import KafkaProducerAdapter
66+
67+
kafka_producer = KafkaProducerAdapter(
68+
bootstrap_servers=["localhost:9092"],
69+
client_id="my-app",
70+
)
71+
72+
mediator = bootstrap.bootstrap(
73+
di_container=di.Container(),
74+
sagas_mapper=saga_mapper,
75+
domain_events_mapper=events_mapper,
76+
saga_storage=storage,
77+
message_broker=kafka.KafkaMessageBroker(
78+
producer=kafka_producer,
79+
aiokafka_log_level="ERROR",
80+
),
81+
)
82+
```
83+
84+
### With Middlewares and On Startup
85+
86+
```python
87+
from cqrs.middlewares import base
88+
89+
class SagaLoggingMiddleware(base.Middleware):
90+
async def __call__(self, request: cqrs.Request, handle):
91+
print(f"Before saga step: {type(request).__name__}")
92+
result = await handle(request)
93+
print(f"After saga step: {type(request).__name__}")
94+
return result
95+
96+
def init_storage():
97+
# e.g. create tables for SqlAlchemySagaStorage
98+
pass
99+
100+
mediator = bootstrap.bootstrap(
101+
di_container=di.Container(),
102+
sagas_mapper=saga_mapper,
103+
saga_storage=storage,
104+
middlewares=[SagaLoggingMiddleware()],
105+
on_startup=[init_storage],
106+
)
107+
```
108+
109+
### Executing a Saga
110+
111+
Use `mediator.stream(context, saga_id=...)` to run the saga. It returns an async iterator; consume it with `async for`:
112+
113+
```python
114+
import uuid
115+
116+
context = OrderContext(order_id="123", items=["item_1"], total_amount=100.0)
117+
saga_id = uuid.uuid4()
118+
119+
async for step_result in mediator.stream(context, saga_id=saga_id):
120+
print(f"Step completed: {step_result.step_type.__name__}")
121+
```
122+
123+
For recovery, use the same `saga_id` and call `recover_saga()` (see [Saga Recovery](../saga/recovery.md)).
124+
125+
### Complete Example
126+
127+
```python
128+
import dataclasses
129+
import uuid
130+
import di
131+
import cqrs
132+
from cqrs.saga import bootstrap
133+
from cqrs.saga.saga import Saga
134+
from cqrs.saga.step import SagaStepHandler, SagaStepResult
135+
from cqrs.saga.storage.memory import MemorySagaStorage
136+
from cqrs.saga.models import SagaContext
137+
from cqrs.response import Response
138+
139+
@dataclasses.dataclass
140+
class OrderContext(SagaContext):
141+
order_id: str
142+
items: list[str]
143+
total_amount: float
144+
inventory_reservation_id: str | None = None
145+
payment_id: str | None = None
146+
147+
class ReserveInventoryStep(SagaStepHandler[OrderContext, Response]):
148+
def __init__(self, inventory_service):
149+
self._inventory_service = inventory_service
150+
151+
async def act(self, context: OrderContext) -> SagaStepResult:
152+
reservation_id = await self._inventory_service.reserve_items(
153+
context.order_id, context.items
154+
)
155+
context.inventory_reservation_id = reservation_id
156+
return self._generate_step_result(Response())
157+
158+
async def compensate(self, context: OrderContext) -> None:
159+
if context.inventory_reservation_id:
160+
await self._inventory_service.release_items(
161+
context.inventory_reservation_id
162+
)
163+
164+
class OrderSaga(Saga[OrderContext]):
165+
steps = [ReserveInventoryStep]
166+
167+
# Register services in container
168+
di_container = di.Container()
169+
# di_container.bind(...)
170+
171+
def saga_mapper(mapper: cqrs.SagaMap) -> None:
172+
mapper.bind(OrderContext, OrderSaga)
173+
174+
storage = MemorySagaStorage()
175+
mediator = bootstrap.bootstrap(
176+
di_container=di_container,
177+
sagas_mapper=saga_mapper,
178+
saga_storage=storage,
179+
)
180+
181+
context = OrderContext(order_id="123", items=["item_1"], total_amount=100.0)
182+
saga_id = uuid.uuid4()
183+
184+
async for step_result in mediator.stream(context, saga_id=saga_id):
185+
print(f"Step: {step_result.step_type.__name__}")
186+
```
187+
188+
## Bootstrap Parameters
189+
190+
| Parameter | Description |
191+
|-----------|-------------|
192+
| `di_container` | DI container (`di.Container` or CQRS `Container`) for resolving saga step handlers |
193+
| `sagas_mapper` | Callable that receives `cqrs.SagaMap` and registers context type → saga class (e.g. `mapper.bind(OrderContext, OrderSaga)`) |
194+
| `saga_storage` | Optional `ISagaStorage` implementation. If `None`, defaults to in-memory behaviour when storage is needed. For production, use e.g. `SqlAlchemySagaStorage` and register it in the container |
195+
| `domain_events_mapper` | Optional callable to register event handlers (for events emitted by steps) |
196+
| `message_broker` | Optional message broker for event publishing; defaults to `DevnullMessageBroker` |
197+
| `middlewares` | Optional list of middlewares for request processing |
198+
| `on_startup` | Optional list of callables invoked once when bootstrap runs |
199+
| `max_concurrent_event_handlers` | Max concurrent event handlers (default: 1) |
200+
| `concurrent_event_handle_enable` | Whether to process events in parallel (default: True) |

docs/bootstrap/streaming_mediator.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
[:octicons-arrow-left-24: Back to Overview](index.md)
1010

11+
- :material-play-circle: **Stream Handling**
12+
13+
Incremental processing, SSE, and streaming configuration.
14+
15+
[:octicons-arrow-right-24: Read More](../stream_handling/index.md)
16+
1117
</div>
1218

1319
---

docs/javascripts/navigation.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
{ title: 'Request Mediator', url: 'bootstrap/request_mediator/', path: 'bootstrap/request_mediator' },
6161
{ title: 'Streaming Mediator', url: 'bootstrap/streaming_mediator/', path: 'bootstrap/streaming_mediator' },
6262
{ title: 'Event Mediator', url: 'bootstrap/event_mediator/', path: 'bootstrap/event_mediator' },
63+
{ title: 'Saga Mediator', url: 'bootstrap/saga_mediator/', path: 'bootstrap/saga_mediator' },
6364
{ title: 'Message Brokers', url: 'bootstrap/message_brokers/', path: 'bootstrap/message_brokers' },
6465
{ title: 'Middlewares', url: 'bootstrap/middlewares/', path: 'bootstrap/middlewares' },
6566
{ title: 'DI Containers', url: 'bootstrap/di_containers/', path: 'bootstrap/di_containers' },

0 commit comments

Comments
 (0)