Skip to content

Commit

Permalink
Application (#18)
Browse files Browse the repository at this point in the history
implementation of the Application class, refactor of event handling
  • Loading branch information
pgorecki authored May 16, 2023
1 parent f0c6d00 commit 88b7ac5
Show file tree
Hide file tree
Showing 34 changed files with 959 additions and 313 deletions.
2 changes: 1 addition & 1 deletion .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 14 additions & 18 deletions src/api/routers/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
from api.models import ListingIndexModel, ListingReadModel, ListingWriteModel
from api.shared import dependency
from config.container import Container, inject
from modules.catalog import CatalogModule
from modules.catalog.application.command import (
CreateListingDraftCommand,
DeleteListingDraftCommand,
)
from modules.catalog.application.query.get_all_listings import GetAllListings
from modules.catalog.application.query.get_listing_details import GetListingDetails
from seedwork.application import Application
from seedwork.domain.value_objects import Money
from seedwork.infrastructure.request_context import request_context

Expand All @@ -19,30 +19,28 @@
@router.get("/catalog", tags=["catalog"], response_model=ListingIndexModel)
@inject
async def get_all_listings(
module: CatalogModule = dependency(Container.catalog_module),
app: Application = dependency(Container.application),
):
"""
Shows all published listings in the catalog
"""
query = GetAllListings()
with module.unit_of_work():
query_result = module.execute_query(query)
return dict(data=query_result.payload)
query_result = app.execute_query(query)
return dict(data=query_result.payload)


@router.get("/catalog/{listing_id}", tags=["catalog"], response_model=ListingReadModel)
@inject
async def get_listing_details(
listing_id,
module: CatalogModule = dependency(Container.catalog_module),
app: Application = dependency(Container.application),
):
"""
Shows listing details
"""
query = GetListingDetails(listing_id=listing_id)
with module.unit_of_work():
query_result = module.execute_query(query)
return query_result.payload
query_result = app.execute_query(query)
return dict(data=query_result.payload)


@router.post(
Expand All @@ -51,7 +49,7 @@ async def get_listing_details(
@inject
async def create_listing(
request_body: ListingWriteModel,
module: CatalogModule = dependency(Container.catalog_module),
app: Application = dependency(Container.application),
):
"""
Creates a new listing.
Expand All @@ -62,12 +60,11 @@ async def create_listing(
ask_price=Money(request_body.ask_price_amount, request_body.ask_price_currency),
seller_id=request_context.current_user.id,
)
with module.unit_of_work():
command_result = module.execute_command(command)
command_result = app.execute_command(command)

query = GetListingDetails(listing_id=command_result.result)
query_result = module.execute_query(query)
return query_result.payload
query = GetListingDetails(listing_id=command_result.payload)
query_result = app.execute_query(query)
return dict(data=query_result.payload)


@router.delete(
Expand All @@ -76,13 +73,12 @@ async def create_listing(
@inject
async def delete_listing(
listing_id,
module: CatalogModule = dependency(Container.catalog_module),
app: Application = dependency(Container.application),
):
"""
Delete listing
"""
command = DeleteListingDraftCommand(
listing_id=listing_id,
)
with module.unit_of_work():
module.execute_command(command)
app.execute_command(command)
67 changes: 32 additions & 35 deletions src/api/tests/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@ def test_empty_catalog_list(api_client):
@pytest.mark.integration
def test_catalog_list_with_one_item(api, api_client):
# arrange
catalog_module = api.container.catalog_module()
with catalog_module.unit_of_work():
command_result = catalog_module.execute_command(
CreateListingDraftCommand(
title="Foo",
description="Bar",
ask_price=Money(10),
seller_id=UUID("00000000000000000000000000000002"),
)
app = api.container.application()
command_result = app.execute_command(
CreateListingDraftCommand(
title="Foo",
description="Bar",
ask_price=Money(10),
seller_id=UUID("00000000000000000000000000000002"),
)
)

# act
response = api_client.get("/catalog")
Expand All @@ -48,24 +47,23 @@ def test_catalog_list_with_one_item(api, api_client):
@pytest.mark.integration
def test_catalog_list_with_two_items(api, api_client):
# arrange
catalog_module = api.container.catalog_module()
with catalog_module.unit_of_work():
catalog_module.execute_command(
CreateListingDraftCommand(
title="Foo #1",
description="Bar",
ask_price=Money(10),
seller_id=UUID("00000000000000000000000000000002"),
)
app = api.container.application()
app.execute_command(
CreateListingDraftCommand(
title="Foo #1",
description="Bar",
ask_price=Money(10),
seller_id=UUID("00000000000000000000000000000002"),
)
catalog_module.execute_command(
CreateListingDraftCommand(
title="Foo #2",
description="Bar",
ask_price=Money(10),
seller_id=UUID("00000000000000000000000000000002"),
)
)
app.execute_command(
CreateListingDraftCommand(
title="Foo #2",
description="Bar",
ask_price=Money(10),
seller_id=UUID("00000000000000000000000000000002"),
)
)

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

@pytest.mark.integration
def test_catalog_delete_draft(api, api_client):
catalog_module = api.container.catalog_module()
with catalog_module.unit_of_work():
result = catalog_module.execute_command(
CreateListingDraftCommand(
title="Foo to be deleted",
description="Bar",
ask_price=Money(10),
seller_id=UUID("00000000000000000000000000000002"),
)
app = api.container.application()
command_result = app.execute_command(
CreateListingDraftCommand(
title="Foo to be deleted",
description="Bar",
ask_price=Money(10),
seller_id=UUID("00000000000000000000000000000002"),
)
)

response = api_client.delete(f"/catalog/{result.entity_id}")
response = api_client.delete(f"/catalog/{command_result.entity_id}")

assert response.status_code == 204

Expand Down
42 changes: 32 additions & 10 deletions src/cli/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import uuid
from contextlib import contextmanager

from sqlalchemy.orm import Session

from config.container import Container
from modules.catalog.application.command.create_listing_draft import (
CreateListingDraftCommand,
Expand All @@ -6,13 +11,28 @@
from modules.catalog.infrastructure.listing_repository import Base
from seedwork.infrastructure.logging import LoggerFactory, logger


@contextmanager
def unit_of_work(module):
from seedwork.infrastructure.request_context import request_context

with Session(engine) as db_session:
correlation_id = uuid.uuid4()
request_context.correlation_id.set(correlation_id)
with module.unit_of_work(
correlation_id=correlation_id, db_session=db_session
) as uow:
yield uow
db_session.commit()
request_context.correlation_id.set(None)


# a sample command line script to print all listings
# run with "cd src && python -m cli"

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


container = Container()
container.config.from_dict(
dict(
Expand All @@ -25,22 +45,24 @@
engine = container.engine()
Base.metadata.create_all(engine)

catalog_module = container.catalog_module()
app = container.application()


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

with catalog_module.unit_of_work():
logger.info(f"adding new draft")
catalog_module.execute_command(
CreateListingDraftCommand(
title="First listing", description=".", ask_price=Money(100), seller_id=None
)

logger.info(f"adding new draft")
command_result = app.execute_command(
CreateListingDraftCommand(
title="First listing", description=".", ask_price=Money(100), seller_id=None
)
)
print(command_result)

with catalog_module.unit_of_work() as uow:
with unit_of_work(app.catalog) as uow:
logger.info(f"executing unit of work")
count = uow.listing_repository.count()
logger.info(f"{count} listing in the repository")
29 changes: 24 additions & 5 deletions src/config/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from modules.catalog import CatalogModule
from modules.iam import IamModule
from seedwork.application.event_dispatcher import InMemoryEventDispatcher
from seedwork.application import Application
from seedwork.application.inbox_outbox import InMemoryOutbox


def _default(val):
Expand Down Expand Up @@ -47,6 +48,18 @@ def create_engine_once(config):
return engine


def create_app(name, version, config, engine, catalog_module, outbox) -> Application:
app = Application(
name=name,
version=version,
config=config,
engine=engine,
outbox=outbox,
)
app.add_module("catalog", catalog_module)
return app


class Container(containers.DeclarativeContainer):
"""Dependency Injection Container
Expand All @@ -57,16 +70,22 @@ class Container(containers.DeclarativeContainer):

config = providers.Configuration()
engine = providers.Singleton(create_engine_once, config)
domain_event_dispatcher = InMemoryEventDispatcher()
outbox = providers.Factory(InMemoryOutbox)

catalog_module = providers.Factory(
CatalogModule,
engine=engine,
domain_event_dispatcher=domain_event_dispatcher,
)

iam_module = providers.Factory(
IamModule,
)

application = providers.Factory(
create_app,
name="Auctions API",
version="0.1.0",
config=config,
engine=engine,
domain_event_dispatcher=domain_event_dispatcher,
catalog_module=catalog_module,
outbox=outbox,
)
28 changes: 27 additions & 1 deletion src/modules/catalog/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
Item catalog module
# Listing catalog module

This part is a seller portal. It allows sellers to create and manage their listings.

# Business rules

# User stories:

- [x] As a seller, I want to create a listing draft for a product I want to sell.

- [x] As a seller, I want to update a listing draft.

- [ ] As a seller, I want to delete a listing draft.

- [ ] As a seller, I want to publish a listing draft immediately.

- [ ] As a seller, I want to schedule a listing draft for publishing.

- [ ] As a seller, I want to end a listing immediately.

- [ ] As a seller, I want to view all my listings (published, unpublished, ended).

- [ ] As a seller, I want to view details of a listing.

- [ ] As a system, I want to notify a seller when a listing is published.

- [ ] As a system, I want to notify a seller when a listing is ended.
4 changes: 2 additions & 2 deletions src/modules/catalog/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from modules.catalog.application.command import (
CreateListingDraftCommand,
DeleteListingDraftCommand,
PublishListingCommand,
PublishListingDraftCommand,
UpdateListingDraftCommand,
)
from modules.catalog.application.query import (
Expand All @@ -21,7 +21,7 @@ class CatalogModule(BusinessModule):
CreateListingDraftCommand,
UpdateListingDraftCommand,
DeleteListingDraftCommand,
PublishListingCommand,
PublishListingDraftCommand,
)
supported_queries = (GetAllListings, GetListingDetails, GetListingsOfSeller)

Expand Down
2 changes: 1 addition & 1 deletion src/modules/catalog/application/command/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .create_listing_draft import CreateListingDraftCommand
from .delete_listing_draft import DeleteListingDraftCommand
from .publish_listing import PublishListingCommand
from .publish_listing_draft import PublishListingDraftCommand
from .update_listing_draft import UpdateListingDraftCommand
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from modules.catalog.domain.entities import Listing
from modules.catalog.domain.events import ListingDraftCreatedEvent
from modules.catalog.domain.repositories import ListingRepository
from modules.catalog.domain.value_objects import ListingStatus
from seedwork.application.command_handlers import CommandResult
from seedwork.application.commands import Command
from seedwork.application.decorators import command_handler
Expand Down
Loading

0 comments on commit 88b7ac5

Please sign in to comment.