Skip to content

Commit 88b7ac5

Browse files
authored
Application (#18)
implementation of the Application class, refactor of event handling
1 parent f0c6d00 commit 88b7ac5

34 files changed

+959
-313
lines changed

.idea/vcs.xml

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

src/api/routers/catalog.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
from api.models import ListingIndexModel, ListingReadModel, ListingWriteModel
44
from api.shared import dependency
55
from config.container import Container, inject
6-
from modules.catalog import CatalogModule
76
from modules.catalog.application.command import (
87
CreateListingDraftCommand,
98
DeleteListingDraftCommand,
109
)
1110
from modules.catalog.application.query.get_all_listings import GetAllListings
1211
from modules.catalog.application.query.get_listing_details import GetListingDetails
12+
from seedwork.application import Application
1313
from seedwork.domain.value_objects import Money
1414
from seedwork.infrastructure.request_context import request_context
1515

@@ -19,30 +19,28 @@
1919
@router.get("/catalog", tags=["catalog"], response_model=ListingIndexModel)
2020
@inject
2121
async def get_all_listings(
22-
module: CatalogModule = dependency(Container.catalog_module),
22+
app: Application = dependency(Container.application),
2323
):
2424
"""
2525
Shows all published listings in the catalog
2626
"""
2727
query = GetAllListings()
28-
with module.unit_of_work():
29-
query_result = module.execute_query(query)
30-
return dict(data=query_result.payload)
28+
query_result = app.execute_query(query)
29+
return dict(data=query_result.payload)
3130

3231

3332
@router.get("/catalog/{listing_id}", tags=["catalog"], response_model=ListingReadModel)
3433
@inject
3534
async def get_listing_details(
3635
listing_id,
37-
module: CatalogModule = dependency(Container.catalog_module),
36+
app: Application = dependency(Container.application),
3837
):
3938
"""
4039
Shows listing details
4140
"""
4241
query = GetListingDetails(listing_id=listing_id)
43-
with module.unit_of_work():
44-
query_result = module.execute_query(query)
45-
return query_result.payload
42+
query_result = app.execute_query(query)
43+
return dict(data=query_result.payload)
4644

4745

4846
@router.post(
@@ -51,7 +49,7 @@ async def get_listing_details(
5149
@inject
5250
async def create_listing(
5351
request_body: ListingWriteModel,
54-
module: CatalogModule = dependency(Container.catalog_module),
52+
app: Application = dependency(Container.application),
5553
):
5654
"""
5755
Creates a new listing.
@@ -62,12 +60,11 @@ async def create_listing(
6260
ask_price=Money(request_body.ask_price_amount, request_body.ask_price_currency),
6361
seller_id=request_context.current_user.id,
6462
)
65-
with module.unit_of_work():
66-
command_result = module.execute_command(command)
63+
command_result = app.execute_command(command)
6764

68-
query = GetListingDetails(listing_id=command_result.result)
69-
query_result = module.execute_query(query)
70-
return query_result.payload
65+
query = GetListingDetails(listing_id=command_result.payload)
66+
query_result = app.execute_query(query)
67+
return dict(data=query_result.payload)
7168

7269

7370
@router.delete(
@@ -76,13 +73,12 @@ async def create_listing(
7673
@inject
7774
async def delete_listing(
7875
listing_id,
79-
module: CatalogModule = dependency(Container.catalog_module),
76+
app: Application = dependency(Container.application),
8077
):
8178
"""
8279
Delete listing
8380
"""
8481
command = DeleteListingDraftCommand(
8582
listing_id=listing_id,
8683
)
87-
with module.unit_of_work():
88-
module.execute_command(command)
84+
app.execute_command(command)

src/api/tests/test_catalog.py

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@ def test_empty_catalog_list(api_client):
1414
@pytest.mark.integration
1515
def test_catalog_list_with_one_item(api, api_client):
1616
# arrange
17-
catalog_module = api.container.catalog_module()
18-
with catalog_module.unit_of_work():
19-
command_result = catalog_module.execute_command(
20-
CreateListingDraftCommand(
21-
title="Foo",
22-
description="Bar",
23-
ask_price=Money(10),
24-
seller_id=UUID("00000000000000000000000000000002"),
25-
)
17+
app = api.container.application()
18+
command_result = app.execute_command(
19+
CreateListingDraftCommand(
20+
title="Foo",
21+
description="Bar",
22+
ask_price=Money(10),
23+
seller_id=UUID("00000000000000000000000000000002"),
2624
)
25+
)
2726

2827
# act
2928
response = api_client.get("/catalog")
@@ -48,24 +47,23 @@ def test_catalog_list_with_one_item(api, api_client):
4847
@pytest.mark.integration
4948
def test_catalog_list_with_two_items(api, api_client):
5049
# arrange
51-
catalog_module = api.container.catalog_module()
52-
with catalog_module.unit_of_work():
53-
catalog_module.execute_command(
54-
CreateListingDraftCommand(
55-
title="Foo #1",
56-
description="Bar",
57-
ask_price=Money(10),
58-
seller_id=UUID("00000000000000000000000000000002"),
59-
)
50+
app = api.container.application()
51+
app.execute_command(
52+
CreateListingDraftCommand(
53+
title="Foo #1",
54+
description="Bar",
55+
ask_price=Money(10),
56+
seller_id=UUID("00000000000000000000000000000002"),
6057
)
61-
catalog_module.execute_command(
62-
CreateListingDraftCommand(
63-
title="Foo #2",
64-
description="Bar",
65-
ask_price=Money(10),
66-
seller_id=UUID("00000000000000000000000000000002"),
67-
)
58+
)
59+
app.execute_command(
60+
CreateListingDraftCommand(
61+
title="Foo #2",
62+
description="Bar",
63+
ask_price=Money(10),
64+
seller_id=UUID("00000000000000000000000000000002"),
6865
)
66+
)
6967

7068
# act
7169
response = api_client.get("/catalog")
@@ -89,18 +87,17 @@ def test_catalog_create_draft_fails_due_to_incomplete_data(api, api_client):
8987

9088
@pytest.mark.integration
9189
def test_catalog_delete_draft(api, api_client):
92-
catalog_module = api.container.catalog_module()
93-
with catalog_module.unit_of_work():
94-
result = catalog_module.execute_command(
95-
CreateListingDraftCommand(
96-
title="Foo to be deleted",
97-
description="Bar",
98-
ask_price=Money(10),
99-
seller_id=UUID("00000000000000000000000000000002"),
100-
)
90+
app = api.container.application()
91+
command_result = app.execute_command(
92+
CreateListingDraftCommand(
93+
title="Foo to be deleted",
94+
description="Bar",
95+
ask_price=Money(10),
96+
seller_id=UUID("00000000000000000000000000000002"),
10197
)
98+
)
10299

103-
response = api_client.delete(f"/catalog/{result.entity_id}")
100+
response = api_client.delete(f"/catalog/{command_result.entity_id}")
104101

105102
assert response.status_code == 204
106103

src/cli/__main__.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import uuid
2+
from contextlib import contextmanager
3+
4+
from sqlalchemy.orm import Session
5+
16
from config.container import Container
27
from modules.catalog.application.command.create_listing_draft import (
38
CreateListingDraftCommand,
@@ -6,13 +11,28 @@
611
from modules.catalog.infrastructure.listing_repository import Base
712
from seedwork.infrastructure.logging import LoggerFactory, logger
813

14+
15+
@contextmanager
16+
def unit_of_work(module):
17+
from seedwork.infrastructure.request_context import request_context
18+
19+
with Session(engine) as db_session:
20+
correlation_id = uuid.uuid4()
21+
request_context.correlation_id.set(correlation_id)
22+
with module.unit_of_work(
23+
correlation_id=correlation_id, db_session=db_session
24+
) as uow:
25+
yield uow
26+
db_session.commit()
27+
request_context.correlation_id.set(None)
28+
29+
930
# a sample command line script to print all listings
1031
# run with "cd src && python -m cli"
1132

1233
# configure logger prior to first usage
1334
LoggerFactory.configure(logger_name="cli")
1435

15-
1636
container = Container()
1737
container.config.from_dict(
1838
dict(
@@ -25,22 +45,24 @@
2545
engine = container.engine()
2646
Base.metadata.create_all(engine)
2747

28-
catalog_module = container.catalog_module()
48+
app = container.application()
49+
2950

30-
with catalog_module.unit_of_work() as uow:
51+
with unit_of_work(app.catalog) as uow:
3152
logger.info(f"executing unit of work")
3253
count = uow.listing_repository.count()
3354
logger.info(f"{count} listing in the repository")
3455

35-
with catalog_module.unit_of_work():
36-
logger.info(f"adding new draft")
37-
catalog_module.execute_command(
38-
CreateListingDraftCommand(
39-
title="First listing", description=".", ask_price=Money(100), seller_id=None
40-
)
56+
57+
logger.info(f"adding new draft")
58+
command_result = app.execute_command(
59+
CreateListingDraftCommand(
60+
title="First listing", description=".", ask_price=Money(100), seller_id=None
4161
)
62+
)
63+
print(command_result)
4264

43-
with catalog_module.unit_of_work() as uow:
65+
with unit_of_work(app.catalog) as uow:
4466
logger.info(f"executing unit of work")
4567
count = uow.listing_repository.count()
4668
logger.info(f"{count} listing in the repository")

src/config/container.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
from modules.catalog import CatalogModule
66
from modules.iam import IamModule
7-
from seedwork.application.event_dispatcher import InMemoryEventDispatcher
7+
from seedwork.application import Application
8+
from seedwork.application.inbox_outbox import InMemoryOutbox
89

910

1011
def _default(val):
@@ -47,6 +48,18 @@ def create_engine_once(config):
4748
return engine
4849

4950

51+
def create_app(name, version, config, engine, catalog_module, outbox) -> Application:
52+
app = Application(
53+
name=name,
54+
version=version,
55+
config=config,
56+
engine=engine,
57+
outbox=outbox,
58+
)
59+
app.add_module("catalog", catalog_module)
60+
return app
61+
62+
5063
class Container(containers.DeclarativeContainer):
5164
"""Dependency Injection Container
5265
@@ -57,16 +70,22 @@ class Container(containers.DeclarativeContainer):
5770

5871
config = providers.Configuration()
5972
engine = providers.Singleton(create_engine_once, config)
60-
domain_event_dispatcher = InMemoryEventDispatcher()
73+
outbox = providers.Factory(InMemoryOutbox)
6174

6275
catalog_module = providers.Factory(
6376
CatalogModule,
64-
engine=engine,
65-
domain_event_dispatcher=domain_event_dispatcher,
6677
)
6778

6879
iam_module = providers.Factory(
6980
IamModule,
81+
)
82+
83+
application = providers.Factory(
84+
create_app,
85+
name="Auctions API",
86+
version="0.1.0",
87+
config=config,
7088
engine=engine,
71-
domain_event_dispatcher=domain_event_dispatcher,
89+
catalog_module=catalog_module,
90+
outbox=outbox,
7291
)

src/modules/catalog/README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,27 @@
1-
Item catalog module
1+
# Listing catalog module
2+
3+
This part is a seller portal. It allows sellers to create and manage their listings.
4+
5+
# Business rules
6+
7+
# User stories:
8+
9+
- [x] As a seller, I want to create a listing draft for a product I want to sell.
10+
11+
- [x] As a seller, I want to update a listing draft.
12+
13+
- [ ] As a seller, I want to delete a listing draft.
14+
15+
- [ ] As a seller, I want to publish a listing draft immediately.
16+
17+
- [ ] As a seller, I want to schedule a listing draft for publishing.
18+
19+
- [ ] As a seller, I want to end a listing immediately.
20+
21+
- [ ] As a seller, I want to view all my listings (published, unpublished, ended).
22+
23+
- [ ] As a seller, I want to view details of a listing.
24+
25+
- [ ] As a system, I want to notify a seller when a listing is published.
26+
27+
- [ ] As a system, I want to notify a seller when a listing is ended.

src/modules/catalog/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from modules.catalog.application.command import (
22
CreateListingDraftCommand,
33
DeleteListingDraftCommand,
4-
PublishListingCommand,
4+
PublishListingDraftCommand,
55
UpdateListingDraftCommand,
66
)
77
from modules.catalog.application.query import (
@@ -21,7 +21,7 @@ class CatalogModule(BusinessModule):
2121
CreateListingDraftCommand,
2222
UpdateListingDraftCommand,
2323
DeleteListingDraftCommand,
24-
PublishListingCommand,
24+
PublishListingDraftCommand,
2525
)
2626
supported_queries = (GetAllListings, GetListingDetails, GetListingsOfSeller)
2727

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .create_listing_draft import CreateListingDraftCommand
22
from .delete_listing_draft import DeleteListingDraftCommand
3-
from .publish_listing import PublishListingCommand
3+
from .publish_listing_draft import PublishListingDraftCommand
44
from .update_listing_draft import UpdateListingDraftCommand

src/modules/catalog/application/command/create_listing_draft.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from modules.catalog.domain.entities import Listing
44
from modules.catalog.domain.events import ListingDraftCreatedEvent
55
from modules.catalog.domain.repositories import ListingRepository
6+
from modules.catalog.domain.value_objects import ListingStatus
67
from seedwork.application.command_handlers import CommandResult
78
from seedwork.application.commands import Command
89
from seedwork.application.decorators import command_handler

0 commit comments

Comments
 (0)