python-cqrs 0.2.0__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/PKG-INFO +199 -99
  2. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/README.md +190 -97
  3. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/pyproject.toml +16 -4
  4. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/__init__.py +11 -7
  5. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/adapters/kafka.py +11 -17
  6. python_cqrs-2.0.0/src/cqrs/compressors/__init__.py +4 -0
  7. python_cqrs-2.0.0/src/cqrs/deserializers/protobuf.py +52 -0
  8. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/events/__init__.py +1 -2
  9. python_cqrs-2.0.0/src/cqrs/events/event.py +59 -0
  10. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/events/event_emitter.py +30 -48
  11. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/message_brokers/amqp.py +4 -5
  12. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/message_brokers/devnull.py +4 -1
  13. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/message_brokers/kafka.py +7 -3
  14. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/message_brokers/protocol.py +6 -4
  15. python_cqrs-2.0.0/src/cqrs/outbox/map.py +24 -0
  16. python_cqrs-2.0.0/src/cqrs/outbox/mock.py +55 -0
  17. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/outbox/repository.py +12 -18
  18. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/outbox/sqlalchemy.py +57 -100
  19. {python_cqrs-0.2.0/src/cqrs/outbox → python_cqrs-2.0.0/src/cqrs}/producer.py +26 -32
  20. python_cqrs-2.0.0/src/cqrs/serializers/__init__.py +0 -0
  21. python_cqrs-2.0.0/src/cqrs/serializers/default.py +8 -0
  22. python_cqrs-2.0.0/src/cqrs/serializers/protobuf.py +39 -0
  23. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/python_cqrs.egg-info/PKG-INFO +199 -99
  24. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/python_cqrs.egg-info/SOURCES.txt +8 -2
  25. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/python_cqrs.egg-info/requires.txt +10 -1
  26. python_cqrs-0.2.0/src/cqrs/events/event.py +0 -90
  27. python_cqrs-0.2.0/src/cqrs/outbox/protocol.py +0 -56
  28. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/LICENSE +0 -0
  29. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/setup.cfg +0 -0
  30. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/adapters/__init__.py +0 -0
  31. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/adapters/amqp.py +0 -0
  32. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/compressors/protocol.py +0 -0
  33. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/compressors/zlib.py +0 -0
  34. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/container/__init__.py +0 -0
  35. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/container/di.py +0 -0
  36. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/container/protocol.py +0 -0
  37. {python_cqrs-0.2.0/src/cqrs/compressors → python_cqrs-2.0.0/src/cqrs/deserializers}/__init__.py +0 -0
  38. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/dispatcher/__init__.py +0 -0
  39. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/dispatcher/dispatcher.py +0 -0
  40. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/events/bootstrap.py +0 -0
  41. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/events/event_handler.py +0 -0
  42. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/events/map.py +0 -0
  43. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/mediator.py +0 -0
  44. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/message_brokers/__init__.py +0 -0
  45. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/middlewares/__init__.py +0 -0
  46. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/middlewares/base.py +0 -0
  47. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/middlewares/logging.py +0 -0
  48. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/outbox/__init__.py +0 -0
  49. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/requests/__init__.py +0 -0
  50. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/requests/bootstrap.py +0 -0
  51. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/requests/map.py +0 -0
  52. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/requests/request.py +0 -0
  53. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/requests/request_handler.py +0 -0
  54. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/cqrs/response.py +0 -0
  55. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/python_cqrs.egg-info/dependency_links.txt +0 -0
  56. {python_cqrs-0.2.0 → python_cqrs-2.0.0}/src/python_cqrs.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-cqrs
3
- Version: 0.2.0
3
+ Version: 2.0.0
4
4
  Summary: Python CQRS pattern implementation
5
5
  Author-email: Vadim Kozyrevskiy <vadikko2@mail.ru>
6
6
  Maintainer-email: Vadim Kozyrevskiy <vadikko2@mail.ru>
@@ -21,6 +21,7 @@ Requires-Dist: aio-pika==9.3.0
21
21
  Requires-Dist: di[anyio]==0.79.2
22
22
  Requires-Dist: sqlalchemy[asyncio]==2.0.*
23
23
  Requires-Dist: retry-async==0.1.4
24
+ Requires-Dist: python-dotenv==1.0.1
24
25
  Provides-Extra: dev
25
26
  Requires-Dist: pre-commit==3.8.0; extra == "dev"
26
27
  Requires-Dist: pyright==1.1.377; extra == "dev"
@@ -29,28 +30,39 @@ Requires-Dist: aiokafka==0.10.0; extra == "dev"
29
30
  Requires-Dist: pytest~=7.4.2; extra == "dev"
30
31
  Requires-Dist: pytest-asyncio~=0.21.1; extra == "dev"
31
32
  Requires-Dist: pytest-env==0.6.2; extra == "dev"
32
- Requires-Dist: python-dotenv==1.0.1; extra == "dev"
33
33
  Requires-Dist: cryptography==42.0.2; extra == "dev"
34
34
  Requires-Dist: asyncmy==0.2.9; extra == "dev"
35
+ Provides-Extra: examples
36
+ Requires-Dist: fastapi==0.109.*; extra == "examples"
37
+ Requires-Dist: uvicorn==0.32.0; extra == "examples"
38
+ Requires-Dist: faststream[kafka]==0.5.28; extra == "examples"
35
39
  Provides-Extra: kafka
36
40
  Requires-Dist: aiokafka==0.10.0; extra == "kafka"
41
+ Requires-Dist: confluent-kafka==2.6.0; extra == "kafka"
42
+ Provides-Extra: protobuf
43
+ Requires-Dist: protobuf==5.0; extra == "protobuf"
37
44
 
38
- # Python CQRS pattern implementaion with Transaction Outbox supporting
45
+ # Python CQRS pattern implementation with Transaction Outbox supporting
39
46
 
40
47
  ## Overview
41
48
 
42
49
  This is a package for implementing the CQRS (Command Query Responsibility Segregation) pattern in Python applications.
43
- It provides a set of abstractions and utilities to help separate read and write use cases, ensuring better scalability, performance, and maintainability of the application.
50
+ It provides a set of abstractions and utilities to help separate read and write use cases, ensuring better scalability,
51
+ performance, and maintainability of the application.
44
52
 
45
- This package is a fork of the [diator](https://github.com/akhundMurad/diator) project ([documentation](https://akhundmurad.github.io/diator/)) with several enhancements:
53
+ This package is a fork of the [diator](https://github.com/akhundMurad/diator)
54
+ project ([documentation](https://akhundmurad.github.io/diator/)) with several enhancements:
46
55
 
47
56
  1. Support for Pydantic [v2.*](https://docs.pydantic.dev/2.8/);
48
57
  2. `Kafka` support using [aiokafka](https://github.com/aio-libs/aiokafka);
49
58
  3. Added `EventMediator` for handling `Notification` and `ECST` events coming from the bus;
50
59
  4. Redesigned the event and request mapping mechanism to handlers;
51
60
  5. Added `bootstrap` for easy setup;
52
- 6. Added support for [Transaction Outbox](https://microservices.io/patterns/data/transactional-outbox.html), ensuring that `Notification` and `ECST` events are sent to the broker.
53
-
61
+ 6. Added support for [Transaction Outbox](https://microservices.io/patterns/data/transactional-outbox.html), ensuring
62
+ that `Notification` and `ECST` events are sent to the broker;
63
+ 7. FastAPI supporting;
64
+ 8. FastStream supporting;
65
+ 9. [Protobuf](https://protobuf.dev/) events supporting.
54
66
 
55
67
  ## Request Handlers
56
68
 
@@ -58,7 +70,8 @@ Request handlers can be divided into two main types:
58
70
 
59
71
  ### Command Handler
60
72
 
61
- Command Handler executes the received command. The logic of the handler may include, for example, modifying the state of the domain model.
73
+ Command Handler executes the received command. The logic of the handler may include, for example, modifying the state of
74
+ the domain model.
62
75
  As a result of executing the command, an event may be produced to the broker.
63
76
  > [!TIP]
64
77
  > By default, the command handler does not return any result, but it is not mandatory.
@@ -96,9 +109,13 @@ class SyncJoinMeetingCommandHandler(SyncRequestHandler[JoinMeetingCommand, None]
96
109
  ...
97
110
  ```
98
111
 
112
+ A complete example can be found in
113
+ the [documentation](https://github.com/vadikko2/cqrs/blob/master/examples/request_handler.py)
114
+
99
115
  ### Query handler
100
116
 
101
- Query Handler returns a representation of the requested data, for example, from the [read model](https://radekmaziarka.pl/2018/01/08/cqrs-third-step-simple-read-model/#simple-read-model---to-the-rescue).
117
+ Query Handler returns a representation of the requested data, for example, from
118
+ the [read model](https://radekmaziarka.pl/2018/01/08/cqrs-third-step-simple-read-model/#simple-read-model---to-the-rescue).
102
119
  > [!TIP]
103
120
  > The read model can be constructed based on domain events produced by the `Command Handler`.
104
121
 
@@ -122,6 +139,8 @@ class ReadMeetingQueryHandler(RequestHandler[ReadMeetingQuery, ReadMeetingQueryR
122
139
 
123
140
  ```
124
141
 
142
+ A complete example can be found in
143
+ the [documentation](https://github.com/vadikko2/cqrs/blob/master/examples/request_handler.py)
125
144
 
126
145
  ## Event Handlers
127
146
 
@@ -130,59 +149,81 @@ To configure event handling, you need to implement a broker consumer on the side
130
149
  Below is an example of `Kafka event consuming` that can be used in the Presentation Layer.
131
150
 
132
151
  ```python
133
- from cqrs.events import EventHandler, SyncEventHandler
134
-
135
- class UserJoinedEventHandler(EventHandler[UserJoinedEventHandler]):
152
+ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
153
+ def __init__(self):
154
+ self._events = []
136
155
 
137
- def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
138
- self._meetings_api = meetings_api
139
-
140
- async def handle(self, event: UserJoinedEventHandler) -> None:
141
- await self._meetings_api.notify_room(event.meeting_id, "New user joined!")
156
+ @property
157
+ def events(self):
158
+ return self._events
142
159
 
143
- class SyncUserJoinedEventHandler(SyncEventHandler[UserJoinedEventHandler]):
160
+ async def handle(self, request: JoinMeetingCommand) -> None:
161
+ STORAGE[request.meeting_id].append(request.user_id)
162
+ self._events.append(
163
+ UserJoined(user_id=request.user_id, meeting_id=request.meeting_id),
164
+ )
165
+ print(f"User {request.user_id} joined meeting {request.meeting_id}")
144
166
 
145
- def __init__(self, meetings_api: MeetingAPIProtocol) -> None:
146
- self._meetings_api = meetings_api
147
167
 
148
- def handle(self, event: UserJoinedEventHandler) -> None:
149
- # do some sync logic
150
- ...
168
+ class UserJoinedEventHandler(cqrs.EventHandler[UserJoined]):
169
+ async def handle(self, event: UserJoined) -> None:
170
+ print(f"Handle user {event.user_id} joined meeting {event.meeting_id} event")
151
171
  ```
152
172
 
173
+ A complete example can be found in
174
+ the [documentation](https://github.com/vadikko2/cqrs/blob/master/examples/domain_event_handler.py)
175
+
153
176
  ## Producing Notification/ECST Events
154
177
 
155
- During the handling of a command event, messages of type `cqrs.NotificationEvent` or `cqrs.ECSTEvent` may be generated and then sent to the broker.
178
+ During the handling of a command event, messages of type `cqrs.NotificationEvent` or `cqrs.ECSTEvent` may be generated
179
+ and then sent to the broker.
156
180
 
157
181
  ```python
158
- class CloseMeetingRoomCommandHandler(requests.RequestHandler[CloseMeetingRoomCommand, None]):
159
-
160
- def __init__(self) -> None:
161
- self._events: typing.List[events.Event] = []
182
+ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
183
+ def __init__(self):
184
+ self._events = []
162
185
 
163
186
  @property
164
- def events(self) -> typing.List[events.Event]:
187
+ def events(self):
165
188
  return self._events
166
189
 
167
- async def handle(self, request: CloseMeetingRoomCommand) -> None:
168
- # some process
169
- event = events.NotificationEvent(
170
- event_topic="meeting_room_notifications",
171
- event_name="meeteng_room_closed",
172
- payload=dict(
173
- meeting_room_id=request.meeting_room_id,
174
- ),
190
+ async def handle(self, request: JoinMeetingCommand) -> None:
191
+ print(f"User {request.user_id} joined meeting {request.meeting_id}")
192
+ self._events.append(
193
+ cqrs.NotificationEvent[UserJoinedNotificationPayload](
194
+ event_name="UserJoined",
195
+ topic="user_notification_events",
196
+ payload=UserJoinedNotificationPayload(
197
+ user_id=request.user_id,
198
+ meeting_id=request.meeting_id,
199
+ ),
200
+ )
201
+ )
202
+ self._events.append(
203
+ cqrs.ECSTEvent[UserJoinedECSTPayload](
204
+ event_name="UserJoined",
205
+ topic="user_ecst_events",
206
+ payload=UserJoinedECSTPayload(
207
+ user_id=request.user_id,
208
+ meeting_id=request.meeting_id,
209
+ ),
210
+ )
175
211
  )
176
- self._events.append(event)
177
212
  ```
178
213
 
214
+ A complete example can be found in
215
+ the [documentation](https://github.com/vadikko2/cqrs/blob/master/examples/event_producing.py)
216
+
179
217
  After processing the command/request, if there are any Notification/ECST events,
180
218
  the EventEmitter is invoked to produce the events via the message broker.
181
219
 
182
220
  > [!WARNING]
183
- > It is important to note that producing events using the events property parameter does not guarantee message delivery to the broker.
184
- > In the event of broker unavailability or an exception occurring during message formation or sending, the message may be lost.
185
- > This issue can potentially be addressed by configuring retry attempts for sending messages to the broker, but we recommend using the [Transaction Outbox](https://microservices.io/patterns/data/transactional-outbox.html) pattern,
221
+ > It is important to note that producing events using the events property parameter does not guarantee message delivery
222
+ > to the broker.
223
+ > In the event of broker unavailability or an exception occurring during message formation or sending, the message may
224
+ > be lost.
225
+ > This issue can potentially be addressed by configuring retry attempts for sending messages to the broker, but we
226
+ > recommend using the [Transaction Outbox](https://microservices.io/patterns/data/transactional-outbox.html) pattern,
186
227
  > which is implemented in the current version of the python-cqrs package for this purpose.
187
228
 
188
229
  ## Kafka broker
@@ -202,13 +243,10 @@ await broker.send_message(...)
202
243
 
203
244
  ## Transactional Outbox
204
245
 
205
- The package implements the [Transactional Outbox](https://microservices.io/patterns/data/transactional-outbox.html) pattern, which ensures that messages are produced to the broker according to the at-least-once semantics.
206
-
246
+ The package implements the [Transactional Outbox](https://microservices.io/patterns/data/transactional-outbox.html)
247
+ pattern, which ensures that messages are produced to the broker according to the at-least-once semantics.
207
248
 
208
249
  ```python
209
- from sqlalchemy.ext.asyncio import session as sql_session
210
- from cqrs import events
211
-
212
250
  def do_some_logic(meeting_room_id: int, session: sql_session.AsyncSession):
213
251
  """
214
252
  Make changes to the database
@@ -216,33 +254,59 @@ def do_some_logic(meeting_room_id: int, session: sql_session.AsyncSession):
216
254
  session.add(...)
217
255
 
218
256
 
219
- class CloseMeetingRoomCommandHandler(requests.RequestHandler[CloseMeetingRoomCommand, None]):
220
-
221
- def __init__(self, repository: cqrs.SqlAlchemyOutboxedEventRepository):
222
- self._repository = repository
223
- self._events: typing.List[events.Event] = []
257
+ class JoinMeetingCommandHandler(cqrs.RequestHandler[JoinMeetingCommand, None]):
258
+ def __init__(self, outbox: cqrs.OutboxedEventRepository):
259
+ self.outbox = outbox
224
260
 
225
261
  @property
226
262
  def events(self):
227
- return self._events
228
-
229
- async def handle(self, request: CloseMeetingRoomCommand) -> None:
230
- async with self._repository as session:
231
- do_some_logic(request.meeting_room_id, session)
232
- self.repository.add(
233
- session,
234
- events.ECSTEvent(
235
- event_name="MeetingRoomClosed",
236
- payload=dict(message="foo"),
237
- ),
238
- )
239
- await self.repository.commit(session)
263
+ return []
264
+
265
+ async def handle(self, request: JoinMeetingCommand) -> None:
266
+ print(f"User {request.user_id} joined meeting {request.meeting_id}")
267
+ async with self.outbox as session:
268
+ do_some_logic(request.meeting_id, session) # business logic
269
+ self.outbox.add(
270
+ session,
271
+ cqrs.NotificationEvent[UserJoinedNotificationPayload](
272
+ event_name="UserJoined",
273
+ topic="user_notification_events",
274
+ payload=UserJoinedNotificationPayload(
275
+ user_id=request.user_id,
276
+ meeting_id=request.meeting_id,
277
+ ),
278
+ ),
279
+ )
280
+ self.outbox.add(
281
+ session,
282
+ cqrs.ECSTEvent[UserJoinedECSTPayload](
283
+ event_name="UserJoined",
284
+ topic="user_ecst_events",
285
+ payload=UserJoinedECSTPayload(
286
+ user_id=request.user_id,
287
+ meeting_id=request.meeting_id,
288
+ ),
289
+ ),
290
+ )
291
+ await self.outbox.commit(session)
240
292
  ```
241
293
 
294
+ A complete example can be found in
295
+ the [documentation](https://github.com/vadikko2/cqrs/blob/master/examples/save_events_into_outbox.py)
296
+
297
+ > [!TIP]
298
+ > You can specify the name of the Outbox table using the environment variable `OUTBOX_SQLA_TABLE`.
299
+ > By default, it is set to `outbox`.
300
+
301
+ > [!TIP]
302
+ > If you use the protobuf events you should specify `OutboxedEventRepository`
303
+ > by [protobuf serialize](https://github.com/vadikko2/cqrs/blob/master/src/cqrs/serializers/protobuf.py). A complete example can be found in
304
+ the [documentation](https://github.com/vadikko2/cqrs/blob/master/examples/save_proto_events_into_outbox.py)
242
305
 
243
306
  ## Producing Events from Outbox to Kafka
244
307
 
245
- As an implementation of the Transactional Outbox pattern, the SqlAlchemyOutboxedEventRepository is available for use as an access repository to the Outbox storage.
308
+ As an implementation of the Transactional Outbox pattern, the SqlAlchemyOutboxedEventRepository is available for use as
309
+ an access repository to the Outbox storage.
246
310
  It can be utilized in conjunction with the KafkaMessageBroker.
247
311
 
248
312
  ```python
@@ -257,12 +321,8 @@ session_factory = async_sessionmaker(
257
321
  )
258
322
  )
259
323
 
260
- broker = kafka_broker.KafkaMessageBroker(
261
- kafka_adapter.kafka_producer_factory(
262
- dsn="localhost:9094",
263
- topics=["test.topic1", "test.topic2"],
264
- ),
265
- "DEBUG"
324
+ broker = kafka.KafkaMessageBroker(
325
+ producer=kafka_adapters.kafka_producer_factory(dsn="localhost:9092"),
266
326
  )
267
327
 
268
328
  producer = cqrs.EventProducer(cqrs.SqlAlchemyOutboxedEventRepository(session_factory, zlib.ZlibCompressor()), broker)
@@ -270,20 +330,25 @@ loop = asyncio.get_event_loop()
270
330
  loop.run_until_complete(app.periodically_task())
271
331
  ```
272
332
 
333
+ A complete example can be found in
334
+ the [documentation](https://github.com/vadikko2/cqrs/blob/master/examples/kafka_outboxed_event_producing.py)
273
335
 
274
336
  ## Transaction log tailing
275
337
 
276
- If the Outbox polling strategy does not suit your needs, I recommend exploring the [Transaction Log Tailing](https://microservices.io/patterns/data/transaction-log-tailing.html) pattern.
338
+ If the Outbox polling strategy does not suit your needs, I recommend exploring
339
+ the [Transaction Log Tailing](https://microservices.io/patterns/data/transaction-log-tailing.html) pattern.
277
340
  The current version of the python-cqrs package does not support the implementation of this pattern.
278
341
 
279
342
  > [!TIP]
280
- > However, it can be implemented using [Debezium + Kafka Connect](https://debezium.io/documentation/reference/stable/architecture.html),
281
- > which allows you to produce all newly created events within the Outbox storage directly to the corresponding topic in Kafka (or any other broker).
282
-
343
+ > However, it can be implemented
344
+ > using [Debezium + Kafka Connect](https://debezium.io/documentation/reference/stable/architecture.html),
345
+ > which allows you to produce all newly created events within the Outbox storage directly to the corresponding topic in
346
+ > Kafka (or any other broker).
283
347
 
284
348
  ## DI container
285
349
 
286
- Use the following example to set up dependency injection in your command, query and event handlers. This will make dependency management simpler.
350
+ Use the following example to set up dependency injection in your command, query and event handlers. This will make
351
+ dependency management simpler.
287
352
 
288
353
  ```python
289
354
  import di
@@ -309,6 +374,8 @@ def setup_di() -> di.Container:
309
374
  return container
310
375
  ```
311
376
 
377
+ A complete example can be found in
378
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/dependency_injection.py)
312
379
 
313
380
  ## Mapping
314
381
 
@@ -333,10 +400,11 @@ def init_events(mapper: events.EventMap) -> None:
333
400
  mapper.bind(events.ECSTEvent[event_models.ECSTMeetingRoomClosed], event_handlers.UpdateMeetingRoomReadModelHandler)
334
401
  ```
335
402
 
336
-
337
403
  ## Bootstrap
338
404
 
339
- The `python-cqrs` package implements a set of bootstrap utilities designed to simplify the initial configuration of an application.
405
+ The `python-cqrs` package implements a set of bootstrap utilities designed to simplify the initial configuration of an
406
+ application.
407
+
340
408
  ```python
341
409
  import functools
342
410
 
@@ -345,6 +413,7 @@ from cqrs.requests import bootstrap as request_bootstrap
345
413
 
346
414
  from app import dependencies, mapping, orm
347
415
 
416
+
348
417
  @functools.lru_cache
349
418
  def mediator_factory():
350
419
  return request_bootstrap.bootstrap(
@@ -365,12 +434,15 @@ def event_mediator_factory():
365
434
  )
366
435
  ```
367
436
 
368
- ## Integaration with presentation layers
437
+ ## Integration with presentation layers
369
438
 
370
- >[!TIP]
371
- > I recommend reading the useful paper [Onion Architecture Used in Software Development](https://www.researchgate.net/publication/371006360_Onion_Architecture_Used_in_Software_Development).
439
+ > [!TIP]
440
+ > I recommend reading the useful
441
+ >
442
+ paper [Onion Architecture Used in Software Development](https://www.researchgate.net/publication/371006360_Onion_Architecture_Used_in_Software_Development).
372
443
  > Separating user interaction and use-cases into Application and Presentation layers is a good practice.
373
- > This can improve the `Testability`, `Maintainability`, `Scalability` of the application. It also provides benefits such as `Separation of Concerns`.
444
+ > This can improve the `Testability`, `Maintainability`, `Scalability` of the application. It also provides benefits
445
+ > such as `Separation of Concerns`.
374
446
 
375
447
  ### FastAPI requests handling
376
448
 
@@ -394,34 +466,62 @@ async def join_metting(
394
466
  ):
395
467
  await mediator.send(commands.JoinMeetingCommand(meeting_id=meeting_id, user_id=user_id))
396
468
  return {"result": "ok"}
397
-
398
469
  ```
399
470
 
471
+ A complete example can be found in
472
+ the [documentation](https://github.com/vadikko2/cqrs/blob/master/examples/fastapi_integration.py)
473
+
400
474
  ### Kafka events consuming
401
475
 
402
- If you build interaction by events over brocker like `Kafka`, you can to implement an event consumer on your application's side,
476
+ If you build interaction by events over broker like `Kafka`, you can to implement an event consumer on your
477
+ application's side,
403
478
  which will call the appropriate handler for each event.
404
479
  An example of handling events from `Kafka` is provided below.
405
480
 
406
481
  ```python
407
- import aiokafka
408
482
  import cqrs
409
- import orjson
410
483
 
411
- from app import events
484
+ import pydantic
485
+ import faststream
486
+ from faststream import kafka
487
+
488
+ broker = kafka.KafkaBroker(bootstrap_servers=["localhost:9092"])
489
+ app = faststream.FastStream(broker)
412
490
 
413
- class OnEvent:
414
491
 
415
- def __init__(
416
- self,
417
- event_mediator: cqrs.EventMediator
418
- ):
419
- self._event_mediator = event_mediator
492
+ class HelloWorldPayload(pydantic.BaseModel):
493
+ hello: str = pydantic.Field(default="Hello")
494
+ world: str = pydantic.Field(default="World")
420
495
 
421
- async def __call__(self, kafka_message: aiokafka.ConsumerRecord) -> None:
422
- event = cqrs.ECSTEvent[events.ECSTMeetingRoomClosed].model_validate(
423
- orjson.loads(kafka_message.value),
424
- context={"assume_validated": True},
425
- )
426
- await self._event_mediator.send(event)
496
+
497
+ class HelloWorldECSTEventHandler(cqrs.EventHandler[cqrs.ECSTEvent[HelloWorldPayload]]):
498
+ async def handle(self, event: cqrs.ECSTEvent[HelloWorldPayload]) -> None:
499
+ print(f"{event.payload.hello} {event.payload.world}") # type: ignore
500
+
501
+
502
+ @broker.subscriber(
503
+ "hello_world",
504
+ group_id="examples",
505
+ auto_commit=False,
506
+ value_deserializer=value_deserializer,
507
+ )
508
+ async def hello_world_event_handler(
509
+ body: cqrs.ECSTEvent[HelloWorldPayload] | None,
510
+ msg: kafka.KafkaMessage,
511
+ mediator: cqrs.EventMediator = faststream.Depends(mediator_factory),
512
+ ):
513
+ if body is not None:
514
+ await mediator.send(body)
515
+ await msg.ack()
427
516
  ```
517
+
518
+ A complete example can be found in
519
+ the [documentation](https://github.com/vadikko2/python-cqrs/blob/master/examples/kafka_event_consuming.py)
520
+
521
+ ## Protobuf messaging
522
+
523
+ The `python-cqrs` package supports integration with [protobuf](https://developers.google.com/protocol-buffers/).\
524
+ Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data –
525
+ think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use
526
+ special generated source code to easily write and read your structured data to and from a variety of data streams and
527
+ using a variety of languages.