python-hexagonal 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hexagonal/__init__.py +2 -0
- hexagonal/adapters/drivens/buses/base/__init__.py +15 -0
- hexagonal/adapters/drivens/buses/base/command_bus.py +69 -0
- hexagonal/adapters/drivens/buses/base/event_bus.py +160 -0
- hexagonal/adapters/drivens/buses/base/infrastructure.py +38 -0
- hexagonal/adapters/drivens/buses/base/message_bus.py +73 -0
- hexagonal/adapters/drivens/buses/base/query.py +82 -0
- hexagonal/adapters/drivens/buses/base/utils.py +1 -0
- hexagonal/adapters/drivens/buses/inmemory/__init__.py +12 -0
- hexagonal/adapters/drivens/buses/inmemory/command_bus.py +70 -0
- hexagonal/adapters/drivens/buses/inmemory/event_bus.py +69 -0
- hexagonal/adapters/drivens/buses/inmemory/infra.py +49 -0
- hexagonal/adapters/drivens/mappers.py +127 -0
- hexagonal/adapters/drivens/repository/base/__init__.py +13 -0
- hexagonal/adapters/drivens/repository/base/repository.py +85 -0
- hexagonal/adapters/drivens/repository/base/unit_of_work.py +75 -0
- hexagonal/adapters/drivens/repository/sqlite/__init__.py +18 -0
- hexagonal/adapters/drivens/repository/sqlite/datastore.py +197 -0
- hexagonal/adapters/drivens/repository/sqlite/env_vars.py +2 -0
- hexagonal/adapters/drivens/repository/sqlite/infrastructure.py +20 -0
- hexagonal/adapters/drivens/repository/sqlite/outbox.py +405 -0
- hexagonal/adapters/drivens/repository/sqlite/repository.py +286 -0
- hexagonal/adapters/drivens/repository/sqlite/unit_of_work.py +25 -0
- hexagonal/adapters/drivers/__init__.py +5 -0
- hexagonal/adapters/drivers/app.py +38 -0
- hexagonal/application/__init__.py +29 -0
- hexagonal/application/api.py +61 -0
- hexagonal/application/app.py +76 -0
- hexagonal/application/bus_app.py +70 -0
- hexagonal/application/handlers.py +107 -0
- hexagonal/application/infrastructure.py +64 -0
- hexagonal/application/query.py +71 -0
- hexagonal/domain/__init__.py +77 -0
- hexagonal/domain/aggregate.py +159 -0
- hexagonal/domain/base.py +169 -0
- hexagonal/domain/exceptions.py +38 -0
- hexagonal/entrypoints/__init__.py +4 -0
- hexagonal/entrypoints/app.py +53 -0
- hexagonal/entrypoints/base.py +105 -0
- hexagonal/entrypoints/bus.py +68 -0
- hexagonal/entrypoints/sqlite.py +49 -0
- hexagonal/ports/__init__.py +0 -0
- hexagonal/ports/drivens/__init__.py +43 -0
- hexagonal/ports/drivens/application.py +35 -0
- hexagonal/ports/drivens/buses.py +148 -0
- hexagonal/ports/drivens/infrastructure.py +19 -0
- hexagonal/ports/drivens/repository.py +152 -0
- hexagonal/ports/drivers/__init__.py +3 -0
- hexagonal/ports/drivers/app.py +58 -0
- hexagonal/py.typed +0 -0
- python_hexagonal-0.1.0.dist-info/METADATA +15 -0
- python_hexagonal-0.1.0.dist-info/RECORD +53 -0
- python_hexagonal-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from hexagonal.adapters.drivens.repository.base import BaseUnitOfWork
|
|
4
|
+
from hexagonal.ports.drivens.repository import IBaseRepository
|
|
5
|
+
|
|
6
|
+
from .datastore import SQLiteConnectionContextManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SQLiteUnitOfWork(BaseUnitOfWork[SQLiteConnectionContextManager]):
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
*repositories: IBaseRepository[SQLiteConnectionContextManager],
|
|
13
|
+
connection_manager: Optional[SQLiteConnectionContextManager] = None,
|
|
14
|
+
):
|
|
15
|
+
if connection_manager is None:
|
|
16
|
+
connection_manager = SQLiteConnectionContextManager()
|
|
17
|
+
super().__init__(*repositories, connection_manager=connection_manager)
|
|
18
|
+
|
|
19
|
+
def commit(self) -> None:
|
|
20
|
+
self.verify()
|
|
21
|
+
return self.connection_manager.current_connection.commit()
|
|
22
|
+
|
|
23
|
+
def rollback(self) -> None:
|
|
24
|
+
self.verify()
|
|
25
|
+
return self.connection_manager.current_connection.rollback()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from hexagonal.domain import CloudMessage, TCommand, TEvent
|
|
2
|
+
from hexagonal.ports.drivens import TManager
|
|
3
|
+
from hexagonal.ports.drivers import IBaseApplication, IBusApp
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ApplicationProxyAdapter(IBaseApplication[TManager]):
|
|
7
|
+
def __init__(self, application: IBaseApplication[TManager]):
|
|
8
|
+
self._application = application
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def bus_app(self):
|
|
12
|
+
return self._application.bus_app
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def bus_infrastructure(self):
|
|
16
|
+
return self._application.bus_infrastructure
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def command_bus(self):
|
|
20
|
+
return self._application.command_bus
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def query_bus(self):
|
|
24
|
+
return self._application.query_bus
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def event_bus(self):
|
|
28
|
+
return self._application.event_bus
|
|
29
|
+
|
|
30
|
+
def bootstrap(self, bus_app: IBusApp[TManager]) -> None:
|
|
31
|
+
self._application.bootstrap(bus_app)
|
|
32
|
+
|
|
33
|
+
def dispatch_and_wait_events(
|
|
34
|
+
self,
|
|
35
|
+
command: CloudMessage[TCommand],
|
|
36
|
+
*event_types: type[TEvent],
|
|
37
|
+
) -> dict[type[TEvent], TEvent | None]:
|
|
38
|
+
return self._application.dispatch_and_wait_events(command, *event_types)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .api import BaseAPI, GetEvent
|
|
2
|
+
from .app import Application
|
|
3
|
+
from .bus_app import BusAppGroup, ComposableBusApp
|
|
4
|
+
from .handlers import CommandHandler, EventHandler, MessageHandler, QueryHandler
|
|
5
|
+
from .infrastructure import (
|
|
6
|
+
ComposableInfrastructure,
|
|
7
|
+
Infrastructure,
|
|
8
|
+
InfrastructureGroup,
|
|
9
|
+
)
|
|
10
|
+
from .query import AggregateView, GetById, GetByIdHandler, SearchAggregateRepository
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"BaseAPI",
|
|
14
|
+
"GetEvent",
|
|
15
|
+
"Application",
|
|
16
|
+
"ComposableBusApp",
|
|
17
|
+
"BusAppGroup",
|
|
18
|
+
"CommandHandler",
|
|
19
|
+
"EventHandler",
|
|
20
|
+
"MessageHandler",
|
|
21
|
+
"QueryHandler",
|
|
22
|
+
"ComposableInfrastructure",
|
|
23
|
+
"Infrastructure",
|
|
24
|
+
"InfrastructureGroup",
|
|
25
|
+
"GetById",
|
|
26
|
+
"SearchAggregateRepository",
|
|
27
|
+
"AggregateView",
|
|
28
|
+
"GetByIdHandler",
|
|
29
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# mypy: disable-error-code="misc"
|
|
2
|
+
from typing import Any, Dict, Generic, List, Optional, Tuple, Type, TypeVar
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from hexagonal.domain import CloudMessage, Query, TCommand, TEvento
|
|
6
|
+
from hexagonal.ports.drivens import TAggregate
|
|
7
|
+
from hexagonal.ports.drivers import IBaseApplication
|
|
8
|
+
|
|
9
|
+
from .app import GetEvent
|
|
10
|
+
from .query import AggregateView
|
|
11
|
+
|
|
12
|
+
TBaseApp = TypeVar("TBaseApp", bound=IBaseApplication[Any])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseAPI(Generic[TBaseApp]):
|
|
16
|
+
def __init__(self, app: TBaseApp):
|
|
17
|
+
self._app = app
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def app(self) -> TBaseApp:
|
|
21
|
+
return self._app
|
|
22
|
+
|
|
23
|
+
def _dispatch_command(
|
|
24
|
+
self,
|
|
25
|
+
command: TCommand,
|
|
26
|
+
*,
|
|
27
|
+
events: Optional[List[Type[TEvento]]] = None,
|
|
28
|
+
default_events: Optional[List[Type[TEvento]]] = None,
|
|
29
|
+
to_outbox: bool = False,
|
|
30
|
+
**kwargs: Any,
|
|
31
|
+
) -> Tuple[CloudMessage[TCommand], Dict[Type[TEvento], TEvento | None]]:
|
|
32
|
+
"""Dispatch a command and optionally await events before returning."""
|
|
33
|
+
cloud_message = CloudMessage[type(command)].new(command, **kwargs)
|
|
34
|
+
if to_outbox:
|
|
35
|
+
self.app.command_bus.dispatch(cloud_message, to_outbox=to_outbox)
|
|
36
|
+
return cloud_message, {}
|
|
37
|
+
tracked_events: set[Type[TEvento]] = set()
|
|
38
|
+
if events is not None:
|
|
39
|
+
for e in events:
|
|
40
|
+
tracked_events.add(e)
|
|
41
|
+
if default_events is not None:
|
|
42
|
+
for e in default_events:
|
|
43
|
+
tracked_events.add(e)
|
|
44
|
+
awaited: Dict[Type[TEvento], GetEvent[TEvento]] = {}
|
|
45
|
+
for e in tracked_events:
|
|
46
|
+
handler = GetEvent[e]() # type: ignore
|
|
47
|
+
self.app.event_bus.wait_for_publish(e, handler)
|
|
48
|
+
awaited[e] = handler
|
|
49
|
+
self.app.command_bus.dispatch(cloud_message)
|
|
50
|
+
return cloud_message, {
|
|
51
|
+
event: wrapper.event for event, wrapper in awaited.items()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def _get_aggregate(
|
|
55
|
+
self,
|
|
56
|
+
id: UUID,
|
|
57
|
+
query_type: Type[Query[AggregateView[TAggregate]]],
|
|
58
|
+
**kwargs: Any,
|
|
59
|
+
) -> TAggregate:
|
|
60
|
+
query = query_type.new(id, **kwargs)
|
|
61
|
+
return self.app.query_bus.get(query, one=True).item.value
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from typing import Dict, Generic, Type
|
|
2
|
+
|
|
3
|
+
from hexagonal.domain import CloudMessage, TCommand, TEvent
|
|
4
|
+
from hexagonal.ports.drivens import (
|
|
5
|
+
IBusInfrastructure,
|
|
6
|
+
ICommandBus,
|
|
7
|
+
IEventBus,
|
|
8
|
+
IQueryBus,
|
|
9
|
+
TManager,
|
|
10
|
+
)
|
|
11
|
+
from hexagonal.ports.drivers import IBaseApplication, IBusApp
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GetEvent(Generic[TEvent]):
|
|
15
|
+
event: TEvent | None = None
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.event = None
|
|
19
|
+
|
|
20
|
+
def __call__(self, event: TEvent):
|
|
21
|
+
self.event = event
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Application(IBaseApplication[TManager]):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
bus_app: IBusApp[TManager],
|
|
28
|
+
bus_infrastructure: IBusInfrastructure[TManager],
|
|
29
|
+
):
|
|
30
|
+
bus_infrastructure.verify()
|
|
31
|
+
self._bus_infrastructure = bus_infrastructure
|
|
32
|
+
self._bus_app = bus_app
|
|
33
|
+
self._bus_app.uow.attach_repo(self.event_bus.inbox_repository)
|
|
34
|
+
self._bus_app.uow.attach_repo(self.event_bus.outbox_repository)
|
|
35
|
+
self._bus_app.uow.attach_repo(self.command_bus.inbox_repository)
|
|
36
|
+
self._bus_app.uow.attach_repo(self.command_bus.outbox_repository)
|
|
37
|
+
self.bootstrap(self._bus_app)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def bus_app(self) -> IBusApp[TManager]:
|
|
41
|
+
return self._bus_app
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def bus_infrastructure(self) -> IBusInfrastructure[TManager]:
|
|
45
|
+
return self._bus_infrastructure
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def command_bus(self) -> ICommandBus[TManager]:
|
|
49
|
+
return self._bus_infrastructure.command_bus
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def query_bus(self) -> IQueryBus[TManager]:
|
|
53
|
+
return self._bus_infrastructure.query_bus
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def event_bus(self) -> IEventBus[TManager]:
|
|
57
|
+
return self._bus_infrastructure.event_bus
|
|
58
|
+
|
|
59
|
+
def bootstrap(self, bus_app: IBusApp[TManager]) -> None:
|
|
60
|
+
bus_app.bootstrap(
|
|
61
|
+
command_bus=self.command_bus,
|
|
62
|
+
query_bus=self.query_bus,
|
|
63
|
+
event_bus=self.event_bus,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def dispatch_and_wait_events(
|
|
67
|
+
self,
|
|
68
|
+
command: CloudMessage[TCommand],
|
|
69
|
+
*event_types: Type[TEvent],
|
|
70
|
+
) -> Dict[Type[TEvent], TEvent | None]:
|
|
71
|
+
handlers = [(event_type, GetEvent[event_type]()) for event_type in event_types] # type: ignore
|
|
72
|
+
# type: ignore
|
|
73
|
+
for event_type, handler in handlers:
|
|
74
|
+
self.event_bus.wait_for_publish(event_type, handler)
|
|
75
|
+
self.command_bus.dispatch(command)
|
|
76
|
+
return {event_type: handler.event for event_type, handler in handlers}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from hexagonal.ports.drivens import (
|
|
4
|
+
ICommandBus,
|
|
5
|
+
IEventBus,
|
|
6
|
+
IQueryBus,
|
|
7
|
+
IUnitOfWork,
|
|
8
|
+
TManager,
|
|
9
|
+
)
|
|
10
|
+
from hexagonal.ports.drivers import IBusApp
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ComposableBusApp(IBusApp[TManager]):
|
|
16
|
+
def __init__(self, bus_app: IBusApp[TManager]):
|
|
17
|
+
super().__init__()
|
|
18
|
+
self._bus_app = bus_app
|
|
19
|
+
|
|
20
|
+
def bootstrap(
|
|
21
|
+
self,
|
|
22
|
+
command_bus: ICommandBus[TManager],
|
|
23
|
+
query_bus: IQueryBus[TManager],
|
|
24
|
+
event_bus: IEventBus[TManager],
|
|
25
|
+
) -> None:
|
|
26
|
+
self._bus_app.bootstrap(command_bus, query_bus, event_bus)
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def uow(self) -> IUnitOfWork[TManager]:
|
|
30
|
+
return self._bus_app.uow
|
|
31
|
+
|
|
32
|
+
def __or__(self, other: IBusApp[TManager]) -> "ComposableBusApp[TManager]":
|
|
33
|
+
other_is_root = other.bus_apps if isinstance(other, BusAppGroup) else [other]
|
|
34
|
+
|
|
35
|
+
self_is_root = self.bus_apps if isinstance(self, BusAppGroup) else [self]
|
|
36
|
+
return BusAppGroup(self.uow, *self_is_root, *other_is_root)
|
|
37
|
+
|
|
38
|
+
def __add__(self, other: IBusApp[TManager]) -> "ComposableBusApp[TManager]":
|
|
39
|
+
return self.__or__(other)
|
|
40
|
+
|
|
41
|
+
def __and__(self, other: IBusApp[TManager]) -> "ComposableBusApp[TManager]":
|
|
42
|
+
return BusAppGroup(self.uow, self, other)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class BusAppGroup(ComposableBusApp[TManager]):
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
uow: IUnitOfWork[TManager],
|
|
49
|
+
bus_app: IBusApp[TManager],
|
|
50
|
+
*bus_apps: IBusApp[TManager],
|
|
51
|
+
):
|
|
52
|
+
self._bus_apps = [bus_app, *bus_apps]
|
|
53
|
+
self._uow = uow
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def uow(self) -> IUnitOfWork[TManager]:
|
|
57
|
+
return self._uow
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def bus_apps(self) -> list[IBusApp[TManager]]:
|
|
61
|
+
return self._bus_apps
|
|
62
|
+
|
|
63
|
+
def bootstrap(
|
|
64
|
+
self,
|
|
65
|
+
command_bus: ICommandBus[TManager],
|
|
66
|
+
query_bus: IQueryBus[TManager],
|
|
67
|
+
event_bus: IEventBus[TManager],
|
|
68
|
+
) -> None:
|
|
69
|
+
for bus_app in self._bus_apps:
|
|
70
|
+
bus_app.bootstrap(command_bus, query_bus, event_bus)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Iterable
|
|
3
|
+
|
|
4
|
+
from hexagonal.domain import (
|
|
5
|
+
CloudMessage,
|
|
6
|
+
QueryResults,
|
|
7
|
+
TCommand,
|
|
8
|
+
TEvent,
|
|
9
|
+
TEvento,
|
|
10
|
+
TMessagePayloadType,
|
|
11
|
+
TQuery,
|
|
12
|
+
TView,
|
|
13
|
+
)
|
|
14
|
+
from hexagonal.ports.drivens import (
|
|
15
|
+
IBaseRepository,
|
|
16
|
+
IEventBus,
|
|
17
|
+
IMessageHandler,
|
|
18
|
+
IQueryHandler,
|
|
19
|
+
ISearchRepository,
|
|
20
|
+
IUnitOfWork,
|
|
21
|
+
IUseCase,
|
|
22
|
+
TManager,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MessageHandler(IMessageHandler[TMessagePayloadType]):
|
|
29
|
+
event_bus: IEventBus[Any]
|
|
30
|
+
uow: IUnitOfWork[Any]
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
event_bus: IEventBus[TManager],
|
|
35
|
+
uow: IUnitOfWork[TManager],
|
|
36
|
+
*repositories: IBaseRepository[TManager],
|
|
37
|
+
) -> None:
|
|
38
|
+
self.event_bus = event_bus
|
|
39
|
+
self.uow = uow
|
|
40
|
+
for repository in repositories:
|
|
41
|
+
self.uow.attach_repo(repository)
|
|
42
|
+
|
|
43
|
+
def handle_message(self, message: CloudMessage[TMessagePayloadType]) -> None:
|
|
44
|
+
use_case = self.get_use_case(message.payload)
|
|
45
|
+
with self.uow:
|
|
46
|
+
events = use_case.execute()
|
|
47
|
+
if not events:
|
|
48
|
+
return
|
|
49
|
+
messages = [message.derive(event, **message.metadata) for event in events]
|
|
50
|
+
self.event_bus.outbox_repository.save(*messages)
|
|
51
|
+
return self.event_bus.publish_from_outbox()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class EventHandler(MessageHandler[TEvent]):
|
|
55
|
+
class UseCaseImpl(IUseCase):
|
|
56
|
+
def __init__(
|
|
57
|
+
self, event_handler: "EventHandler[TEvent]", event: TEvent
|
|
58
|
+
) -> None:
|
|
59
|
+
self.event_handler = event_handler
|
|
60
|
+
self.event = event
|
|
61
|
+
|
|
62
|
+
def execute(self):
|
|
63
|
+
evento = self.event_handler.handle(self.event)
|
|
64
|
+
return evento
|
|
65
|
+
|
|
66
|
+
def handle(self, event: TEvent) -> Iterable[TEvento]:
|
|
67
|
+
return []
|
|
68
|
+
|
|
69
|
+
def get_use_case(self, message: TEvent) -> IUseCase:
|
|
70
|
+
return self.UseCaseImpl(self, message)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CommandHandler(MessageHandler[TCommand]):
|
|
74
|
+
def execute(self, command: TCommand) -> Iterable[TEvento]:
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
class UseCaseImpl(IUseCase):
|
|
78
|
+
def __init__(
|
|
79
|
+
self, event_handler: "CommandHandler[TCommand]", event: TCommand
|
|
80
|
+
) -> None:
|
|
81
|
+
self.event_handler = event_handler
|
|
82
|
+
self.event = event
|
|
83
|
+
|
|
84
|
+
def execute(self):
|
|
85
|
+
evento = self.event_handler.execute(self.event)
|
|
86
|
+
return evento
|
|
87
|
+
|
|
88
|
+
def get_use_case(self, message: TCommand) -> IUseCase:
|
|
89
|
+
return self.UseCaseImpl(self, message)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class QueryHandler(IQueryHandler[TManager, TQuery, TView]):
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
repository: ISearchRepository[TManager, TQuery, TView],
|
|
96
|
+
*args: Any,
|
|
97
|
+
**kwargs: Any,
|
|
98
|
+
) -> None:
|
|
99
|
+
self._repository = repository
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def repository(self) -> ISearchRepository[TManager, TQuery, TView]:
|
|
103
|
+
return self._repository
|
|
104
|
+
|
|
105
|
+
def get(self, query: TQuery) -> QueryResults[TView]:
|
|
106
|
+
results = self.repository.search(query)
|
|
107
|
+
return QueryResults[TView].new(items=results, limit=len(results))
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from logging import getLogger
|
|
2
|
+
from typing import Mapping
|
|
3
|
+
|
|
4
|
+
from hexagonal.ports.drivens import IBaseInfrastructure
|
|
5
|
+
|
|
6
|
+
logger = getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ComposableInfrastructure(IBaseInfrastructure):
|
|
10
|
+
def __init__(self, infrastructure: IBaseInfrastructure):
|
|
11
|
+
self.infrastructure = infrastructure
|
|
12
|
+
|
|
13
|
+
def initialize(self, env: Mapping[str, str]) -> None:
|
|
14
|
+
self.infrastructure.initialize(env)
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def initialized(self) -> bool:
|
|
18
|
+
return self.infrastructure.initialized
|
|
19
|
+
|
|
20
|
+
def __or__(self, other: IBaseInfrastructure) -> "ComposableInfrastructure":
|
|
21
|
+
other_is_root = (
|
|
22
|
+
other.infrastructures if isinstance(other, InfrastructureGroup) else [other]
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
self_is_root = (
|
|
26
|
+
self.infrastructures if isinstance(self, InfrastructureGroup) else [self]
|
|
27
|
+
)
|
|
28
|
+
return InfrastructureGroup(*self_is_root, *other_is_root)
|
|
29
|
+
|
|
30
|
+
def __add__(self, other: IBaseInfrastructure) -> "ComposableInfrastructure":
|
|
31
|
+
return self.__or__(other)
|
|
32
|
+
|
|
33
|
+
def __and__(self, other: IBaseInfrastructure) -> "ComposableInfrastructure":
|
|
34
|
+
return InfrastructureGroup(self, other)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class InfrastructureGroup(ComposableInfrastructure):
|
|
38
|
+
def __init__(self, *infrastructures: IBaseInfrastructure):
|
|
39
|
+
self.infrastructures = list(infrastructures)
|
|
40
|
+
|
|
41
|
+
def initialize(self, env: Mapping[str, str]) -> None:
|
|
42
|
+
for infrastructure in self.infrastructures:
|
|
43
|
+
if infrastructure.initialized:
|
|
44
|
+
continue
|
|
45
|
+
infrastructure.initialize(env)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def initialized(self) -> bool:
|
|
49
|
+
return all(
|
|
50
|
+
infrastructure.initialized for infrastructure in self.infrastructures
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Infrastructure(ComposableInfrastructure):
|
|
55
|
+
def __init__(self):
|
|
56
|
+
self._initialized = False
|
|
57
|
+
|
|
58
|
+
def initialize(self, env: Mapping[str, str]) -> None:
|
|
59
|
+
logger.debug(f"Initializing {self.__class__.__name__}")
|
|
60
|
+
self._initialized = True
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def initialized(self) -> bool:
|
|
64
|
+
return self._initialized
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from typing import Any, Generic, List, Mapping, Type
|
|
2
|
+
|
|
3
|
+
from hexagonal.application.handlers import QueryHandler
|
|
4
|
+
from hexagonal.domain import Query, TIdEntity, ValueObject
|
|
5
|
+
from hexagonal.ports.drivens import (
|
|
6
|
+
IAggregateRepository,
|
|
7
|
+
ISearchRepository,
|
|
8
|
+
TAggregate,
|
|
9
|
+
TManager,
|
|
10
|
+
)
|
|
11
|
+
from hexagonal.ports.drivens.repository import IUnitOfWork
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AggregateView(ValueObject[TAggregate]): ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GetById(Query[AggregateView[TAggregate]], Generic[TAggregate, TIdEntity]):
|
|
18
|
+
id: TIdEntity
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def new(cls, id: TIdEntity, agg_type: Type[TAggregate], *_: Any, **__: Any):
|
|
22
|
+
return cls(id=id, view=AggregateView[agg_type]) # type: ignore
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SearchAggregateRepository(
|
|
26
|
+
ISearchRepository[
|
|
27
|
+
TManager,
|
|
28
|
+
GetById[TAggregate, TIdEntity],
|
|
29
|
+
AggregateView[TAggregate],
|
|
30
|
+
],
|
|
31
|
+
Generic[TManager, TAggregate, TIdEntity],
|
|
32
|
+
):
|
|
33
|
+
def __init__(self, repo: IAggregateRepository[TManager, TAggregate, TIdEntity]):
|
|
34
|
+
self._repo = repo
|
|
35
|
+
|
|
36
|
+
def search(
|
|
37
|
+
self, query: GetById[TAggregate, TIdEntity]
|
|
38
|
+
) -> List[AggregateView[TAggregate]]:
|
|
39
|
+
aggregate = self._repo.get(query.id)
|
|
40
|
+
return [AggregateView[TAggregate].new(aggregate)]
|
|
41
|
+
|
|
42
|
+
## decorate methods from IAggregateRepository to pass through initialization ##
|
|
43
|
+
def initialize(self, env: Mapping[str, str]) -> None:
|
|
44
|
+
self._repo.initialize(env)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def initialized(self):
|
|
48
|
+
return self._repo.initialized
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def connection_manager(self) -> TManager:
|
|
52
|
+
return self._repo.connection_manager
|
|
53
|
+
|
|
54
|
+
def attach_to_unit_of_work(self, uow: IUnitOfWork[TManager]) -> None:
|
|
55
|
+
self._repo.attach_to_unit_of_work(uow)
|
|
56
|
+
|
|
57
|
+
def detach_from_unit_of_work(self) -> None:
|
|
58
|
+
self._repo.detach_from_unit_of_work()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class GetByIdHandler(
|
|
62
|
+
QueryHandler[
|
|
63
|
+
TManager,
|
|
64
|
+
GetById[TAggregate, TIdEntity],
|
|
65
|
+
AggregateView[TAggregate],
|
|
66
|
+
],
|
|
67
|
+
Generic[TManager, TAggregate, TIdEntity],
|
|
68
|
+
):
|
|
69
|
+
def __init__(self, agg_repo: IAggregateRepository[TManager, TAggregate, TIdEntity]):
|
|
70
|
+
search = SearchAggregateRepository(agg_repo)
|
|
71
|
+
super().__init__(search)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from .aggregate import (
|
|
2
|
+
AggregateRoot,
|
|
3
|
+
AggregateSnapshot,
|
|
4
|
+
IdValueObject,
|
|
5
|
+
SnapshotState,
|
|
6
|
+
TIdEntity,
|
|
7
|
+
command,
|
|
8
|
+
)
|
|
9
|
+
from .base import (
|
|
10
|
+
CloudMessage,
|
|
11
|
+
Command,
|
|
12
|
+
DomainEvent,
|
|
13
|
+
FactoryMethod,
|
|
14
|
+
HasTopic,
|
|
15
|
+
Inmutable,
|
|
16
|
+
IntegrationEvent,
|
|
17
|
+
Message,
|
|
18
|
+
Query,
|
|
19
|
+
QueryResult,
|
|
20
|
+
QueryResults,
|
|
21
|
+
TCommand,
|
|
22
|
+
TEvent,
|
|
23
|
+
TEvento,
|
|
24
|
+
TMessage,
|
|
25
|
+
TMessagePayload,
|
|
26
|
+
TMessagePayloadType,
|
|
27
|
+
TQuery,
|
|
28
|
+
TView,
|
|
29
|
+
ValueObject,
|
|
30
|
+
)
|
|
31
|
+
from .exceptions import (
|
|
32
|
+
AggregateNotFound,
|
|
33
|
+
AggregateVersionMismatch,
|
|
34
|
+
DomainException,
|
|
35
|
+
DomainValueError,
|
|
36
|
+
HandlerAlreadyRegistered,
|
|
37
|
+
HandlerNotFound,
|
|
38
|
+
HandlerNotRegistered,
|
|
39
|
+
InfrastructureNotInitialized,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"AggregateRoot",
|
|
44
|
+
"AggregateSnapshot",
|
|
45
|
+
"IdValueObject",
|
|
46
|
+
"command",
|
|
47
|
+
"TIdEntity",
|
|
48
|
+
"CloudMessage",
|
|
49
|
+
"Command",
|
|
50
|
+
"DomainEvent",
|
|
51
|
+
"Message",
|
|
52
|
+
"FactoryMethod",
|
|
53
|
+
"HasTopic",
|
|
54
|
+
"IntegrationEvent",
|
|
55
|
+
"Inmutable",
|
|
56
|
+
"ValueObject",
|
|
57
|
+
"TCommand",
|
|
58
|
+
"TEvent",
|
|
59
|
+
"TEvento",
|
|
60
|
+
"TView",
|
|
61
|
+
"TMessage",
|
|
62
|
+
"TMessagePayload",
|
|
63
|
+
"TMessagePayloadType",
|
|
64
|
+
"Query",
|
|
65
|
+
"QueryResult",
|
|
66
|
+
"QueryResults",
|
|
67
|
+
"TQuery",
|
|
68
|
+
"DomainException",
|
|
69
|
+
"DomainValueError",
|
|
70
|
+
"AggregateNotFound",
|
|
71
|
+
"AggregateVersionMismatch",
|
|
72
|
+
"HandlerAlreadyRegistered",
|
|
73
|
+
"HandlerNotFound",
|
|
74
|
+
"InfrastructureNotInitialized",
|
|
75
|
+
"HandlerNotRegistered",
|
|
76
|
+
"SnapshotState",
|
|
77
|
+
]
|