Skip to content

Commit 79f9558

Browse files
committed
collect domain event from aggregate roots when command handler is executed
1 parent 204ee71 commit 79f9558

25 files changed

+178
-150
lines changed

poetry.lock

Lines changed: 15 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ click = "8.0.4"
2828
httpx = "^0.23.1"
2929
requests = "^2.28.1"
3030
bcrypt = "^4.0.1"
31+
mypy = "^1.4.1"
3132

3233
[tool.poetry.dev-dependencies]
3334
poethepoet = "^0.10.0"
3435
pytest-cov = "^2.12.1"
35-
mypy = "^0.910"
3636
vulture = "^2.7"
3737

3838
[build-system]

src/modules/bidding/application/command/place_bid.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ def place_bid(
2121
command: PlaceBidCommand, listing_repository: ListingRepository
2222
) -> CommandResult:
2323
bidder = Bidder(id=command.bidder_id)
24-
bid = Bid(bidder=bidder, max_price=Money(command.amount))
24+
bid = Bid(bidder=bidder, max_price=Money(amount=command.amount))
2525

2626
listing = listing_repository.get_by_id(command.listing_id)
2727
listing.place_bid(bid)
28-
29-
return CommandResult.success()

src/modules/bidding/application/command/retract_bid.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,4 @@ def retract_bid(
1919
bidder = Bidder(id=command.bidder_id)
2020

2121
listing: Listing = listing_repository.get_by_id(id=command.listing_id)
22-
event = listing.retract_bid_of(bidder)
23-
24-
return CommandResult.success(event=event)
22+
listing.retract_bid_of(bidder)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .notify_outbid_winner import notify_outbid_winner
2+
from .when_listing_is_published_start_auction import (
3+
when_listing_is_published_start_auction,
4+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from modules.bidding.application import bidding_module
2+
from modules.bidding.domain.events import BidWasPlaced
3+
from seedwork.infrastructure.logging import logger
4+
5+
6+
@bidding_module.domain_event_handler
7+
def notify_outbid_winner(event: BidWasPlaced):
8+
logger.info(f"Message from a handler: Listing {event.listing_id} was published")

src/modules/bidding/application/event.py renamed to src/modules/bidding/application/event/when_listing_is_published_start_auction.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,3 @@ def when_listing_is_published_start_auction(
2020
ends_at=datetime.now() + timedelta(days=7),
2121
)
2222
listing_repository.add(listing)
23-
return EventResult.success()

src/modules/bidding/domain/entities.py

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
from datetime import datetime, timedelta
33
from typing import Optional
44

5+
from modules.bidding.domain.events import (
6+
BidWasPlaced,
7+
BidWasRetracted,
8+
HighestBidderWasOutbid,
9+
ListingWasCancelled,
10+
)
511
from modules.bidding.domain.rules import (
612
BidCanBeRetracted,
713
ListingCanBeCancelled,
8-
PlacedBidMustBeGreaterOrEqualThanNextMinimumBid,
14+
PriceOfPlacedBidMustBeGreaterOrEqualThanNextMinimumPrice,
915
)
1016
from modules.bidding.domain.value_objects import Bid, Bidder, Seller
1117
from seedwork.domain.entities import AggregateRoot
@@ -26,18 +32,6 @@ class ListingCannotBeCancelled(DomainException):
2632
...
2733

2834

29-
class BidPlacedEvent(DomainEvent):
30-
...
31-
32-
33-
class BidRetractedEvent(DomainEvent):
34-
...
35-
36-
37-
class ListingCancelledEvent(DomainEvent):
38-
...
39-
40-
4135
@dataclass(kw_only=True)
4236
class Listing(AggregateRoot[GenericUUID]):
4337
seller: Seller
@@ -61,34 +55,57 @@ def next_minimum_price(self) -> Money:
6155
return self.current_price + Money(amount=1, currency=self.ask_price.currency)
6256

6357
# public commands
64-
def place_bid(self, bid: Bid) -> DomainEvent:
58+
def place_bid(self, bid: Bid):
6559
"""Public method"""
6660
self.check_rule(
67-
PlacedBidMustBeGreaterOrEqualThanNextMinimumBid(
61+
PriceOfPlacedBidMustBeGreaterOrEqualThanNextMinimumPrice(
6862
current_price=bid.max_price, next_minimum_price=self.next_minimum_price
6963
)
7064
)
7165

66+
previous_winner_id = self.highest_bid.bidder.id if self.highest_bid else None
67+
current_winner_id = bid.bidder.id
68+
7269
if self.has_bid_placed_by(bidder=bid.bidder):
7370
self._update_bid(bid)
7471
else:
7572
self._add_bid(bid)
7673

77-
return BidPlacedEvent(
78-
listing_id=self.id, bidder=bid.bidder, max_price=bid.max_price
74+
self.register_event(
75+
BidWasPlaced(
76+
listing_id=self.id,
77+
bidder_id=bid.bidder.id,
78+
)
7979
)
8080

81-
def retract_bid_of(self, bidder: Bidder) -> DomainEvent:
81+
# if there was previous winner...
82+
if previous_winner_id is not None and previous_winner_id != current_winner_id:
83+
self.register_event(
84+
HighestBidderWasOutbid(
85+
listing_id=self.id,
86+
outbid_bidder_id=previous_winner_id,
87+
)
88+
)
89+
90+
def retract_bid_of(self, bidder: Bidder):
8291
"""Public method"""
8392
bid = self.get_bid_of(bidder)
8493
self.check_rule(
8594
BidCanBeRetracted(listing_ends_at=self.ends_at, bid_placed_at=bid.placed_at)
8695
)
8796

8897
self._remove_bid_of(bidder=bidder)
89-
return BidRetractedEvent(listing_id=self.id, bidder_id=bidder.id)
98+
self.register_event(
99+
BidWasRetracted(
100+
listing_id=self.id,
101+
retracting_bidder_id=bidder.id,
102+
winning_bidder_id=self.highest_bid.bidder.id
103+
if self.highest_bid
104+
else None,
105+
)
106+
)
90107

91-
def cancel(self) -> DomainEvent:
108+
def cancel(self):
92109
"""
93110
Seller can cancel a listing (end a listing early). Listing must be eligible to cancelled,
94111
depending on time left and if bids have been placed.
@@ -100,14 +117,13 @@ def cancel(self) -> DomainEvent:
100117
)
101118
)
102119
self.ends_at = datetime.utcnow()
103-
return ListingCancelledEvent(listing_id=self.id)
120+
self.register_event(ListingWasCancelled(listing_id=self.id))
104121

105122
def end(self) -> DomainEvent:
106123
"""
107124
Ends listing.
108125
"""
109126
raise NotImplementedError()
110-
return []
111127

112128
# public queries
113129
def get_bid_of(self, bidder: Bidder) -> Bid:
@@ -126,7 +142,7 @@ def has_bid_placed_by(self, bidder: Bidder) -> bool:
126142
return True
127143

128144
@property
129-
def winning_bid(self) -> Optional[Bid]:
145+
def highest_bid(self) -> Optional[Bid]:
130146
try:
131147
highest_bid = max(self.bids, key=lambda bid: bid.max_price)
132148
except ValueError:
Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,20 @@
1-
class PlacedBidIsGreaterThanCurrentWinningBid:
1+
from seedwork.domain.events import DomainEvent
2+
from seedwork.domain.value_objects import GenericUUID
3+
4+
5+
class BidWasPlaced(DomainEvent):
6+
listing_id: GenericUUID
7+
bidder_id: GenericUUID
8+
9+
10+
class HighestBidderWasOutbid(DomainEvent):
11+
listing_id: GenericUUID
12+
outbid_bidder_id: GenericUUID
13+
14+
15+
class BidWasRetracted(DomainEvent):
16+
...
17+
18+
19+
class ListingWasCancelled(DomainEvent):
220
...

src/modules/bidding/domain/rules.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from seedwork.domain.value_objects import Money
77

88

9-
class PlacedBidMustBeGreaterOrEqualThanNextMinimumBid(BusinessRule):
9+
class PriceOfPlacedBidMustBeGreaterOrEqualThanNextMinimumPrice(BusinessRule):
1010
__message = "Placed bid must be greater or equal than {next_minimum_price}"
1111

1212
current_price: Money

0 commit comments

Comments
 (0)