Skip to content

Commit 76f7546

Browse files
committed
Added "vertical slices" example.
1 parent feafe24 commit 76f7546

File tree

30 files changed

+1426
-166
lines changed

30 files changed

+1426
-166
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ install-packages:
3030

3131
.PHONY: poetry-update
3232
poetry-update:
33-
$(POETRY) lock
33+
$(POETRY) update
3434

3535
.PHONY: update
36-
update: poetry-update install
36+
update: poetry-update install-packages
3737

3838
.PHONY: lint
3939
lint: lint-black lint-ruff lint-isort lint-pyright lint-mypy #lint-dockerfile

docs/topics/examples.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Example applications
4343
examples/content-management
4444
examples/searchable-timestamps
4545
examples/fts-content-management
46+
examples/shop-vertical
4647

4748
.. _Example projections:
4849

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
.. _Vertical slices example:
2+
3+
Application 6 - Vertical slices
4+
===============================
5+
6+
This example demonstrates how to do event sourcing with the "vertical slice architecture" advocated by the event
7+
modelling community.
8+
9+
Get cart items
10+
--------------
11+
12+
.. literalinclude:: ../../../examples/shopvertical/slices/get_cart_items/query.py
13+
:pyobject: CartItem
14+
15+
.. literalinclude:: ../../../examples/shopvertical/slices/get_cart_items/query.py
16+
:pyobject: GetCartItems
17+
18+
.. literalinclude:: ../../../examples/shopvertical/slices/get_cart_items/test.py
19+
:pyobject: TestGetCartItems
20+
21+
Add item to cart
22+
----------------
23+
24+
.. literalinclude:: ../../../examples/shopvertical/slices/add_item_to_cart/cmd.py
25+
:pyobject: AddItemToCart
26+
27+
.. literalinclude:: ../../../examples/shopvertical/slices/add_item_to_cart/test.py
28+
:pyobject: TestAddItemToCart
29+
30+
Remove item from cart
31+
---------------------
32+
33+
.. literalinclude:: ../../../examples/shopvertical/slices/remove_item_from_cart/cmd.py
34+
:pyobject: RemoveItemFromCart
35+
36+
.. literalinclude:: ../../../examples/shopvertical/slices/remove_item_from_cart/test.py
37+
:pyobject: TestRemoveItemFromCart
38+
39+
Clear cart
40+
----------
41+
42+
.. literalinclude:: ../../../examples/shopvertical/slices/clear_cart/cmd.py
43+
:pyobject: ClearCart
44+
45+
.. literalinclude:: ../../../examples/shopvertical/slices/clear_cart/test.py
46+
:pyobject: TestClearCart
47+
48+
Submit cart
49+
-----------
50+
51+
.. literalinclude:: ../../../examples/shopvertical/slices/submit_cart/cmd.py
52+
:pyobject: SubmitCart
53+
54+
.. literalinclude:: ../../../examples/shopvertical/slices/submit_cart/test.py
55+
:pyobject: TestSubmitCart
56+
57+
Adjust product inventory
58+
------------------------
59+
60+
.. literalinclude:: ../../../examples/shopvertical/slices/adjust_product_inventory/cmd.py
61+
:pyobject: AdjustProductInventory
62+
63+
.. literalinclude:: ../../../examples/shopvertical/slices/adjust_product_inventory/test.py
64+
:pyobject: TestAdjustProductInventory
65+
66+
Events
67+
------
68+
69+
.. literalinclude:: ../../../examples/shopvertical/events.py
70+
71+
Exceptions
72+
----------
73+
74+
.. literalinclude:: ../../../examples/shopvertical/exceptions.py
75+
76+
Common code
77+
-----------
78+
79+
.. literalinclude:: ../../../examples/shopvertical/common.py
80+
81+
Integration test
82+
----------------
83+
84+
.. literalinclude:: ../../../examples/shopvertical/test.py
85+
:pyobject: TestShop

examples/aggregate7/orjsonpydantic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
class PydanticMapper(Mapper):
1717
def to_stored_event(self, domain_event: DomainEventProtocol) -> StoredEvent:
1818
topic = get_topic(domain_event.__class__)
19-
event_state = cast("BaseModel", domain_event).model_dump()
19+
event_state = cast("BaseModel", domain_event).model_dump(mode="json")
2020
stored_state = self.transcoder.encode(event_state)
2121
if self.compressor:
2222
stored_state = self.compressor.compress(stored_state)

examples/shopvertical/__init__.py

Whitespace-only changes.

examples/shopvertical/common.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from __future__ import annotations
2+
3+
from abc import ABC, abstractmethod
4+
from typing import TYPE_CHECKING, Any, cast
5+
6+
from eventsourcing.application import Application
7+
from eventsourcing.utils import get_topic
8+
from examples.aggregate7.orjsonpydantic import OrjsonTranscoder, PydanticMapper
9+
from examples.shopvertical.events import DomainEvent
10+
11+
if TYPE_CHECKING:
12+
from uuid import UUID
13+
14+
15+
class Command(ABC):
16+
@abstractmethod
17+
def handle(self, events: tuple[DomainEvent, ...]) -> tuple[DomainEvent, ...]:
18+
pass # pragma: no cover
19+
20+
@abstractmethod
21+
def execute(self) -> int | None:
22+
pass # pragma: no cover
23+
24+
25+
class Query(ABC):
26+
@abstractmethod
27+
def execute(self) -> Any:
28+
pass # pragma: no cover
29+
30+
31+
_event_store = Application(
32+
env={
33+
"TRANSCODER_TOPIC": get_topic(OrjsonTranscoder),
34+
"MAPPER_TOPIC": get_topic(PydanticMapper),
35+
}
36+
).events
37+
38+
39+
def get_events(originator_id: UUID) -> tuple[DomainEvent, ...]:
40+
return cast(tuple[DomainEvent, ...], tuple(_event_store.get(originator_id)))
41+
42+
43+
def put_events(events: tuple[DomainEvent, ...]) -> int | None:
44+
recordings = _event_store.put(events)
45+
return recordings[-1].notification.id if recordings else None

examples/shopvertical/events.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from __future__ import annotations
2+
3+
from decimal import Decimal
4+
from uuid import UUID
5+
6+
from pydantic import BaseModel, ConfigDict
7+
8+
9+
class DomainEvent(BaseModel, frozen=True):
10+
model_config = ConfigDict(extra="forbid")
11+
12+
originator_id: UUID
13+
originator_version: int
14+
15+
16+
class AddedItemToCart(DomainEvent, frozen=True):
17+
product_id: UUID
18+
name: str
19+
description: str
20+
price: Decimal
21+
22+
23+
class RemovedItemFromCart(DomainEvent, frozen=True):
24+
product_id: UUID
25+
26+
27+
class ClearedCart(DomainEvent, frozen=True):
28+
pass
29+
30+
31+
class SubmittedCart(DomainEvent, frozen=True):
32+
pass
33+
34+
35+
class AdjustedProductInventory(DomainEvent, frozen=True):
36+
adjustment: int
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class CartFullError(Exception):
2+
pass
3+
4+
5+
class ProductNotInCartError(Exception):
6+
pass
7+
8+
9+
class InsufficientInventoryError(Exception):
10+
pass

examples/shopvertical/slices/__init__.py

Whitespace-only changes.

examples/shopvertical/slices/add_item_to_cart/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)