Skip to content

Commit 50ef84f

Browse files
committed
add listing repository
1 parent 0073ccc commit 50ef84f

32 files changed

Lines changed: 600 additions & 196 deletions

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,16 @@ Event storming technique was used to discover the business domain and the most i
7272
![](docs/images/auctions_ContextMap.png)
7373

7474
Since `Payment` context will be provided by a 3rd party payments provider (via REST API), the downstream context (`Bidding`) must conform to whatever the upstream provides.
75+
76+
77+
## How to run this project
78+
79+
`poetry shell`
80+
81+
`poetry install`
82+
83+
`poe compose_up` - run in a separate shell to start the database
84+
85+
`poe start`
86+
87+
`poe test`

poetry.lock

Lines changed: 27 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ typed-ast = "^1.4.3"
2222
python-multipart = "^0.0.5"
2323
psycopg2-binary = "^2.9.2"
2424
freezegun = "^1.1.0"
25+
SQLAlchemy-Utils = "^0.38.3"
2526

2627
[tool.poetry.dev-dependencies]
2728
poethepoet = "^0.10.0"
@@ -37,6 +38,7 @@ build-backend = "poetry.core.masonry.api"
3738
test = { shell = "DATABASE_URL=postgresql://postgres:password@localhost:5433/postgres pytest src" }
3839
test_coverage = "pytest --cov=src --cov-report=html"
3940
start = "uvicorn src.api.main:app --reload"
41+
start_cli = { shell = "cd src && python -v" }
4042
compose_up = "docker-compose -f docker-compose.dev.yml up"
4143

4244
[tool.black]

src/cli/__main__.py

Lines changed: 105 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,120 @@
1313
# run with "cd src && python -m cli"
1414

1515
# configure logger prior to first usage
16+
import uuid
17+
from sqlalchemy.orm import Session
18+
19+
from modules.catalog.infrastructure.listing_repository import (
20+
PostgresJsonListingRepository,
21+
Base,
22+
)
23+
24+
25+
from modules.catalog.domain.entities import Listing, Money
26+
1627
LoggerFactory.configure(logger_name="cli")
1728

18-
# configure catalog module
29+
1930
container = Container()
2031
container.config.from_dict(
2132
dict(
22-
DATABASE_URL="postgresql://postgres:password@localhost/postgres",
33+
DATABASE_URL="sqlite+pysqlite:///:memory:",
2334
DEBUG=True,
2435
)
2536
)
2637

27-
# instantiate catalog module
28-
catalog_module = container.catalog_module()
38+
engine = container.engine()
39+
Base.metadata.create_all(engine)
2940

30-
logger.info("Application configured")
41+
listing_id = uuid.uuid4()
3142

32-
# let's generate a fake seller id for now
33-
seller_id = SellerRepository.next_id()
34-
35-
# interact with a catalog module by issuing queries and commands
36-
# use request context if you want to logically separate queries/commands
37-
# from each other in the logs
38-
with request_context:
39-
command = CreateListingDraftCommand(
40-
title="Foo", description="Bar", price=1, seller_id=seller_id
41-
)
42-
result = catalog_module.execute_command(command)
43-
print(result)
44-
if result.is_ok():
45-
logger.info("Draft added")
46-
else:
47-
logger.error(result.get_errors())
48-
49-
with request_context:
50-
query_result = catalog_module.execute_query(GetAllListings())
51-
logger.info(f"All listings: {query_result.result}")
52-
53-
with request_context:
54-
query_result = catalog_module.execute_query(
55-
GetListingsOfSeller(seller_id=seller_id)
43+
with Session(engine) as session:
44+
request_context.correlation_id.set(uuid.uuid4())
45+
logger.info("Session 1")
46+
print(engine, session)
47+
repo = PostgresJsonListingRepository(session)
48+
listing = Listing(
49+
id=listing_id,
50+
seller_id=uuid.uuid4(),
51+
title="Foo",
52+
description="",
53+
ask_price=Money(1),
5654
)
57-
logger.info(f"Listings of seller {seller_id}: {query_result.result}")
55+
repo.add(listing)
56+
57+
58+
with Session(engine) as session:
59+
request_context.correlation_id.set(uuid.uuid4())
60+
logger.info("Session 2")
61+
repo = PostgresJsonListingRepository(session)
62+
repo.get_by_id(listing_id)
63+
64+
65+
# # configure catalog module
66+
#
67+
#
68+
# # instantiate catalog module
69+
# catalog_module = container.catalog_module()
70+
#
71+
# logger.info("Application configured")
72+
#
73+
# # let's generate a fake seller id for now
74+
# seller_id = SellerRepository.next_id()
75+
#
76+
#
77+
# ###############################
78+
# from modules.catalog.infrastructure.listing_repository import PostgresJsonListingRepository
79+
#
80+
#
81+
# session = ...
82+
# repository = PostgresJsonListingRepository(sqla_session=session)
83+
84+
85+
# from contextlib import contextmanager
86+
# from modules.catalog.module import CatalogModule
87+
#
88+
# @contextmanager
89+
# def business_tansaction():
90+
# try:
91+
# unit_of_work = ...
92+
#
93+
# yield CatalogModule(unit_of_work, listing_repository=container.listing_repository())
94+
# finally:
95+
# ...
96+
#
97+
#
98+
#
99+
#
100+
# with BusinessTransactionOn(CatalogModule) as catalog_module:
101+
# catalog_module.foo_repo()
102+
#
103+
#
104+
#
105+
# with catalog_module as transaction:
106+
# print(transaction)
107+
108+
109+
#
110+
# # interact with a catalog module by issuing queries and commands
111+
# # use request context if you want to logically separate queries/commands
112+
# # from each other in the logs
113+
# with request_context:
114+
# command = CreateListingDraftCommand(
115+
# title="Foo", description="Bar", ask_price=1, seller_id=seller_id
116+
# )
117+
# result = catalog_module.execute_command(command)
118+
# print(result)
119+
# if result.is_ok():
120+
# logger.info("Draft added")
121+
# else:
122+
# logger.error(result.get_errors())
123+
#
124+
# with request_context:
125+
# query_result = catalog_module.execute_query(GetAllListings())
126+
# logger.info(f"All listings: {query_result.result}")
127+
#
128+
# with request_context:
129+
# query_result = catalog_module.execute_query(
130+
# GetListingsOfSeller(seller_id=seller_id)
131+
# )
132+
# logger.info(f"Listings of seller {seller_id}: {query_result.result}")

src/conftest.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@
77

88

99
@pytest.fixture
10-
def db_session():
10+
def engine():
1111
config = ApiConfig()
1212
engine = create_engine(config.DATABASE_URL, echo=config.DEBUG)
13+
1314
with engine.begin() as connection:
1415
Base.metadata.drop_all(connection)
1516
Base.metadata.create_all(connection)
1617

18+
return engine
19+
20+
21+
@pytest.fixture
22+
def db_session(engine):
23+
1724
with Session(engine) as session:
1825
yield session
Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from dataclasses import dataclass
2+
13
from seedwork.application.commands import Command
24
from seedwork.domain.value_objects import UUID, Money
35
from seedwork.application.command_handlers import CommandResult
@@ -7,19 +9,28 @@
79
from modules.catalog.domain.repositories import ListingRepository
810

911

12+
@dataclass
1013
class CreateListingDraftCommand(Command):
1114
"""A command for creating new listing in draft state"""
1215

1316
title: str
1417
description: str
15-
price: Money
18+
ask_price: Money
1619
seller_id: UUID
1720

1821

1922
@command_handler
2023
def create_listing_draft(
2124
command: CreateListingDraftCommand, repository: ListingRepository
2225
) -> CommandResult:
23-
listing = Listing(id=Listing.next_id(), **command.dict())
24-
repository.insert(listing)
25-
return CommandResult.ok(result=listing.id, events=[ListingDraftCreatedEvent(listing_id=listing.id)])
26+
listing = Listing(
27+
id=Listing.next_id(),
28+
title=command.title,
29+
description=command.description,
30+
ask_price=command.ask_price,
31+
seller_id=command.seller_id,
32+
)
33+
repository.add(listing)
34+
return CommandResult.ok(
35+
result=listing.id, events=[ListingDraftCreatedEvent(listing_id=listing.id)]
36+
)
Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
from dataclasses import dataclass
2+
13
from seedwork.application.commands import Command
2-
from seedwork.domain.value_objects import UUID
34
from seedwork.application.command_handlers import CommandResult
45
from seedwork.application.decorators import command_handler
56
from modules.catalog.domain.entities import Listing, Seller
7+
from modules.catalog.domain.value_objects import ListingId, SellerId
68
from modules.catalog.domain.repositories import ListingRepository, SellerRepository
79

8-
10+
@dataclass
911
class PublishListingCommand(Command):
10-
"""A command for publishing a listing in draft state"""
12+
"""A command for publishing a draft of a listing"""
1113

12-
listing_id: UUID
13-
seller_id: UUID
14+
listing_id: ListingId
15+
seller_id: SellerId
1416

1517

1618
@command_handler
@@ -19,12 +21,9 @@ def publish_listing(
1921
listing_repository: ListingRepository,
2022
seller_repository: SellerRepository,
2123
):
22-
listing: Listing = listing_repository.get_by_id(command.listing_id)
2324
seller: Seller = seller_repository.get_by_id(command.seller_id)
25+
listing: Listing = listing_repository.get_by_id(command.listing_id)
2426

2527
events = seller.publish_listing(listing)
2628

27-
# TODO: for now we need to manually persist the changes, but it should be handled automatically using "Unit of Work"
28-
listing_repository.update(listing)
29-
3029
return CommandResult.ok(events=events)
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1+
from dataclasses import dataclass
12
from seedwork.application.commands import Command
23
from seedwork.application.command_handlers import CommandResult
34
from seedwork.application.decorators import command_handler
45
from seedwork.domain.value_objects import Money, UUID
56
from modules.catalog.domain.entities import Listing
67
from modules.catalog.domain.repositories import ListingRepository
78

8-
9+
@dataclass
910
class UpdateListingDraftCommand(Command):
1011
"""A command for updating a listing"""
1112

1213
listing_id: UUID
1314
title: str
1415
description: str
15-
price: Money
16+
ask_price: Money
1617
modify_user_id: UUID
1718

1819

@@ -22,7 +23,6 @@ def update_listing_draft(
2223
) -> CommandResult:
2324
listing: Listing = repository.get_by_id(command.listing_id)
2425
events = listing.change_main_attributes(
25-
title=command.title, description=command.description, price=command.price
26+
title=command.title, description=command.description, ask_price=command.ask_price
2627
)
27-
repository.update(listing)
2828
return CommandResult.ok(events=events)

0 commit comments

Comments
 (0)