Skip to content

Commit dee266b

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

File tree

36 files changed

+1696
-166
lines changed

36 files changed

+1696
-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: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
Add product to shop
10+
-------------------
11+
12+
.. literalinclude:: ../../../examples/shopvertical/slices/add_product_to_shop/cmd.py
13+
:pyobject: AddProductToShop
14+
15+
.. literalinclude:: ../../../examples/shopvertical/slices/add_product_to_shop/test.py
16+
:pyobject: TestAddProductToShop
17+
18+
List products
19+
-------------
20+
21+
.. literalinclude:: ../../../examples/shopvertical/slices/list_products_in_shop/query.py
22+
:pyobject: Product
23+
24+
.. literalinclude:: ../../../examples/shopvertical/slices/list_products_in_shop/query.py
25+
:pyobject: ListProductsInShop
26+
27+
.. literalinclude:: ../../../examples/shopvertical/slices/list_products_in_shop/test.py
28+
:pyobject: TestListProductsInShop
29+
30+
Get cart items
31+
--------------
32+
33+
.. literalinclude:: ../../../examples/shopvertical/slices/get_cart_items/query.py
34+
:pyobject: CartItem
35+
36+
.. literalinclude:: ../../../examples/shopvertical/slices/get_cart_items/query.py
37+
:pyobject: GetCartItems
38+
39+
.. literalinclude:: ../../../examples/shopvertical/slices/get_cart_items/test.py
40+
:pyobject: TestGetCartItems
41+
42+
Add item to cart
43+
----------------
44+
45+
.. literalinclude:: ../../../examples/shopvertical/slices/add_item_to_cart/cmd.py
46+
:pyobject: AddItemToCart
47+
48+
.. literalinclude:: ../../../examples/shopvertical/slices/add_item_to_cart/test.py
49+
:pyobject: TestAddItemToCart
50+
51+
Remove item from cart
52+
---------------------
53+
54+
.. literalinclude:: ../../../examples/shopvertical/slices/remove_item_from_cart/cmd.py
55+
:pyobject: RemoveItemFromCart
56+
57+
.. literalinclude:: ../../../examples/shopvertical/slices/remove_item_from_cart/test.py
58+
:pyobject: TestRemoveItemFromCart
59+
60+
Clear cart
61+
----------
62+
63+
.. literalinclude:: ../../../examples/shopvertical/slices/clear_cart/cmd.py
64+
:pyobject: ClearCart
65+
66+
.. literalinclude:: ../../../examples/shopvertical/slices/clear_cart/test.py
67+
:pyobject: TestClearCart
68+
69+
Submit cart
70+
-----------
71+
72+
.. literalinclude:: ../../../examples/shopvertical/slices/submit_cart/cmd.py
73+
:pyobject: SubmitCart
74+
75+
.. literalinclude:: ../../../examples/shopvertical/slices/submit_cart/test.py
76+
:pyobject: TestSubmitCart
77+
78+
Adjust product inventory
79+
------------------------
80+
81+
.. literalinclude:: ../../../examples/shopvertical/slices/adjust_product_inventory/cmd.py
82+
:pyobject: AdjustProductInventory
83+
84+
.. literalinclude:: ../../../examples/shopvertical/slices/adjust_product_inventory/test.py
85+
:pyobject: TestAdjustProductInventory
86+
87+
Events
88+
------
89+
90+
.. literalinclude:: ../../../examples/shopvertical/events.py
91+
92+
Exceptions
93+
----------
94+
95+
.. literalinclude:: ../../../examples/shopvertical/exceptions.py
96+
97+
Common code
98+
-----------
99+
100+
.. literalinclude:: ../../../examples/shopvertical/common.py
101+
102+
Integration test
103+
----------------
104+
105+
.. literalinclude:: ../../../examples/shopvertical/test.py
106+
: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: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
_app = Application(
32+
env={
33+
"TRANSCODER_TOPIC": get_topic(OrjsonTranscoder),
34+
"MAPPER_TOPIC": get_topic(PydanticMapper),
35+
}
36+
)
37+
38+
39+
def get_events(originator_id: UUID) -> tuple[DomainEvent, ...]:
40+
return cast(tuple[DomainEvent, ...], tuple(_app.events.get(originator_id)))
41+
42+
43+
def put_events(events: tuple[DomainEvent, ...]) -> int | None:
44+
recordings = _app.events.put(events)
45+
return recordings[-1].notification.id if recordings else None
46+
47+
48+
def get_all_events() -> tuple[DomainEvent, ...]:
49+
return cast(
50+
tuple[DomainEvent, ...],
51+
tuple(
52+
map(
53+
_app.mapper.to_domain_event,
54+
_app.recorder.select_notifications(start=0, limit=1000000),
55+
)
56+
),
57+
)

examples/shopvertical/events.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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
37+
38+
39+
class AddedProductToShop(DomainEvent, frozen=True):
40+
name: str
41+
description: str
42+
price: Decimal
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from __future__ import annotations
2+
3+
4+
class CartFullError(Exception):
5+
pass
6+
7+
8+
class ProductNotInCartError(Exception):
9+
pass
10+
11+
12+
class InsufficientInventoryError(Exception):
13+
pass
14+
15+
16+
class ProductExistsError(Exception):
17+
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)