python-ddd-framework 0.6.0__py3-none-any.whl → 0.7.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.
- python_ddd_framework/authorization/checking.py +35 -0
- python_ddd_framework/developer_kit/generation.py +0 -9
- python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +29 -77
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +5 -72
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +1 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +2 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +8 -62
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +1 -2
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +12 -8
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +22 -10
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/src/host/main.py.jinja +4 -1
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +40 -6
- python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +177 -30
- python_ddd_framework/fastapi/__init__.py +2 -0
- python_ddd_framework/fastapi/access.py +127 -0
- python_ddd_framework/fastapi/access_options.py +13 -0
- python_ddd_framework/fastapi/action.py +6 -0
- python_ddd_framework/fastapi/manual_action.py +33 -2
- python_ddd_framework/fastapi/module.py +2 -0
- python_ddd_framework/fastapi/realtime/authentication.py +21 -10
- python_ddd_framework/fastapi/realtime/options.py +0 -1
- python_ddd_framework/fastapi/realtime/runtime.py +41 -26
- python_ddd_framework/fastapi/request_context.py +10 -2
- python_ddd_framework/fastapi/routing.py +12 -5
- python_ddd_framework/fastapi/server.py +9 -0
- python_ddd_framework/fastapi/service_endpoints.py +5 -0
- python_ddd_framework/fastapi/streaming.py +95 -0
- python_ddd_framework/fastapi/transfer.py +138 -3
- python_ddd_framework/invocation/dispatcher.py +2 -24
- python_ddd_framework/invocation/scopes.py +5 -3
- python_ddd_framework/observability/formatting.py +1 -0
- python_ddd_framework/unit_of_work/manager.py +11 -2
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/METADATA +29 -8
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/RECORD +39 -86
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/WHEEL +1 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +0 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +0 -45
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +0 -8
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +0 -1
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +0 -35
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +0 -5
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +0 -15
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +0 -28
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +0 -26
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +0 -9
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +0 -9
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +0 -23
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_integration_service.py.jinja +0 -71
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +0 -16
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +0 -16
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +0 -17
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/order_options.py.jinja +0 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +0 -65
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +0 -35
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +0 -41
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +0 -17
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +0 -6
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +0 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +0 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +0 -13
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +0 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +0 -12
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +0 -9
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +0 -12
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/order.py.jinja +0 -48
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +0 -13
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +0 -17
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +0 -22
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +0 -23
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/approval_settings.py.jinja +0 -17
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +0 -10
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +0 -3
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +0 -6
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +0 -11
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +0 -26
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +0 -13
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permission_provider.py.jinja +0 -19
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +0 -7
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +0 -18
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +0 -15
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +0 -5
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/order_files.py.jinja +0 -76
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +0 -44
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/order_model.py.jinja +0 -29
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +0 -81
- python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +0 -19
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"""内部统计使用正式 IntegrationService 调用链,不开放 HTTP。"""
|
|
2
|
-
|
|
3
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.integration_services.order_reporting_service import (
|
|
4
|
-
OrderReportingService,
|
|
5
|
-
)
|
|
6
|
-
from modules.{{ cookiecutter.module_name }}.domain.repositories.order_repository import OrderRepository
|
|
7
|
-
|
|
8
|
-
from python_ddd_framework import IntegrationService
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class _OrderReportingService(IntegrationService, OrderReportingService):
|
|
12
|
-
def __init__(self, repository: OrderRepository) -> None:
|
|
13
|
-
self._repository = repository
|
|
14
|
-
|
|
15
|
-
async def get_count(self) -> int:
|
|
16
|
-
return await self._repository.get_count()
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""Record invocation timing through the framework's interceptor extension."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from time import monotonic
|
|
5
|
-
|
|
6
|
-
from python_ddd_framework import Interceptor, MethodInvocation
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class OrderTimingInterceptor(Interceptor):
|
|
10
|
-
async def intercept(self, invocation: MethodInvocation) -> object:
|
|
11
|
-
started = monotonic()
|
|
12
|
-
try:
|
|
13
|
-
return await invocation.proceed()
|
|
14
|
-
finally:
|
|
15
|
-
logging.getLogger(__name__).info(
|
|
16
|
-
"Order invocation completed in %.4fs", monotonic() - started
|
|
17
|
-
)
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from uuid import UUID
|
|
3
|
-
|
|
4
|
-
from modules.{{ cookiecutter.module_name }}.application.background_jobs.order_approval.handler import (
|
|
5
|
-
ApprovalJob,
|
|
6
|
-
)
|
|
7
|
-
from modules.{{ cookiecutter.module_name }}.application.background_jobs.order_approval.payload import (
|
|
8
|
-
ApprovalPayload,
|
|
9
|
-
)
|
|
10
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.inputs.approve_order import ApproveOrder
|
|
11
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.services.order_approval_service import (
|
|
12
|
-
{{ cookiecutter.class_prefix }}ApprovalApplicationService,
|
|
13
|
-
)
|
|
14
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.views.order_view import OrderView
|
|
15
|
-
from modules.{{ cookiecutter.module_name }}.domain.repositories.order_repository import OrderRepository
|
|
16
|
-
from modules.{{ cookiecutter.module_name }}.domain.services.order_approval_service import OrderApprovalService
|
|
17
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.permissions.order_permissions import ORDERS_WRITE
|
|
18
|
-
|
|
19
|
-
from python_ddd_framework import ApplicationService, CurrentUser
|
|
20
|
-
from python_ddd_framework.authorization import authorize
|
|
21
|
-
from python_ddd_framework.background_jobs import BackgroundJobEnqueuer
|
|
22
|
-
from python_ddd_framework.errors.base import ResourceConflictError, ResourceNotFoundError
|
|
23
|
-
from python_ddd_framework.fastapi import http
|
|
24
|
-
|
|
25
|
-
logger = logging.getLogger(__name__)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class _{{ cookiecutter.class_prefix }}ApprovalApplicationService(
|
|
29
|
-
ApplicationService, {{ cookiecutter.class_prefix }}ApprovalApplicationService
|
|
30
|
-
):
|
|
31
|
-
def __init__(
|
|
32
|
-
self,
|
|
33
|
-
repository: OrderRepository,
|
|
34
|
-
user: CurrentUser,
|
|
35
|
-
jobs: BackgroundJobEnqueuer,
|
|
36
|
-
approval: OrderApprovalService,
|
|
37
|
-
) -> None:
|
|
38
|
-
self._repository = repository
|
|
39
|
-
self._user = user
|
|
40
|
-
self._jobs = jobs
|
|
41
|
-
self._approval = approval
|
|
42
|
-
|
|
43
|
-
@authorize(ORDERS_WRITE)
|
|
44
|
-
async def approve(self, id: UUID, command: ApproveOrder) -> OrderView:
|
|
45
|
-
# 业务扩展:这里编排“读取聚合 → 核对版本 → 领域服务检查策略并执行动作 → 保存”。
|
|
46
|
-
# 审批只是动作示例;替换为入库等用例时保留权限/并发边界,把状态规则放进领域方法。
|
|
47
|
-
order = await self._repository.find(id)
|
|
48
|
-
if order is None:
|
|
49
|
-
raise ResourceNotFoundError("Order not found")
|
|
50
|
-
if order.version != command.expected_version:
|
|
51
|
-
raise ResourceConflictError("Order version changed")
|
|
52
|
-
await self._approval.approve(order, user_id=self._user.user_id)
|
|
53
|
-
await self._repository.save(order)
|
|
54
|
-
logger.info("Order approved")
|
|
55
|
-
return OrderView.model_validate(order)
|
|
56
|
-
|
|
57
|
-
@authorize(ORDERS_WRITE)
|
|
58
|
-
@http.post(status_code=202)
|
|
59
|
-
async def queue_approval(self, id: UUID) -> str:
|
|
60
|
-
# 业务扩展:这里校验入队条件并构造自己的任务载荷;实际后台业务写在对应 Job.execute。
|
|
61
|
-
# Job 和载荷类型一起替换,通过注入的入队服务调用,不自行创建消费者或数据库连接。
|
|
62
|
-
if await self._repository.find(id) is None:
|
|
63
|
-
raise ResourceNotFoundError("Order not found")
|
|
64
|
-
# 入队参与本方法原生事务;失败时订单业务与队列写入一起回滚。
|
|
65
|
-
return await self._jobs.enqueue(ApprovalJob, ApprovalPayload(order_id=id))
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from uuid import uuid4
|
|
3
|
-
|
|
4
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.inputs.create_order import CreateOrder
|
|
5
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.services.order_management_service import (
|
|
6
|
-
{{ cookiecutter.class_prefix }}ManagementApplicationService,
|
|
7
|
-
)
|
|
8
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.views.order_view import OrderView
|
|
9
|
-
from modules.{{ cookiecutter.module_name }}.domain.entities.order import Order
|
|
10
|
-
from modules.{{ cookiecutter.module_name }}.domain.repositories.order_repository import OrderRepository
|
|
11
|
-
from modules.{{ cookiecutter.module_name }}.domain.value_objects.order_title import OrderTitle
|
|
12
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.permissions.order_permissions import ORDERS_WRITE
|
|
13
|
-
|
|
14
|
-
from python_ddd_framework import ApplicationService
|
|
15
|
-
from python_ddd_framework.authorization import authorize
|
|
16
|
-
from python_ddd_framework.fastapi import http
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class _{{ cookiecutter.class_prefix }}ManagementApplicationService(
|
|
22
|
-
ApplicationService, {{ cookiecutter.class_prefix }}ManagementApplicationService
|
|
23
|
-
):
|
|
24
|
-
def __init__(self, repository: OrderRepository) -> None:
|
|
25
|
-
self._repository = repository
|
|
26
|
-
|
|
27
|
-
@authorize(ORDERS_WRITE)
|
|
28
|
-
@http.post(status_code=201)
|
|
29
|
-
async def create(self, command: CreateOrder) -> OrderView:
|
|
30
|
-
# 业务扩展:这里编排创建用例,把输入 DTO 转成领域对象,调用领域方法,再交给仓储保存。
|
|
31
|
-
# 将 Order/OrderTitle 换成自己的聚合和值对象;领域规则写进聚合,事务由正式调用管线完成。
|
|
32
|
-
order = Order.create(uuid4(), OrderTitle(value=command.title))
|
|
33
|
-
await self._repository.save(order)
|
|
34
|
-
logger.info("Order created")
|
|
35
|
-
return OrderView.model_validate(order)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from uuid import UUID
|
|
3
|
-
|
|
4
|
-
from modules.{{ cookiecutter.module_name }}.application.caching.order_cache import OrderCacheItem
|
|
5
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.services.order_query_service import (
|
|
6
|
-
{{ cookiecutter.class_prefix }}QueryApplicationService,
|
|
7
|
-
)
|
|
8
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.views.order_view import OrderView
|
|
9
|
-
from modules.{{ cookiecutter.module_name }}.domain.repositories.order_repository import OrderRepository
|
|
10
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.permissions.order_permissions import ORDERS_READ
|
|
11
|
-
|
|
12
|
-
from python_ddd_framework import ApplicationService
|
|
13
|
-
from python_ddd_framework.authorization import authorize
|
|
14
|
-
from python_ddd_framework.caching import DistributedCache
|
|
15
|
-
from python_ddd_framework.errors.base import ResourceNotFoundError
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class _{{ cookiecutter.class_prefix }}QueryApplicationService(ApplicationService, {{ cookiecutter.class_prefix }}QueryApplicationService):
|
|
21
|
-
def __init__(self, repository: OrderRepository, cache: DistributedCache[OrderCacheItem]) -> None:
|
|
22
|
-
self._repository = repository
|
|
23
|
-
self._cache = cache
|
|
24
|
-
|
|
25
|
-
@authorize(ORDERS_READ)
|
|
26
|
-
async def get(self, id: UUID) -> OrderView:
|
|
27
|
-
# 业务扩展:这里组织单条查询及 DTO 返回;保留缓存时,定义和变更后的失效逻辑要一起调整。
|
|
28
|
-
cached = await self._cache.get(str(id))
|
|
29
|
-
if cached is not None:
|
|
30
|
-
return cached
|
|
31
|
-
order = await self._repository.find(id)
|
|
32
|
-
if order is None:
|
|
33
|
-
raise ResourceNotFoundError("Order not found")
|
|
34
|
-
view = OrderCacheItem.model_validate(order)
|
|
35
|
-
await self._cache.set(str(id), view)
|
|
36
|
-
return view
|
|
37
|
-
|
|
38
|
-
@authorize(ORDERS_READ)
|
|
39
|
-
async def get_list(self) -> tuple[OrderView, ...]:
|
|
40
|
-
# 业务扩展:这里接收实际业务的查询条件;需要筛选/分页时同步扩展 DTO 和仓储查询契约。
|
|
41
|
-
return tuple(OrderView.model_validate(order) for order in await self._repository.get_list())
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""Application-owned setting snapshot refreshed by the framework."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import os
|
|
5
|
-
|
|
6
|
-
from python_ddd_framework.settings import SettingSnapshot
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ApprovalSettingObserver:
|
|
10
|
-
def __init__(self) -> None:
|
|
11
|
-
self.snapshot: SettingSnapshot[bool] | None = None
|
|
12
|
-
|
|
13
|
-
async def apply(self, snapshot: SettingSnapshot[bool]) -> None:
|
|
14
|
-
self.snapshot = snapshot
|
|
15
|
-
logging.getLogger(__name__).info(
|
|
16
|
-
"Approval setting applied: %s pid=%s", snapshot.value, os.getpid()
|
|
17
|
-
)
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import TITLE_MAX_LENGTH
|
|
2
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class CreateOrder(BaseModel):
|
|
6
|
-
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
7
|
-
title: str = Field(min_length=1, max_length=TITLE_MAX_LENGTH)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from typing import Protocol
|
|
2
|
-
from uuid import UUID
|
|
3
|
-
|
|
4
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.inputs.approve_order import ApproveOrder
|
|
5
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.views.order_view import OrderView
|
|
6
|
-
|
|
7
|
-
from python_ddd_framework import ApplicationServiceContract
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class {{ cookiecutter.class_prefix }}ApprovalApplicationService(ApplicationServiceContract, Protocol):
|
|
11
|
-
async def approve(self, id: UUID, command: ApproveOrder) -> OrderView: ...
|
|
12
|
-
|
|
13
|
-
async def queue_approval(self, id: UUID) -> str: ...
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
from typing import Protocol
|
|
2
|
-
|
|
3
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.inputs.create_order import CreateOrder
|
|
4
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.views.order_view import OrderView
|
|
5
|
-
|
|
6
|
-
from python_ddd_framework import ApplicationServiceContract
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class {{ cookiecutter.class_prefix }}ManagementApplicationService(ApplicationServiceContract, Protocol):
|
|
10
|
-
async def create(self, command: CreateOrder) -> OrderView: ...
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
from typing import Protocol
|
|
2
|
-
from uuid import UUID
|
|
3
|
-
|
|
4
|
-
from modules.{{ cookiecutter.module_name }}.application_contracts.views.order_view import OrderView
|
|
5
|
-
|
|
6
|
-
from python_ddd_framework import ApplicationServiceContract
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class {{ cookiecutter.class_prefix }}QueryApplicationService(ApplicationServiceContract, Protocol):
|
|
10
|
-
async def get(self, id: UUID) -> OrderView: ...
|
|
11
|
-
|
|
12
|
-
async def get_list(self) -> tuple[OrderView, ...]: ...
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
from uuid import UUID
|
|
2
|
-
|
|
3
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.enums.order_status import OrderStatus
|
|
4
|
-
from pydantic import BaseModel, ConfigDict
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class OrderView(BaseModel):
|
|
8
|
-
model_config = ConfigDict(frozen=True, from_attributes=True)
|
|
9
|
-
id: UUID
|
|
10
|
-
title: str
|
|
11
|
-
status: OrderStatus
|
|
12
|
-
version: int
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from uuid import UUID
|
|
4
|
-
|
|
5
|
-
from modules.{{ cookiecutter.module_name }}.domain.events.order_changed import OrderChanged
|
|
6
|
-
from modules.{{ cookiecutter.module_name }}.domain.value_objects.order_title import OrderTitle
|
|
7
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.enums.order_status import OrderStatus
|
|
8
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.errors.order_errors import ALREADY_APPROVED
|
|
9
|
-
|
|
10
|
-
from python_ddd_framework import AggregateRoot
|
|
11
|
-
from python_ddd_framework.errors import BusinessError
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Order(AggregateRoot[UUID]):
|
|
15
|
-
def __init__(
|
|
16
|
-
self,
|
|
17
|
-
id: UUID,
|
|
18
|
-
title: OrderTitle,
|
|
19
|
-
*,
|
|
20
|
-
status: OrderStatus = OrderStatus.PENDING,
|
|
21
|
-
version: int = 0,
|
|
22
|
-
) -> None:
|
|
23
|
-
super().__init__(id, version=version)
|
|
24
|
-
self._title, self._status = title, status
|
|
25
|
-
|
|
26
|
-
@property
|
|
27
|
-
def title(self) -> str:
|
|
28
|
-
return self._title.value
|
|
29
|
-
|
|
30
|
-
@property
|
|
31
|
-
def status(self) -> OrderStatus:
|
|
32
|
-
return self._status
|
|
33
|
-
|
|
34
|
-
@classmethod
|
|
35
|
-
def create(cls, id: UUID, title: OrderTitle) -> Order:
|
|
36
|
-
# 业务扩展:在这里校验新聚合的创建条件、初始化实体和值对象;替换订单示例时一起调整参数。
|
|
37
|
-
# 领域方法只维护业务状态和事件,持久化由应用服务调用仓储完成。
|
|
38
|
-
order = cls(id, title)
|
|
39
|
-
order.raise_local_event(OrderChanged(id, order.status))
|
|
40
|
-
return order
|
|
41
|
-
|
|
42
|
-
def approve(self, *, user_id: UUID | None = None) -> None:
|
|
43
|
-
# 业务扩展:在这里实现自己的状态转换条件,例如库存是否足够、单据是否允许提交。
|
|
44
|
-
# 校验通过后再修改状态并记录对应事件;HTTP 参数绑定和调用者授权留在外层。
|
|
45
|
-
if self.status is OrderStatus.APPROVED:
|
|
46
|
-
raise BusinessError(ALREADY_APPROVED)
|
|
47
|
-
self._status = OrderStatus.APPROVED
|
|
48
|
-
self.raise_local_event(OrderChanged(self.id, self.status, user_id))
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from uuid import UUID
|
|
5
|
-
|
|
6
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.enums.order_status import OrderStatus
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@dataclass(frozen=True)
|
|
10
|
-
class OrderChanged:
|
|
11
|
-
order_id: UUID
|
|
12
|
-
status: OrderStatus
|
|
13
|
-
user_id: UUID | None = None
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""领域需要的业务仓储边界;不暴露 Session 或容器。"""
|
|
2
|
-
|
|
3
|
-
from typing import Protocol
|
|
4
|
-
from uuid import UUID
|
|
5
|
-
|
|
6
|
-
from modules.{{ cookiecutter.module_name }}.domain.entities.order import Order
|
|
7
|
-
|
|
8
|
-
from python_ddd_framework import RepositoryContract
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class OrderRepository(RepositoryContract, Protocol):
|
|
12
|
-
async def find(self, id: UUID, include_details: bool = True) -> Order | None: ...
|
|
13
|
-
async def get(self, id: UUID, include_details: bool = True) -> Order: ...
|
|
14
|
-
async def get_count(self) -> int: ...
|
|
15
|
-
async def get_paged_list(self, skip_count: int, max_result_count: int, sorting: str | None = None, include_details: bool = False) -> list[Order]: ...
|
|
16
|
-
async def get_list(self, include_details: bool = False) -> list[Order]: ...
|
|
17
|
-
async def save(self, order: Order) -> None: ...
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"""仅 pddd db seed 显式调用;确定性业务 ID 保证重复初始化不产生新订单。"""
|
|
2
|
-
|
|
3
|
-
from uuid import NAMESPACE_URL, uuid5
|
|
4
|
-
|
|
5
|
-
from modules.{{ cookiecutter.module_name }}.domain.entities.order import Order
|
|
6
|
-
from modules.{{ cookiecutter.module_name }}.domain.repositories.order_repository import OrderRepository
|
|
7
|
-
from modules.{{ cookiecutter.module_name }}.domain.value_objects.order_title import OrderTitle
|
|
8
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
9
|
-
|
|
10
|
-
from python_ddd_framework import DataSeedContributor
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class {{ cookiecutter.class_prefix }}SeedContributor(DataSeedContributor):
|
|
14
|
-
def __init__(self, repository: OrderRepository) -> None:
|
|
15
|
-
self._repository = repository
|
|
16
|
-
|
|
17
|
-
async def seed(self) -> None:
|
|
18
|
-
# 业务扩展:把演示订单换成业务需要的初始数据;使用稳定标识并先查再写,保证重复 seed 安全。
|
|
19
|
-
# 真实业务不需要这条示例数据时可移除它;不要在这里执行迁移或启动后台任务。
|
|
20
|
-
id = uuid5(NAMESPACE_URL, f"{MODULE_NAME}:sample-order")
|
|
21
|
-
if await self._repository.find(id) is None:
|
|
22
|
-
await self._repository.save(Order.create(id, OrderTitle(value="Sample order")))
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"""审批设置属于跨对象的领域策略;订单自身仍拥有状态转换规则。"""
|
|
2
|
-
|
|
3
|
-
from uuid import UUID
|
|
4
|
-
|
|
5
|
-
from modules.{{ cookiecutter.module_name }}.domain.entities.order import Order
|
|
6
|
-
from modules.{{ cookiecutter.module_name }}.domain.settings.approval_settings import APPROVAL_ENABLED
|
|
7
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.errors.order_errors import APPROVAL_DISABLED
|
|
8
|
-
|
|
9
|
-
from python_ddd_framework import DomainService, ValidationEnabled
|
|
10
|
-
from python_ddd_framework.errors import BusinessError
|
|
11
|
-
from python_ddd_framework.settings import SettingProvider
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class OrderApprovalService(DomainService, ValidationEnabled):
|
|
15
|
-
def __init__(self, settings: SettingProvider) -> None:
|
|
16
|
-
self._settings = settings
|
|
17
|
-
|
|
18
|
-
async def approve(self, order: Order, *, user_id: UUID | None = None) -> None:
|
|
19
|
-
# 业务扩展:依赖设置、仓储或多个聚合的规则写在领域服务;不接收 HTTP DTO。
|
|
20
|
-
# 聚合仍保证不能重复审批;保存和提交由调用方完成,失败才能整体回滚。
|
|
21
|
-
if not await self._settings.get(APPROVAL_ENABLED):
|
|
22
|
-
raise BusinessError(APPROVAL_DISABLED)
|
|
23
|
-
order.approve(user_id=user_id)
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""设置默认值属于领域,业务通过 SettingProvider 获取冻结读取视图。"""
|
|
2
|
-
|
|
3
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
4
|
-
|
|
5
|
-
from python_ddd_framework.settings import (
|
|
6
|
-
SettingDefinition,
|
|
7
|
-
SettingDefinitionContext,
|
|
8
|
-
SettingDefinitionProvider,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
APPROVAL_ENABLED = SettingDefinition(f"{MODULE_NAME}.approval_enabled", bool, default=True)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class {{ cookiecutter.class_prefix }}SettingDefinitionProvider(SettingDefinitionProvider):
|
|
15
|
-
def define(self, context: SettingDefinitionContext) -> None:
|
|
16
|
-
# 定义扩展:在这里发布本模块的动态业务设置;类型/默认值归 SettingDefinition,读取走 SettingProvider。
|
|
17
|
-
context.add(APPROVAL_ENABLED)
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import TITLE_MAX_LENGTH
|
|
4
|
-
from pydantic import Field
|
|
5
|
-
|
|
6
|
-
from python_ddd_framework import ValueObject
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class OrderTitle(ValueObject):
|
|
10
|
-
value: str = Field(min_length=1, max_length=TITLE_MAX_LENGTH)
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
2
|
-
|
|
3
|
-
from python_ddd_framework.errors import BusinessErrorDefinition
|
|
4
|
-
|
|
5
|
-
ALREADY_APPROVED = BusinessErrorDefinition(
|
|
6
|
-
f"{MODULE_NAME}.already_approved", "Order is already approved"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
APPROVAL_DISABLED = BusinessErrorDefinition(
|
|
10
|
-
f"{MODULE_NAME}.approval_disabled", "Order approval is disabled"
|
|
11
|
-
)
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from uuid import UUID
|
|
2
|
-
|
|
3
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
4
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.enums.order_status import OrderStatus
|
|
5
|
-
from pydantic import BaseModel, ConfigDict
|
|
6
|
-
|
|
7
|
-
from python_ddd_framework.realtime import RealtimeMessage
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class OrderChangedMessage(RealtimeMessage):
|
|
11
|
-
message_type = f"{MODULE_NAME}.changed.v1"
|
|
12
|
-
id: UUID
|
|
13
|
-
status: OrderStatus
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class OrderSnapshotItem(BaseModel):
|
|
17
|
-
model_config = ConfigDict(frozen=True)
|
|
18
|
-
id: UUID
|
|
19
|
-
title: str
|
|
20
|
-
status: OrderStatus
|
|
21
|
-
version: int
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class OrderSnapshotMessage(RealtimeMessage):
|
|
25
|
-
message_type = f"{MODULE_NAME}.snapshot.v1"
|
|
26
|
-
orders: tuple[OrderSnapshotItem, ...]
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"""权限名称由共享定义拥有;Provider 仅发布定义。"""
|
|
2
|
-
|
|
3
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.permissions.order_permissions import (
|
|
4
|
-
ORDERS_READ,
|
|
5
|
-
ORDERS_WRITE,
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
from python_ddd_framework import (
|
|
9
|
-
PermissionDefinition,
|
|
10
|
-
PermissionDefinitionContext,
|
|
11
|
-
PermissionDefinitionProvider,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class {{ cookiecutter.class_prefix }}PermissionDefinitionProvider(PermissionDefinitionProvider):
|
|
16
|
-
def define(self, context: PermissionDefinitionContext) -> None:
|
|
17
|
-
# 定义扩展:在这里发布自己的业务权限,名称统一放在共享定义;实际授权检查声明在服务/端点上。
|
|
18
|
-
context.add(PermissionDefinition(ORDERS_READ, "Read orders"))
|
|
19
|
-
context.add(PermissionDefinition(ORDERS_WRITE, "Create and approve orders"))
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
from modules.{{ cookiecutter.module_name }}.domain_shared.constants.order_constants import MODULE_NAME
|
|
2
|
-
|
|
3
|
-
from python_ddd_framework import PermissionName
|
|
4
|
-
|
|
5
|
-
ORDERS_READ = PermissionName(f"{MODULE_NAME}.read")
|
|
6
|
-
|
|
7
|
-
ORDERS_WRITE = PermissionName(f"{MODULE_NAME}.write")
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"""Shared immutable monetary value for domain objects and public DTOs."""
|
|
2
|
-
|
|
3
|
-
from decimal import Decimal
|
|
4
|
-
|
|
5
|
-
from pydantic import Field, field_validator
|
|
6
|
-
|
|
7
|
-
from python_ddd_framework import ValueObject
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Money(ValueObject):
|
|
11
|
-
amount: Decimal = Field(ge=0, max_digits=12, decimal_places=2)
|
|
12
|
-
currency: str = Field(default="CNY", pattern=r"^[A-Z]{3}$")
|
|
13
|
-
|
|
14
|
-
@field_validator("amount")
|
|
15
|
-
@classmethod
|
|
16
|
-
def normalize_amount(cls, value: Decimal) -> Decimal:
|
|
17
|
-
# 与两位小数的持久价格一致,避免写入前后公开 DTO 表达不同。
|
|
18
|
-
return value.quantize(Decimal("0.01"))
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from collections.abc import Awaitable, Callable
|
|
3
|
-
|
|
4
|
-
from python_ddd_framework.fastapi import ActionFilterContext
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class ExportFilter:
|
|
8
|
-
async def invoke(
|
|
9
|
-
self, context: ActionFilterContext, next_action: Callable[[], Awaitable[object]]
|
|
10
|
-
) -> object:
|
|
11
|
-
# 管线扩展:在这里添加端点执行前/后的 HTTP 处理;领域规则仍由调用的应用服务/聚合负责。
|
|
12
|
-
# Filter 的作用域只覆盖已绑定参数到响应准备;不假定流传输已经结束。
|
|
13
|
-
result = await next_action()
|
|
14
|
-
logging.getLogger(__name__).info("File response prepared")
|
|
15
|
-
return result
|