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.
Files changed (90) hide show
  1. python_ddd_framework/authorization/checking.py +35 -0
  2. python_ddd_framework/developer_kit/generation.py +0 -9
  3. python_ddd_framework/developer_kit/templates/module/cookiecutter.json +1 -1
  4. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/README.md +29 -77
  5. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +5 -72
  6. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +1 -3
  7. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +2 -3
  8. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +8 -62
  9. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +1 -2
  10. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/AGENTS.md +12 -8
  11. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +22 -10
  12. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/backend/src/host/main.py.jinja +4 -1
  13. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/architecture.md +40 -6
  14. python_ddd_framework/developer_kit/templates/project/{{cookiecutter.project_name}}/docs/development.md +177 -30
  15. python_ddd_framework/fastapi/__init__.py +2 -0
  16. python_ddd_framework/fastapi/access.py +127 -0
  17. python_ddd_framework/fastapi/access_options.py +13 -0
  18. python_ddd_framework/fastapi/action.py +6 -0
  19. python_ddd_framework/fastapi/manual_action.py +33 -2
  20. python_ddd_framework/fastapi/module.py +2 -0
  21. python_ddd_framework/fastapi/realtime/authentication.py +21 -10
  22. python_ddd_framework/fastapi/realtime/options.py +0 -1
  23. python_ddd_framework/fastapi/realtime/runtime.py +41 -26
  24. python_ddd_framework/fastapi/request_context.py +10 -2
  25. python_ddd_framework/fastapi/routing.py +12 -5
  26. python_ddd_framework/fastapi/server.py +9 -0
  27. python_ddd_framework/fastapi/service_endpoints.py +5 -0
  28. python_ddd_framework/fastapi/streaming.py +95 -0
  29. python_ddd_framework/fastapi/transfer.py +138 -3
  30. python_ddd_framework/invocation/dispatcher.py +2 -24
  31. python_ddd_framework/invocation/scopes.py +5 -3
  32. python_ddd_framework/observability/formatting.py +1 -0
  33. python_ddd_framework/unit_of_work/manager.py +11 -2
  34. {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/METADATA +29 -8
  35. {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/RECORD +39 -86
  36. {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/WHEEL +1 -1
  37. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/__init__.py.jinja +0 -1
  38. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/handler.py.jinja +0 -45
  39. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_approval/payload.py.jinja +0 -8
  40. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/__init__.py.jinja +0 -1
  41. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/handler.py.jinja +0 -35
  42. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/payload.py.jinja +0 -5
  43. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_jobs/order_statistics/schedule.py.jinja +0 -15
  44. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_maintenance_worker.py.jinja +0 -28
  45. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/background_workers/order_statistics_worker.py.jinja +0 -26
  46. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/order_cache.py.jinja +0 -9
  47. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/caching/statistics_cache.py.jinja +0 -9
  48. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/event_handlers/order_changed_handler.py.jinja +0 -23
  49. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_integration_service.py.jinja +0 -71
  50. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/hosted_services/order_observation_handler.py.jinja +0 -16
  51. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration_services/order_reporting_service.py.jinja +0 -16
  52. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/interceptors/order_timing_interceptor.py.jinja +0 -17
  53. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options/order_options.py.jinja +0 -7
  54. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_approval_service.py.jinja +0 -65
  55. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_management_service.py.jinja +0 -35
  56. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/services/order_query_service.py.jinja +0 -41
  57. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application/setting_handlers/approval_setting_observer.py.jinja +0 -17
  58. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/approve_order.py.jinja +0 -6
  59. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/inputs/create_order.py.jinja +0 -7
  60. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/integration_services/order_reporting_service.py.jinja +0 -7
  61. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_approval_service.py.jinja +0 -13
  62. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_management_service.py.jinja +0 -10
  63. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/services/order_query_service.py.jinja +0 -12
  64. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_statistics_snapshot.py.jinja +0 -9
  65. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/views/order_view.py.jinja +0 -12
  66. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/entities/order.py.jinja +0 -48
  67. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/events/order_changed.py.jinja +0 -13
  68. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repositories/order_repository.py.jinja +0 -17
  69. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding/order_seed_contributor.py.jinja +0 -22
  70. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/services/order_approval_service.py.jinja +0 -23
  71. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings/approval_settings.py.jinja +0 -17
  72. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/value_objects/order_title.py.jinja +0 -10
  73. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/constants/order_constants.py.jinja +0 -3
  74. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/enums/order_status.py.jinja +0 -6
  75. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/errors/order_errors.py.jinja +0 -11
  76. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_messages.py.jinja +0 -26
  77. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/messages/order_observation.py.jinja +0 -13
  78. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permission_provider.py.jinja +0 -19
  79. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions/order_permissions.py.jinja +0 -7
  80. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/value_objects/money.py.jinja +0 -18
  81. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/filters/export_filter.py.jinja +0 -15
  82. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/models/refresh_orders.py.jinja +0 -5
  83. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/routers/order_files.py.jinja +0 -76
  84. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/websockets/order_socket.py.jinja +0 -44
  85. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models/order_model.py.jinja +0 -29
  86. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repositories/order_repository.py.jinja +0 -81
  87. python_ddd_framework/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +0 -19
  88. {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/entry_points.txt +0 -0
  89. {python_ddd_framework-0.6.0.dist-info → python_ddd_framework-0.7.0.dist-info}/licenses/LICENSE +0 -0
  90. {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,12 +1,12 @@
1
1
  # {{ cookiecutter.project_name }}
2
2
 
3
- A modular Python application generated with Python DDD Framework {{ cookiecutter.framework_version }}. The initial project contains a Host; add business modules with `pddd add module` and adapt their examples to your requirements.
3
+ A modular Python application generated with Python DDD Framework {{ cookiecutter.framework_version }}. The initial project contains a Host; add business modules with `pddd add module` and implement their business behavior from confirmed requirements.
4
4
 
5
5
  | Document | Purpose |
6
6
  | --- | --- |
7
- | [AGENTS.md](AGENTS.md) | Development instructions and reading order for Codex and other contributors. |
8
- | [Architecture](docs/architecture.md) | Ownership, dependency direction, transaction boundaries, and lifecycle guarantees. |
9
- | [Development guide](docs/development.md) | Recipes for services, permissions, persistence, configuration, events, and background work. |
7
+ | [AGENTS.md](AGENTS.md) | Mandatory reading, implementation, and delivery checks for Codex and other contributors. |
8
+ | [Architecture](docs/architecture.md) | Design principles, DDD ownership, dependency direction, transactions, streaming, and lifecycle boundaries. |
9
+ | [Development guide](docs/development.md) | CLI entry points, artifact/source lookup, design checks, and framework usage recipes. |
10
10
 
11
11
  Each generated business module also has its own README under `backend/src/modules/<name>/`. This README owns application setup and operations.
12
12
 
@@ -26,14 +26,14 @@ pddd dev-init
26
26
 
27
27
  This starts or reuses PostgreSQL and Redis from the Host's development configuration and preserves data. It does not start the application, migrate, seed, or create an `.env` file. The bundled Compose setup requires local URLs with explicit ports; its Redis service does not configure authentication or TLS.
28
28
 
29
- To add the example business module:
29
+ To add a DDD skeleton or a minimal ordinary service:
30
30
 
31
31
  ```sh
32
32
  pddd add module orders --template ddd
33
33
  pddd add module conversions --template basic
34
34
  ```
35
35
 
36
- Read `backend/src/modules/orders/README.md` before changing the sample. The project is also usable as a pure Host without this step.
36
+ The `orders` name here selects a package, not an order implementation. Read `backend/src/modules/orders/README.md` before adding business code; all responsibility directories are present, but no business service, table, route, or test is generated. The project is also usable as a pure Host without this step.
37
37
 
38
38
  ## Initialize the database
39
39
 
@@ -47,13 +47,13 @@ pddd db upgrade --module background_jobs
47
47
  pddd db seed --module identity
48
48
  ```
49
49
 
50
- If you added `orders`, generate its first revision:
50
+ After implementing actual models in a DDD module named `orders`, generate its first revision. An empty skeleton retains model and migration registration but has no business tables or revisions. Migration commands still use the configured database:
51
51
 
52
52
  ```sh
53
53
  pddd db revision --module orders
54
54
  ```
55
55
 
56
- Review the generated file in `backend/src/modules/orders/sqlalchemy/migrations/`, then apply and seed it:
56
+ Review the generated file in `backend/src/modules/orders/sqlalchemy/migrations/`, then apply it; run seed only when that module declares the intended contributors:
57
57
 
58
58
  ```sh
59
59
  pddd db upgrade --module orders
@@ -75,7 +75,7 @@ Open **http://127.0.0.1:8000/docs**. Call `/api/auth/login` using the generated
75
75
 
76
76
  Identity includes paged user/role management, password operations, and version-protected assignments. Before using it, apply the framework's Identity migrations; preserve the returned `concurrency_version` in updates. See [Identity management](docs/development.md#identity-management) for API usage, session invalidation, and Host authorization options.
77
77
 
78
- With the `orders` example installed, `/api/orders` supports create/query, `/api/orders/{id}/approve` approves an order, and `/api/orders/{id}/queue-approval` enqueues approval. Read the module README for its rules and OpenAPI for request schemas. A stale version produces a conflict response.
78
+ Adding a DDD skeleton creates no business HTTP endpoints. Implement the public contracts and use cases first, then inspect OpenAPI and update the module README with their actual behavior.
79
79
 
80
80
  `http.api_prefix` controls business HTTP addresses and defaults to `/api`; `/ws`, `/health/live`, `/health/ready`, and `/docs` retain independent paths. Readiness reports Application lifecycle state, not continuous infrastructure health.
81
81
 
@@ -95,7 +95,7 @@ uv run pytest
95
95
  uv build --no-sources
96
96
  ```
97
97
 
98
- The application tests include real Host infrastructure and require Docker. Module domain tests can be run separately; see [verification](docs/development.md#verification). Module tests are excluded from production wheels and service discovery.
98
+ The application tests include real Host infrastructure and require Docker. A DDD skeleton has no test cases; after authorized domain tests exist, run them separately; see [verification](docs/development.md#verification). Module tests are excluded from production wheels and service discovery.
99
99
 
100
100
  ## Deploy
101
101
 
@@ -135,3 +135,15 @@ uv build --no-sources
135
135
  ```
136
136
 
137
137
  A framework upgrade does not regenerate or overwrite application source, migrations, or these documents. A global CLI upgrade also does not update this project. Apply required adaptations and update the relevant application and module documentation deliberately.
138
+
139
+ <a id="streaming-and-access-migration-07-unreleased"></a>
140
+
141
+ ### Streaming and access migration (0.7)
142
+
143
+ These notes describe the current 0.7.0 implementation; they are not a release announcement. Check the consumed artifact using [installed framework lookup](docs/development.md#match-the-installed-framework) before using these APIs.
144
+
145
+ - Move `fastapi_realtime.authentication_refresh_seconds` to `fastapi_access.authentication_refresh_seconds` in existing environment files and deployment overrides. Rename `PDDD_FASTAPI_REALTIME__AUTHENTICATION_REFRESH_SECONDS` to `PDDD_FASTAPI_ACCESS__AUTHENTICATION_REFRESH_SECONDS` when set. The old realtime field is rejected. `FastApiAccessOptions` belongs to `FastApiModule` and covers managed HTTP streams and WebSocket connections; its default is 60 seconds and minimum is 1. An omitted section uses these defaults; generation does not need an extra configuration block.
146
+ - Existing HTTP streaming code must follow the [preparation, transmission, and cleanup boundaries](docs/architecture.md#http-streaming-and-continuous-access). The [complete generator example](docs/development.md#http-generators-and-continuous-access) demonstrates native SSE, JSONL, and byte streaming. Keep ApplicationService return contracts unchanged and move checks that need ordinary error responses into preparation.
147
+ - `run_host(..., timeout_graceful_shutdown=10)` forwards a non-negative integer number of seconds to Uvicorn. At the deadline Uvicorn requests cancellation; framework drain still waits for streams, subscriptions, and REQUEST resources to actually release. This is not a hard process-exit deadline. A synchronous generator's running `next()` must return; its thread is not abandoned. Applications running Uvicorn directly must configure its native timeout themselves.
148
+
149
+ Review applicable [design principles](docs/architecture.md#mandatory-design-principles) and [delivery checks](docs/development.md#design-and-delivery-checks) when adapting existing consumers. Template examples do not authorize application business, schema, dependency, or infrastructure changes.
@@ -31,10 +31,13 @@ def create_application(
31
31
  def create_web() -> FastAPI:
32
32
  # HTTP 组装扩展:在这里设置站点信息和 Host middleware,并在 install 前完成需要的配置。
33
33
  application = create_application()
34
+ api_prefix = application.configuration.get_path("http.api_prefix")
35
+ if not isinstance(api_prefix, str):
36
+ raise TypeError("http.api_prefix must be a string")
34
37
  # 全局 middleware 使用原生 Starlette 实现,在 adapter.install 前完成配置。
35
38
  adapter = FastApiAdapter(
36
39
  application, middleware=(Middleware(GZipMiddleware),),
37
- api_prefix=application.configuration.get_path("http.api_prefix"),
40
+ api_prefix=api_prefix,
38
41
  )
39
42
  web = FastAPI(
40
43
  title="{{ cookiecutter.project_name }}", version=version("{{ cookiecutter.project_name }}"), lifespan=adapter.lifespan
@@ -6,7 +6,7 @@ This application was generated with Python DDD Framework {{ cookiecutter.framewo
6
6
 
7
7
  ## Composition and ownership
8
8
 
9
- The initial project is a Host without business modules. `pddd add module <name>` adds an example module that the application team adapts to its domain. Generated order-management behavior is a sample, not a declaration of the application's business requirements.
9
+ The initial project is a Host without business modules. `pddd add module <name>` adds a DDD skeleton with the full responsibility directories and necessary composition. Business rules, services, tables, endpoints, and tests are added only from confirmed requirements; no order example is generated.
10
10
 
11
11
  | Owner | Responsibility |
12
12
  | --- | --- |
@@ -51,13 +51,29 @@ Immutable values shared by domain objects and public DTOs belong in `domain_shar
51
51
 
52
52
  Cross-module access uses public service contracts and explicit Module dependencies. A module owns its own data and migrations. Shared database deployment does not authorize another module to query its tables. Keep internal collaboration contracts separate from end-user service contracts where the caller and authorization semantics differ; `IntegrationService` requires its own contract and is not exposed over HTTP.
53
53
 
54
- The [development guide](development.md#module-layout-and-coding-rules) owns the complete directory and naming rules. Query, management, approval, and optional item operations have separate user-facing contracts. Internal reporting belongs to a separate integration contract and implementation. HTTP keeps its existing paths and operation identities through public route overrides when Python services move.
54
+ The [development guide](development.md#module-layout-and-coding-rules) owns the complete directory and naming rules. When those use cases exist, separate query, management, approval, and item contracts by caller needs. Internal reporting uses a separate integration contract and implementation when its caller and authorization boundary differ. HTTP keeps its existing paths and operation identities through public route overrides when Python services move.
55
55
 
56
- Business Modules explicitly depend on the framework capabilities they use. Domain seed contributors depend on `DataSeedingModule`, permission definitions on `AuthorizationModule`, application services on `ApplicationServicesModule`, and HTTP declarations on `FastApiModule`. Host provider selection does not repair a missing business-module dependency. Capability definitions use typed module declarations; their catalogs and runtime state belong to each Application. Every Host and six-layer Module explicitly declares the seven framework hooks. Synchronous configuration phases only compose the application; asynchronous initialization phases follow dependency-first barriers, and shutdown closes resources in reverse dependency order. Empty hooks use `pass`. Generating a complete example does not enable its workers, hosted services, or schedules.
56
+ Business Modules explicitly depend on the framework capabilities they use. Domain seed contributors depend on `DataSeedingModule`, permission definitions on `AuthorizationModule`, application services on `ApplicationServicesModule`, and HTTP declarations on `FastApiModule`. Host provider selection does not repair a missing business-module dependency. Capability definitions use typed module declarations; their catalogs and runtime state belong to each Application. Every Host and six-layer Module explicitly declares the seven framework hooks. Synchronous configuration phases only compose the application; asynchronous initialization phases follow dependency-first barriers, and shutdown closes resources in reverse dependency order. Empty hooks use `pass`. The skeleton has no workers, hosted services, or schedules to enable.
57
+
58
+ ## Mandatory design principles
59
+
60
+ Apply all seven principles that are relevant to a code or architecture change during implementation and review, together with the DDD rules below. An abstraction must serve a real responsibility, public contract, variation point, or reuse need. Counts of files, classes, or interfaces are not evidence of good design.
61
+
62
+ | Principle | Required implementation and review behavior |
63
+ | --- | --- |
64
+ | Single responsibility | Organize by stable capability, owner, and reason to change. Give public contracts/values, mutable state, orchestration, external I/O, and resource lifecycles explicit owners. Split independent responsibilities; related immutable values, enums, and exceptions may remain together. Do not centralize unrelated state or lifetimes in a generic service. |
65
+ | Open/closed | Use existing Module declarations, DI, and native extension points for implementations that satisfy the current contract. Avoid accumulating provider branches in core use cases. A business-semantic change still needs contract review and authorization; do not anticipate hypothetical changes with plugin layers or compatibility paths. |
66
+ | Liskov substitution | Implementations of a contract must preserve its preconditions, postconditions, and invariants, including errors, cancellation, idempotency, and cleanup. Do not require stronger inputs, weaken outcomes, or return success for unsupported behavior. Review replacements against the caller's contract, not just their signatures. |
67
+ | Interface segregation | Separate public contracts by caller use case, permissions, and lifecycle needs. A caller should depend only on the capability it needs. Avoid an all-purpose service interface and mechanical one-method interfaces. |
68
+ | Dependency inversion | Domain and use cases depend on contracts owned by the relevant capability; persistence and external integrations implement those boundaries. The Host selects providers and composes them. Domain/Contracts must not depend on Host, HTTP, or concrete persistence. Reuse framework abstractions directly. |
69
+ | Law of Demeter | Obtain facts and request operations through direct collaborators' public contracts. Do not reach through another module into its implementation, ORM, tables, or mutable internals. Aggregate changes pass through the root. Do not add a forwarding facade merely to shorten a call chain. |
70
+ | Composition reuse | Prefer injected collaborators, composition, and existing framework capabilities. Use inheritance for a real type relationship or native extension point. Avoid base classes combining permissions, transactions, domain state, and external communication. |
71
+
72
+ State belongs to its domain or runtime owner; orchestration coordinates those owners without absorbing their rules. A lifecycle owner must await actual release of its resources. The Host remains a composition root, and stable contracts never depend back on orchestration or infrastructure. Use a class for identity, state, lifecycle, ownership, or a real variation point; keep a stateless internal algorithm private to its capability. These constraints do not require extra layers, wrappers, or unused interfaces. Record and review changes using the [design and delivery checks](development.md#design-and-delivery-checks).
57
73
 
58
74
  ## Mandatory DDD rules
59
75
 
60
- These constraints apply when a capability models aggregates. The module README owns its actual business language and invariants; generated order rules are examples to replace from confirmed requirements.
76
+ These constraints apply when a capability models aggregates. The module README owns its actual business language and invariants; documentation examples do not define its requirements.
61
77
 
62
78
  | Boundary | Required behavior and review criterion |
63
79
  | --- | --- |
@@ -85,6 +101,24 @@ Typed caches inject `DistributedCache[Item]` and take keys/values directly. The
85
101
 
86
102
  Batch cache I/O remains serial. The first unhidden Redis error, invalid payload, or cancellation can stop later commands; reads may refresh expiry or delete expired entries. After a database commit, ordinary cache errors allow other pending writes to run, while cancellation stops later commands. `hide_errors` only hides Redis errors.
87
103
 
104
+ ## HTTP streaming and continuous access
105
+
106
+ `HttpRouter` supports synchronous and asynchronous generator endpoints with native FastAPI SSE, JSONL, and `StreamingResponse`. ApplicationService methods retain their existing return contract: they may return a native streaming Response, but do not become generator methods. Native FastAPI/Starlette own encoding and transmission; the framework manages invocation and resource boundaries.
107
+
108
+ | Phase | Owner and invariant |
109
+ | --- | --- |
110
+ | Preparation | Authorization precedes input binding. Dependencies and Filters perform checks and short business calls needed before sending. Function-scoped dependencies exit, business work commits, and preparation ACTION/UoW resources are released before transmission. The framework does not read the first generator item here; code before the first `yield` still runs during iteration. |
111
+ | Transmission | The HTTP transport owner retains REQUEST and supervises production, sending, and access checks. Framework-supervised reads receive the owning task's request identity and invocation permission, without inheriting preparation ACTION/UoW state. Further database work uses an independent short service call or UoW that ends before `yield`. Ordinary child tasks and external threads do not gain that permission. |
112
+ | Cleanup | After production exits, close the source, release preparation subscriptions, and then release REQUEST. Acquire subscriptions through a native request-scoped yield dependency so they are released even if the response generator never starts. Its unstarted `finally` cannot own a subscription acquired elsewhere. Repeated cancellation must not end the wait for actual cleanup; cleanup failures retain their cause. A synchronous generator's running `next()` must eventually return. |
113
+
114
+ Never carry a business Session, transaction, or AnyIO cancellation scope across a response `yield`. A generator's asynchronous `finally` must shield its own awaited cleanup locally; framework-managed close does not protect arbitrary awaits inside an already-unwinding generator. See the [complete example](development.md#http-generators-and-continuous-access).
115
+
116
+ Managed HTTP streams with authorization requirements check access before sending and periodically afterwards, including while idle or blocked by a slow client. Each check uses an independent short REQUEST, refreshes identity and permissions, and releases its services/UoW before returning. HTTP authenticators and permission checkers can inject the native `Request` and make short managed calls; they do not share the long request's cached services or transaction. Checks for one connection are serialized; a due send waits for the check. User/session changes invalidate access. Failure is recorded before sending is cancelled; WebSocket transport also discards queued business frames. WebSocket continuous checks concern its connection endpoint declarations, while `invoke` authorizes each service call separately without accumulating historical permissions.
117
+
118
+ Routes with no authorization requirement or `allow_anonymous` do not opt into HTTP continuous access checks. Shared authorization short-circuits these cases and `authorization.always_allow` before reading the final identity. `always_allow` does not fabricate a user or accept an invalid token; authentication and real-user requirements remain active. An authenticator may call managed methods admitted by these short-circuits, but reading final `CurrentUser` during authentication still raises the authentication-cycle error. An endpoint-injected `CurrentUser` remains its immutable preparation snapshot; new short calls during streaming use the latest checked snapshot.
119
+
120
+ Actual `http.response.start` is the error boundary. Before it, preparation/access failures can use normal error handlers. After it, terminate and record the failure; do not attempt a replacement JSON response. Shared refresh configuration and Host shutdown timeout belong to [README operations and migration](../README.md#streaming-and-access-migration-07-unreleased).
121
+
88
122
  ## Extension ownership
89
123
 
90
124
  Identity owns core user/role fields, edit versions, and their base migrations. `concurrency_version` protects edits and relationship replacements; `permission_version` only tracks permission state. Password changes, deactivation, and deletion revoke sessions in the same transaction. Login and refresh coordinate through the user's transaction lock; refresh families retain their own lock-before-row mutation order. Consumer modules own only their declared extension columns/indexes and project-local revisions.
@@ -116,12 +150,12 @@ Module initialization precedes selected hosted services and background execution
116
150
  ## Background work and external boundaries
117
151
 
118
152
  - Durable jobs use typed, versioned payloads and the PostgreSQL provider. Transactional enqueue must use the same configured connection as the surrounding unit of work. External side effects remain the job's idempotency responsibility.
119
- - Periodic workers are explicitly registered. Generated example workers are disabled in their class declarations; configuration cannot enable a code-disabled worker.
153
+ - Periodic workers are explicitly registered. No workers are generated. When adding one, configuration cannot enable a worker disabled in its class declaration.
120
154
  - Execution modes are `host`, `shared`, and `exclusive`. Spawned processes compose their own Application; they do not inherit a live Session or container.
121
155
  - A `HostedService` owns long-lived SDKs or threads, their failures, recovery, and actual shutdown. It is opt-in; background-process instances need explicit selection. Shutdown has global barriers: close ordinary admission, await `stopping`, close remaining admission, drain accepted work, then release resources in `stop` before Module/container cleanup. Only successfully started services prepare; all touched services clean up. External callbacks use the separate, temporary stopping context, never an upgraded startup context.
122
156
  - Redis business locks are leases. Preserve cancellation and cleanup when ownership is lost; a lease is not a transaction, fencing guarantee, or exactly-once guarantee.
123
157
  - In-process channels preserve SDK input during stopping only for declared `stopping_owners` and a current hook context passed to `send/submit`. The channel Module depends on the service owner. Admission permissions are Application-local and temporary; accepted work retains its reservation until execution or a reported non-execution outcome, with unchanged capacity and per-message scopes.
124
- - WebSocket delivery targets currently connected clients. The generated sample has no cross-process backplane, offline replay, or delivery acknowledgment. HTTP streaming must release business transactions before network transmission and clean up producers on disconnect.
158
+ - WebSocket delivery targets currently connected clients. The native process-local transport has no cross-process backplane, offline replay, or delivery acknowledgment. HTTP streams follow the [preparation, transmission, and cleanup boundaries](#http-streaming-and-continuous-access).
125
159
  - Liveness and readiness report lifecycle state, not continuous infrastructure availability. Trace export is disabled in the generated development configuration until explicitly configured.
126
160
 
127
161
  ## Documentation ownership
@@ -2,7 +2,22 @@
2
2
 
3
3
  Use this guide with the project's [architecture](architecture.md) and [working rules](../AGENTS.md). It describes the generated application's use of Python DDD Framework {{ cookiecutter.framework_version }}. Follow the installed version when adapting examples after an upgrade.
4
4
 
5
- [Plan a business change](#plan-a-business-change) · [Choose a capability](#choose-a-framework-capability) · [Installed version](#match-the-installed-framework) · [Add a module](#add-a-module) · [Services and permissions](#services-and-permissions) · [Options and settings](#options-and-settings) · [Persistence](#persistence-and-migrations) · [Events and background work](#events-and-background-work) · [Verification](#verification)
5
+ [CLI operations](#cli-operations) · [Plan a business change](#plan-a-business-change) · [Design checks](#design-and-delivery-checks) · [Choose a capability](#choose-a-framework-capability) · [Installed version](#match-the-installed-framework) · [Add a module](#add-a-module) · [HTTP generators](#http-generators-and-continuous-access) · [Verification](#verification)
6
+
7
+ ## CLI operations
8
+
9
+ Use the installed `pddd` CLI for operations it provides. Project commands dispatch into the owning backend environment; do not bypass them with handwritten module generation or direct migration/seed calls. Use each subcommand's `--help` as the parameter authority.
10
+
11
+ | Operation | Entry point |
12
+ | --- | --- |
13
+ | Create a project | `pddd new <name> --python <major.minor[.patch]>`; `--dry-run` previews, `--no-sync` skips environment setup, and `--framework-wheel <path>` selects a local artifact for validation. |
14
+ | Add a module | `pddd add module <name> --template basic` or `--template ddd`; inspect `--dry-run` before adapting generated examples. |
15
+ | Run development | `pddd dev`; inspect `pddd dev --help` for bind options. |
16
+ | Prepare local infrastructure | `pddd dev-init --environment development`; review the configuration first. |
17
+ | Migrate or seed | `pddd db <revision\|upgrade\|status\|seed> --module <alias> --environment <environment>`; follow the [database procedure](../README.md#initialize-the-database). |
18
+ | Inspect composition | `pddd inspect --environment <environment>`; this builds and closes an Application without starting its resources. |
19
+
20
+ The CLI has no build/test command: use native `uv build --no-sources` and pytest from `backend/`, within the approved verification scope. Availability of a command does not authorize dependency, data, or infrastructure changes.
6
21
 
7
22
  ## Plan a business change
8
23
 
@@ -18,7 +33,7 @@ Start with the affected module README and confirmed requirements. For an existin
18
33
 
19
34
  Choose boundaries from business language, invariants, and consistency needs. A database table or a generated layer Module does not establish a bounded context or aggregate boundary. Keep entity transitions and value constraints in Domain, orchestration and caller policy in Application, and storage/transport in their existing owners. Use a domain service for a rule that needs collaborators; retain rules that an aggregate can enforce itself on the aggregate.
20
35
 
21
- An ordinary in-process service can use the [basic template](#add-a-module). A capability that needs the domain, persistence, and HTTP examples can use `ddd`; generated examples are enabled according to their Module declarations. Record the chosen model, reasons, and unresolved business questions in the module README. The generated DDD README contains an order model and an approval walkthrough to adapt; its sample rules are not requirements for a new business domain.
36
+ An ordinary in-process service can use the [basic template](#add-a-module). A capability that needs domain, persistence, and HTTP structure can use `ddd`; business implementations and capability declarations are added when required. Record the chosen model, reasons, and unresolved business questions in the module README. The generated DDD README provides a responsibility record and first-use-case checklist; it contains no preset domain model. Names such as Order in the recipes below are illustrative types to implement only when required, not existing generated files.
22
37
 
23
38
  ### Check a domain change
24
39
 
@@ -32,21 +47,40 @@ Use the [mandatory DDD rules](architecture.md#mandatory-ddd-rules) as the review
32
47
 
33
48
  These are review criteria, not a generated automated architecture-test suite. Use the actual checks listed under [verification](#verification).
34
49
 
50
+ ### Design and delivery checks
51
+
52
+ For code and architecture changes, follow **requirements and rules -> owner -> layer and directory -> framework capability -> implementation -> verification**. Scale the record to the impact: reuse a module README, accepted decision, or approved plan; a local correction can cite an existing conclusion in the task notes. Do not create a new plan or design file just to satisfy this workflow.
53
+
54
+ Before implementation:
55
+
56
+ 1. Read affected requirements, decisions, module guidance, [layer boundaries](architecture.md#business-module-boundaries), and [directory rules](#module-layout-and-coding-rules). Identify the use case, public contract, mutable state, lifecycle owner, and external I/O boundary.
57
+ 2. Record **Owner -> layer -> target directory -> verified framework API**, with responsibility evidence and a matching installed API/example location. Mark framework lookup as not applicable if the change needs none. Check public imports, Module/provider activation, and direct callers before editing.
58
+ 3. For an abstraction or dependency change, identify the real contract, variation point, or reuse need and apply the [seven principles](architecture.md#mandatory-design-principles). Pause affected work if requirements, ownership, contracts, or the approved plan conflict; present the evidence and obtain an explicit ruling. Do not rewrite a rule to authorize an implementation after the fact.
59
+
60
+ Before delivery:
61
+
62
+ 1. Review the stable diff against each applicable principle, imports, Module dependencies, public contracts, state mutation paths, and resource ownership. Domain changes also require the [aggregate and consistency checks](#check-a-domain-change).
63
+ 2. Check inputs, results, errors, cancellation, idempotency, and cleanup against the actual contract when replacing an implementation. Confirm that new abstractions reuse existing framework capabilities and do not duplicate policy or merely forward calls.
64
+ 3. Run the smallest relevant existing checks and authorized build; record commands, outcomes, and coverage limits. Fix confirmed violations within scope or report the blocking decision. A passing build or unrelated tests do not establish correct ownership. Additional dependencies, tests, gates, and infrastructure still require task authorization.
65
+ 4. Update the affected owner documents and report changes, evidence, unverified behavior, and material risks. For documentation-only changes, check contracts, links, anchors, fences, examples, and the diff; do not claim that this proves runtime behavior.
66
+
67
+ These mandatory reviews are distinct from automated checks. Only claim gates actually provided by the project under [verification](#verification). If future architecture gates are approved, discover boundaries from live source and Module declarations instead of maintaining a second module inventory.
68
+
35
69
  ## Choose a framework capability
36
70
 
37
71
  Use this table to reach an existing implementation. Business Modules declare capability dependencies and typed contributions; the Host selects providers and supplies configuration. Read activation rules at those owners and use `pddd inspect` to check composition.
38
72
 
39
73
  | Task | Existing capability and boundary | Start here |
40
74
  | --- | --- | --- |
41
- | Implement a user operation or expose HTTP | Public ApplicationService contract, permission policy, and explicit HTTP exposure in the owning Application/HttpApi Modules | [Services and permissions](#services-and-permissions); module README's approval walkthrough |
75
+ | Implement a user operation or expose HTTP | Public ApplicationService contract, permission policy, and explicit HTTP exposure in the owning Application/HttpApi Modules | [Services and permissions](#services-and-permissions); module README's first-use-case checklist |
42
76
  | Add login or user/role administration | Existing Identity authentication and management contracts, with the Host's provider and authorization configuration | [Identity management](#identity-management) |
43
77
  | Call an internal service | Inject its public contract through a declared Module dependency; ordinary DI services opt into interception only when required | [Services and permissions](#services-and-permissions); [basic module](#add-a-module) |
44
78
  | Save an aggregate in one business operation | Repository contract, SQLAlchemy implementation, and the managed UoW; keep commit at the invocation boundary | [Persistence](#persistence-and-migrations); module repository and SqlAlchemy Module |
45
79
  | Configure startup policy or change a runtime setting | `Options[T]` for bound startup configuration; `SettingProvider`/`SettingManager` for defined runtime values and conditional updates | [Options and settings](#options-and-settings); existing Options type and Domain setting provider |
46
80
  | React to a domain change | Aggregate events and `LocalEventPhase` handlers; choose transactional or AFTER_COMMIT semantics explicitly | [Events](#events-and-background-work); aggregate and event handler in the module README |
47
- | Run work durably or on a schedule | Typed Job and payload, `BackgroundJobEnqueuer`, and a registered schedule when needed; Host selects the queue provider | [Background work](#events-and-background-work); existing approval Job and Application Module |
48
- | Own a periodic loop or SDK lifetime | Background Worker or HostedService with explicit enablement and awaited cleanup | [Background work](#events-and-background-work); module HostedService example |
49
- | Cache a projection or notify connected clients | `DistributedCache[T]` and realtime APIs; preserve commit order and distinguish online notification from durable delivery | [Caching](#events-and-background-work); [HTTP and realtime](#http-files-and-real-time-communication) |
81
+ | Run work durably or on a schedule | Typed Job and payload, `BackgroundJobEnqueuer`, and a registered schedule when needed; Host selects the queue provider | [Background work](#events-and-background-work); the owning Job definition and Application Module |
82
+ | Own a periodic loop or SDK lifetime | Background Worker or HostedService with explicit enablement and awaited cleanup | [Background work](#events-and-background-work); the owning HostedService declaration |
83
+ | Cache a projection or notify connected clients | `DistributedCache[T]`, native HTTP generators, and realtime APIs; preserve commit order and distinguish online notification from durable delivery | [Caching](#events-and-background-work); [HTTP generators](#http-generators-and-continuous-access); [realtime](#http-files-and-real-time-communication) |
50
84
  | Extend a reusable module | Existing service override or explicitly declared DTO/model extension point | [Module extensions](#extend-reusable-modules) |
51
85
 
52
86
  Follow the linked minimal example, inspect its owner and direct callers, and verify composition before adding a parallel mechanism. If an existing API cannot meet a requirement, identify the exact missing behavior and the installed source checked; propose the smallest change within the task's authorization.
@@ -63,7 +97,11 @@ uv run --no-sync pddd inspect --environment development
63
97
 
64
98
  Compare the installed distribution with `backend/uv.lock` and the requested dependency/source in `backend/pyproject.toml`. A mismatch needs the project's documented environment setup before API conclusions are reliable. `inspect` composes a new Application and closes it without starting resources; it does not show a running process or verify database connectivity.
65
99
 
66
- The printed package path locates the implementation; its `developer_kit/templates/project` and `developer_kit/templates/module` directories contain this version's guidance and examples. Read the affected API and example together, and confirm that the application declares the needed Module/provider. Treat installed files as read-only references. After an upgrade, use the target release's migration notes and update affected application guidance deliberately; copying whole templates over business code or editing the installed package is not an upgrade procedure.
100
+ The printed package path locates the implementation; its `developer_kit/templates/project` and `developer_kit/templates/module` directories contain this version's guidance and skeletons. Read the affected API and relevant usage example together, and confirm that the application declares the needed Module/provider. Treat installed files as read-only references. After an upgrade, use the target release's migration notes and update affected application guidance deliberately; copying whole templates over business code or editing the installed package is not an upgrade procedure.
101
+
102
+ If the user supplies a local framework checkout, treat its path as a reference provided for this task. Read its contributor guidance, relevant architecture/development section or accepted ADR, then the affected public API, implementation, and existing tests. Inspect its HEAD and dirty state with `git -C <framework-source> rev-parse HEAD` and `git -C <framework-source> status --short`; substitute the supplied path. Do not embed a personal absolute path in application guidance or add a source override merely because a checkout is available.
103
+
104
+ Compare that checkout's metadata, relevant source contents, and release/build evidence with the locked and installed artifact. Equal version numbers alone do not prove identical contents. The installed distribution defines APIs available to this application; unmatched newer source is evidence for a possible upgrade, not a currently available capability. Where formal contracts and implementation disagree, report both and resolve the conflict before relying on it. A checkout remains read-only and grants neither framework modification permission nor an application dependency. Existing framework tests and release records retain their original input and coverage; application integration needs its own evidence.
67
105
 
68
106
  ## Add a module
69
107
 
@@ -79,7 +117,7 @@ The CLI generates `backend/src/modules/orders/`, registers its application/persi
79
117
 
80
118
  The default is `ddd`. `basic` instead wires one ordinary Module and its alias, with a synchronous `ConversionService` Protocol and `DefaultConversionService(ConversionService, TransientDependency)`. Each REQUEST resolution creates an instance. Its only files are package markers, `module.py`, the contract, implementation, and README; no providers, Domain, database, migration, seed, HTTP, or background resources are generated. A consuming Module declares a dependency and injects the interface. The basic README shows formal replacement through `post_configure`. Invalid template names are rejected before writes; both templates support dry-run and refuse overwrite.
81
119
 
82
- Read the generated module README before replacing the order example with your domain. The examples below assume an `orders` module; substitute your actual package and contract names. The application remains usable as a pure Host before adding a module.
120
+ Read the generated module README before implementing your domain. Recipes below illustrate an `orders` capability; its business types are not generated. Define the needed types from confirmed requirements and substitute your actual names. The application remains usable as a pure Host before adding a module.
83
121
 
84
122
  ## Module layout and coding rules
85
123
 
@@ -98,14 +136,14 @@ For `basic`, keep public interfaces in `contracts/` and implementations in `serv
98
136
  - Give each independent service, handler, task, and lifecycle owner its own file. Group parts of one capability, such as `background_jobs/order_approval/payload.py` and `handler.py`, in a capability directory. Closely related small immutable values, enums, and exceptions may share a file; line count and class count are not splitting rules.
99
137
  - `module.py` declares dependencies, registrations, and lifecycle hooks. `__init__.py` marks a package; it owns no business code, metadata state, or compatibility re-exports. The ORM metadata owner lives in `sqlalchemy/models/base.py`.
100
138
  - User query, management, approval, and item operations belong in separate `services`. Internal reporting contracts and implementations go in their respective `integration_services`. Thread/SDK lifecycle owners go in `hosted_services`. Inputs and output projections go in `inputs` and `views`; transport-only models go in `http_api/models`.
101
- - Shared immutable values such as `Money` belong in `domain_shared/value_objects`; domain-only values such as `OrderTitle` belong in `domain/value_objects`. Both public DTOs and domain objects may reuse a shared value without Contracts depending on Domain. Keep a single definition owner. The generated Money example does not add monetary fields to the sample HTTP contract.
139
+ - Shared immutable values such as `Money` belong in `domain_shared/value_objects`; domain-only values such as `OrderTitle` belong in `domain/value_objects`. Both public DTOs and domain objects may reuse a shared value without Contracts depending on Domain. Keep a single definition owner. These illustrative names do not require monetary fields in your HTTP contract.
102
140
  - Preserve framework typing and native extension points. Use classes for identity, state, lifecycle, or real variation; keep stateless algorithms private to their capability. Avoid `common`, `utils`, `helpers`, aggregate facades, speculative interfaces, and old-path aliases. Explain key invariants and failure paths with concise Chinese source comments.
103
141
 
104
- The `ddd` template supplies directories and executable examples for supported standard capabilities even when the business does not use them yet. Migrations remain generated from real models. This exception is limited to CLI templates and generated projects; it does not authorize speculative framework layers.
142
+ The `ddd` template retains all responsibility directories and package markers, six Module classes, an ORM Base, model discovery, migration registration, and the module README. It generates no business implementation, placeholder service, table, revision, or test case. Add capability dependencies and declarations as the implementation needs them. The full-directory exception is limited to CLI templates and generated projects; it does not authorize speculative framework layers.
105
143
 
106
144
  All Host and six-layer Modules explicitly define seven hooks with framework context types. `pre_configure` contributes PreOptions, `configure` registers services and Options, and `post_configure` completes Options or overrides existing registrations. The async `pre_initialize`, `initialize`, and `post_initialize` hooks prepare, initialize, and finish resources. Async `shutdown` waits for cleanup. Empty phases use `pass`; configuration phases do not start I/O, threads, or tasks. Do not manually start framework-managed background components in these hooks.
107
145
 
108
- Worker classes default to `enabled=False`; enabling one requires an explicit code decision. To opt into the hosted example, import `OrderIntegrationService` from `application/hosted_services/order_integration_service.py` in `application/module.py` and declare `hosted_services = HostedServices((OrderIntegrationService,))`. To enable the scheduled example, import `STATISTICS_SCHEDULE` from `application/background_jobs/order_statistics/schedule.py` and declare `background_job_schedules = BackgroundSchedules((STATISTICS_SCHEDULE,))`. Choose the background execution profile/modes in Host configuration. A discovered Job handler alone does not enable periodic enqueue.
146
+ The skeleton declares no workers, hosted services, schedules, settings, permissions, or jobs. Introduce each capability at its owning Module when required. Hosted services and schedules require explicit declarations; a discovered Job handler alone does not enable periodic enqueue. Choose provider and execution placement in Host configuration.
109
147
 
110
148
  When splitting services, update Python contracts/imports directly and retain existing HTTP paths, verbs, status codes, response shapes, permissions, and operation IDs using `ApplicationServiceRouteOverride`. Preserve commit-before-event handling, cache removal before notification, transactional enqueue, idempotent Job handling, and awaited resource cleanup.
111
149
 
@@ -117,7 +155,7 @@ When splitting services, update Python contracts/imports directly and retain exi
117
155
  4. Implement the contract with `ApplicationService` in `application/`. Keep permission and HTTP method policies on the final implementation.
118
156
  5. Keep `scan_packages` scoped to the owning package. The generated application module already scans its package; the HTTP module explicitly exposes its contracts.
119
157
 
120
- The generated implementation illustrates a managed write:
158
+ After defining the illustrated business types, a method inside an ApplicationService can implement a managed write as follows:
121
159
 
122
160
  ```python
123
161
  from uuid import uuid4
@@ -131,7 +169,7 @@ from ...domain.entities.order import Order
131
169
  from ...domain.value_objects.order_title import OrderTitle
132
170
  from ...domain_shared.permissions.order_permissions import ORDERS_WRITE
133
171
 
134
- # Method inside the generated ApplicationService implementation:
172
+ # Method inside your ApplicationService implementation:
135
173
  @authorize(ORDERS_WRITE)
136
174
  @http.post(status_code=201)
137
175
  async def create(self, command: CreateOrder) -> OrderView:
@@ -148,7 +186,7 @@ For ordinary services, use the module's existing registration methods or native
148
186
 
149
187
  Ordinary DI services can use `@unit_of_work`, `@authorize`, or inherit `ValidationEnabled` without becoming ApplicationServices. The owning Module must depend directly or transitively on UnitOfWorkModule, AuthorizationModule, or InvocationModule respectively. Preserve native scope/cache registration. Marked methods must be async instance methods; a class-wide declaration cannot cover synchronous, static, class, or async-generator methods. Unmarked synchronous members keep their original signatures and results. Call injected services directly; use `call` / `call_as` to inject them for an external caller. Manual construction and self-calls do not re-enter interception.
150
188
 
151
- The generated Domain `OrderApprovalService` explicitly enables validation and remains transient. Its caller still owns saving and transaction completion. Interceptor match functions receive the shared `ServiceMethod`; plain services and ApplicationServices share the same invocation stages, while ApplicationServices retain their REQUEST-to-ACTION proxy and explicit HTTP exposure rules.
189
+ A Domain service can explicitly enable validation while retaining its transient lifetime; its caller still owns saving and transaction completion. Interceptor match functions receive the shared `ServiceMethod`; plain services and ApplicationServices share the same invocation stages, while ApplicationServices retain their REQUEST-to-ACTION proxy and explicit HTTP exposure rules.
152
190
 
153
191
  ## Extend reusable modules
154
192
 
@@ -191,7 +229,7 @@ class ApprovalPolicy:
191
229
  return self._options.value.allow_background_approval
192
230
  ```
193
231
 
194
- This demonstrates the existing Options declaration and injection pattern; extend the generated types rather than defining a second `OrdersOptions`. Register any new ordinary service in the owning module. Configure the generated option in `backend/app.development.yaml`:
232
+ This illustrates Options declaration and injection; no `OrdersOptions` is generated. Define it only for an authorized requirement and reuse one owner rather than duplicating the type. Register the ordinary service in its module and configure this illustrative option in `backend/app.development.yaml`:
195
233
 
196
234
  ```yaml
197
235
  orders:
@@ -200,7 +238,7 @@ orders:
200
238
 
201
239
  The corresponding environment key is `PDDD_ORDERS__ALLOW_BACKGROUND_APPROVAL`. `PDDD_HTTP__API_PREFIX` changes the business API prefix. `{{ cookiecutter.host_environment_variable }}` selects the Host environment and is separate from business configuration. Values that affect composition require the framework's pre-configuration phase; do not read unbound Options during construction.
202
240
 
203
- Use runtime settings when a value must be changed while the application is running. Add definitions through a scanned `SettingDefinitionProvider`, read them through `SettingProvider`, and use `SettingManager` or the management API for conditional updates/reset with the queried version token. Application services can use `self.setting_provider` during managed invocation, not during construction. The sample's approval setting is defined in `domain/settings/approval_settings.py`.
241
+ Use runtime settings when a value must be changed while the application is running. Add definitions through a scanned `SettingDefinitionProvider`, read them through `SettingProvider`, and use `SettingManager` or the management API for conditional updates/reset with the queried version token. Application services can use `self.setting_provider` during managed invocation, not during construction. Setting definitions belong under `domain/settings/`; declare SettingsModule on the owner when introducing them.
204
242
 
205
243
  Inspect configuration sources and service registrations with:
206
244
 
@@ -214,13 +252,13 @@ This composes and closes a new Application without starting it. Output contains
214
252
 
215
253
  Use `find` for optional entities and `get` for required entities. Standard list/count/page operations share one native SQLAlchemy query and stable primary-key tie breaking; pass explicit loader options for details. `auto_save=True` flushes, while the owning UoW controls commit. Custom `save` methods keep explicit aggregate/row mapping and pass the appropriate entity event to `operation`.
216
254
 
217
- The generated repository interface inherits `RepositoryContract, Protocol`. Its SQLAlchemy implementation nominally implements that contract under `sqlalchemy/repositories/`, which the persistence module scans. Keep Sessions and ORM types inside this layer.
255
+ Define a repository interface with `RepositoryContract, Protocol`. Its SQLAlchemy implementation nominally implements that contract under `sqlalchemy/repositories/`, which the persistence module scans. Keep Sessions and ORM types inside this layer.
218
256
 
219
257
  Map aggregates and rows explicitly within `SqlAlchemySessionProvider.operation`. Pass `aggregate=order` when staging an aggregate write, keep the optimistic version checks, and let that boundary collect the aggregate's events. Avoid collecting the same events again in the service.
220
258
 
221
259
  Place model files in `sqlalchemy/models/` and inherit the package's Base. `SqlAlchemyModelRegistration.from_package` imports that package before freezing metadata, so a new model file does not need a separate import registry. Keep migration scripts in the separate `sqlalchemy/migrations/` package.
222
260
 
223
- After the initial provider setup in the [README](../README.md#initialize-the-database):
261
+ After adding actual models and preparing the providers in the [README](../README.md#initialize-the-database), generate a revision. An empty skeleton is rejected before database I/O:
224
262
 
225
263
  ```sh
226
264
  pddd db revision --module orders
@@ -246,7 +284,7 @@ Raise domain events on the aggregate using frozen records and stable nested valu
246
284
 
247
285
  Inject `DistributedCache[OrderCacheItem]` and call `get`, `set`, `remove`, or their batch variants with keys directly. Cache item classes in `application/caching/` own stable `@cache_name` metadata. Omitted entry options use global defaults (20-minute sliding expiration); an explicit `DistributedCacheEntryOptions` replaces the entire object. `get_or_add` uses an async factory; cache access error hiding does not hide factory/type errors or cancellation. Development configuration explicitly disables `caching.hide_errors`. Use `consider_uow=True` when a cache mutation must wait for successful UoW completion; handle post-commit failure as committed database work.
248
286
 
249
- Jobs inherit `BackgroundJobHandler[Payload]`, declare their durable name/version/current/timeout on the class, and are discovered by module scanning. The generated `ApprovalJob` and `ApprovalPayload` show an idempotent business handler. Enqueue by type from a managed invocation, using the same configured database connection for transactional enqueue:
287
+ Jobs inherit `BackgroundJobHandler[Payload]`, declare their durable name/version/current/timeout on the class, and are discovered by module scanning. The following fragment assumes an application-defined `ApprovalJob` and `ApprovalPayload`; these types are not generated. Enqueue by type from a managed invocation, using the same configured database connection for transactional enqueue:
250
288
 
251
289
  ```python
252
290
  from uuid import UUID
@@ -260,15 +298,15 @@ async def queue_approval(jobs: BackgroundJobEnqueuer, order_id: UUID) -> str:
260
298
  return await jobs.enqueue(ApprovalJob, ApprovalPayload(order_id=order_id))
261
299
  ```
262
300
 
263
- Keep persisted payload versions until their queued jobs are drained or explicitly migrated. A job's external side effects must tolerate repeat execution. A schedule takes a Job type (or a registered function's definition), a typed payload and cron: `BackgroundJobSchedule(name="orders.statistics", cron="0 * * * *", job=OrderStatisticsJob, payload=StatisticsPayload())`. The generated statistics schedule shows this declaration. Register it explicitly with `BackgroundSchedules`; task identity and serialization come from the Application's Job catalog, while PgQueuer owns scheduling.
301
+ Keep persisted payload versions until their queued jobs are drained or explicitly migrated. A job's external side effects must tolerate repeat execution. A schedule takes a Job type (or a registered function's definition), a typed payload and cron: `BackgroundJobSchedule(name="orders.statistics", cron="0 * * * *", job=OrderStatisticsJob, payload=StatisticsPayload())`. These names illustrate application-defined types; no schedule is generated. Register it explicitly with `BackgroundSchedules`; task identity and serialization come from the Application's Job catalog, while PgQueuer owns scheduling.
264
302
 
265
- Workers implement `BackgroundWorker.run_iteration` and are registered with `background_workers = BackgroundWorkers((WorkerType,))`; their period, timeout, and enabled state live on the class. Both generated workers are disabled. Change a worker's code declaration deliberately before expecting it to run; configuration and management endpoints cannot enable a code-disabled worker.
303
+ Workers implement `BackgroundWorker.run_iteration` and are registered with `background_workers = BackgroundWorkers((WorkerType,))`; their period, timeout, and enabled state live on the class. The skeleton contains no worker classes. Change a worker's code declaration deliberately before expecting it to run; configuration and management endpoints cannot enable a code-disabled worker.
266
304
 
267
305
  Background execution Options use catalog names to select `host`, `shared`, or `exclusive` placement. Only worker configuration accepts `enabled`; job configuration does not. Management start/stop is asynchronous: query status after a request. Stopping a job processor preserves its queued data.
268
306
 
269
307
  Use the existing `DistributedLock` provider for explicit business leases. `locks.acquire(key, wait_timeout=timedelta(0))` tries without waiting; omit the argument or pass `None` for configured waiting, or pass a positive timedelta for this acquisition only. If acquisition returns false, skip or report the operation as appropriate. Preserve cancellation and cleanup on lease loss. Use database concurrency and idempotency for their separate guarantees.
270
308
 
271
- A `HostedService` starts its SDK/thread in `start(context)`, initiates business cleanup and waits for callbacks in optional `stopping(context, reason)`, then releases resources and joins threads in `stop(reason)`. Register it with `hosted_services = HostedServices((ServiceType,))`; the generated integration example is not registered by default. It demonstrates an independent `context.call` and a thread callback using `context.submit_call(...).result()`. Each call creates its own scope; never pass a Session or scope to the thread or hold a transaction while waiting for the SDK.
309
+ A `HostedService` starts its SDK/thread in `start(context)`, initiates business cleanup and waits for callbacks in optional `stopping(context, reason)`, then releases resources and joins threads in `stop(reason)`. Register it with `hosted_services = HostedServices((ServiceType,))`; the skeleton registers none. Use `context.call` for an independent managed call and `context.submit_call(...).result()` for a callback from an external thread. Each call creates its own scope; never pass a Session or scope to the thread or hold a transaction while waiting for the SDK.
272
310
 
273
311
  The stopping context is separate from the startup context, which stays closed to new work. Its permission ends when the hook returns, fails, or receives a timeout cancellation request; accepted work still drains before resource stop. Only successfully started services receive `stopping`, while every touched service must tolerate `stop` after partial startup. Each hook uses `hosted_service.shutdown_timeout`, requests cancellation on expiry, and waits for actual cleanup; it is not a total shutdown deadline. HTTP/WebSocket use native transport shutdown.
274
312
 
@@ -278,21 +316,130 @@ The Host passes `http.api_prefix` to `FastApiAdapter`, defaulting to `/api`. The
278
316
 
279
317
  For explicit business routes, use `HttpRouter(use_api_prefix=True, ...)` and inject public service contracts. Independent endpoints such as WebSocket and health routes retain separate paths. Native FastAPI parameter and response types remain supported.
280
318
 
281
- Use `UploadFile`, `File`, and `Form` for uploads and native response types for downloads/streams. Keep byte limits enforced while reading. Release database work before sending a stream, pass detached data to the producer, and protect asynchronous cleanup in its `finally` block; never carry the previous business Session across yields.
319
+ Use `UploadFile`, `File`, and `Form` for uploads and native response types for downloads/streams. Keep byte limits enforced while reading. For generator endpoints, preparation dependencies, and resource cleanup, use the [complete streaming example](#http-generators-and-continuous-access).
282
320
 
283
- The example WebSocket accepts authenticated clients, sends an initial snapshot, and handles `{}` as a refresh request. `OrderChangedMessage` and `OrderSnapshotMessage` in `domain_shared/messages/order_messages.py` own versioned `message_type` values and Pydantic payload schemas. Send an instance through `publisher.send_to_user(user_id, message)` or `connection.send(message)`; explicitly map domain events and query results to that schema. The envelope retains its ID, UTC time and correlation, and reports only local queue acceptance. Invocations recheck identity and permissions. Non-loopback deployments require WSS; query-token authentication requires an explicit Origin policy. Sample notifications stay within the Host process and do not promise replay or delivery acknowledgment.
321
+ No WebSocket route or message type is generated. When the business requires realtime communication, typed messages under `domain_shared/messages/` own their versioned `message_type` values and Pydantic payload schemas. Send an instance through `publisher.send_to_user(user_id, message)` or `connection.send(message)`; explicitly map domain events and query results to that schema. The envelope retains its ID, UTC time and correlation, and reports only local queue acceptance. Invocations recheck identity and permissions. Non-loopback deployments require WSS; query-token authentication requires an explicit Origin policy. Native notifications stay within the Host process and do not promise replay or delivery acknowledgment.
284
322
 
285
323
  `SettingManager.update(definition, value, expected_version=version)` pairs `SettingDefinition[T]` with `value: T`; read the version before update/reset. Reset follows current defaults while retaining a new version token, and saving remains separate from component refresh. A business error may carry `BusinessError(DEFINITION, data=PublicData(...))`, where PublicData is a Pydantic model of intentionally public fields. HTTP omits data when absent; do not put raw exception text, SQL or secrets in the public DTO.
286
324
 
287
325
  Use `logging.getLogger(__name__)` for application logs. Configure logging in the Host's YAML, keep credentials and payloads out of messages, and enable existing tracing export only when its destination is configured. Avoid process-global logging/tracing setup in a business module.
288
326
 
327
+ ## HTTP generators and continuous access
328
+
329
+ `HttpRouter` supports sync and async generators. Put authorization, input checks, and business work that must finish before sending in preparation dependencies or Filters. Native `Depends` functions use Dishka `@inject` / `FromDishka`; endpoint parameters can use the framework's type inference. Preparation does not read the first item. ApplicationService methods keep ordinary return contracts and may return a native `StreamingResponse`; do not convert them into generator methods.
330
+
331
+ The following standalone `backend/stream_example.py` needs only the installed framework and its existing dependencies. It demonstrates a short service call and a finite subscription using AnyIO's native memory stream, without database or Redis providers. Production subscriptions use the owning SDK's resource API and the same request-scoped dependency boundary. This is a usage example, not a business requirement or an additional generated module.
332
+
333
+ ```python
334
+ from collections.abc import AsyncIterator, Iterator
335
+ from pathlib import Path
336
+ from typing import Annotated
337
+
338
+ from anyio import CancelScope, create_memory_object_stream
339
+ from anyio.streams.memory import MemoryObjectReceiveStream
340
+ from dishka.integrations.fastapi import FromDishka, inject
341
+ from fastapi import Depends, FastAPI
342
+ from fastapi.responses import StreamingResponse
343
+ from fastapi.sse import EventSourceResponse, ServerSentEvent
344
+ from pydantic import BaseModel
345
+
346
+ from python_ddd_framework import ApplicationBuilder, ApplicationService, AppModule
347
+ from python_ddd_framework.fastapi import FastApiAdapter, FastApiModule, HttpRouter, contributes_routers
348
+
349
+
350
+ class Reading(BaseModel):
351
+ value: int
352
+
353
+
354
+ class ReadingApplicationService(ApplicationService):
355
+ async def get(self) -> Reading:
356
+ # Real queries belong in this short call; return detached values.
357
+ return Reading(value=1)
358
+
359
+
360
+ @inject
361
+ async def prepare(service: FromDishka[ReadingApplicationService]) -> Reading:
362
+ return await service.get()
363
+
364
+
365
+ async def subscribe(
366
+ initial: Annotated[Reading, Depends(prepare)],
367
+ ) -> AsyncIterator[MemoryObjectReceiveStream[Reading]]:
368
+ reading_stream = create_memory_object_stream[Reading]
369
+ send, receive = reading_stream(1)
370
+ try:
371
+ await send.send(initial)
372
+ await send.aclose()
373
+ yield receive
374
+ finally:
375
+ # Request cleanup also runs when the response generator never starts.
376
+ with CancelScope(shield=True):
377
+ await receive.aclose()
378
+ await send.aclose()
379
+
380
+
381
+ router = HttpRouter()
382
+ Subscription = Annotated[MemoryObjectReceiveStream[Reading], Depends(subscribe, scope="request")]
383
+
384
+
385
+ @router.get("/events", response_class=EventSourceResponse)
386
+ async def events(source: Subscription) -> AsyncIterator[ServerSentEvent]:
387
+ async for item in source:
388
+ yield ServerSentEvent(data=item, event="reading")
389
+
390
+
391
+ @router.get("/jsonl")
392
+ async def jsonl(source: Subscription) -> AsyncIterator[Reading]:
393
+ async for item in source:
394
+ yield item
395
+
396
+
397
+ @router.get("/bytes", response_class=StreamingResponse)
398
+ def download(initial: Annotated[Reading, Depends(prepare)]) -> Iterator[bytes]:
399
+ # Native thread-pool next() must return; shutdown does not abandon it.
400
+ yield str(initial.value).encode()
401
+
402
+
403
+ @contributes_routers(router)
404
+ class StreamingModule(AppModule):
405
+ dependencies = (FastApiModule,)
406
+ scan_packages = (__name__,)
407
+
408
+
409
+ def create_app() -> FastAPI:
410
+ application = ApplicationBuilder(
411
+ StreamingModule, environment="development", base_path=Path(__file__).parent,
412
+ env_prefix="STREAM_",
413
+ ).build()
414
+ adapter = FastApiAdapter(application)
415
+ web = FastAPI(lifespan=adapter.lifespan)
416
+ adapter.install(web)
417
+ return web
418
+ ```
419
+
420
+ From `backend/`, run the standalone factory; use a second terminal for the requests:
421
+
422
+ ```sh
423
+ uv run python -c 'from python_ddd_framework.fastapi.server import run_host; run_host("stream_example:create_app", host="127.0.0.1", port=8000)'
424
+ curl -N http://127.0.0.1:8000/events
425
+ curl -N http://127.0.0.1:8000/jsonl
426
+ curl -N http://127.0.0.1:8000/bytes
427
+ ```
428
+
429
+ Expect an SSE `reading` event containing `{"value":1}`, a JSONL record with that value, and the byte `1`, respectively. [Native SSE](https://fastapi.tiangolo.com/tutorial/server-sent-events/), [JSONL](https://fastapi.tiangolo.com/tutorial/stream-json-lines/), and [yield dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/) supply protocol and dependency behavior; framework scope and authorization boundaries are defined in [architecture](architecture.md#http-streaming-and-continuous-access).
430
+
431
+ For database access during async iteration, call the injected service proxy or use `async with manager.begin() as work`, complete the work with `await work.complete()`, and exit before yielding detached data. Never retain the preparation Session or transaction across `yield`. Preparation-only yield dependencies may use native `Depends(..., scope="function")`; subscriptions needed for transmission use `scope="request"` as above. If a generator itself acquires resources, shield awaited cleanup locally inside its `finally`. Do not keep a `CancelScope` open across `yield`. Unstarted generators cannot clean up subscriptions acquired during preparation; repeated cancellation does not make resource release optional.
432
+
433
+ The example declares no authorization. For a protected route, apply the existing `@authorize(...)` declaration with the application's permission definition and Host-selected Identity provider. Continuous checks then use independent short REQUEST scopes, including while idle or sending to a slow client. Custom HTTP authenticators/checkers may inject native `Request` and use short managed calls/UoWs; never retain their scope or transaction between checks. The [architecture rules](architecture.md#http-streaming-and-continuous-access) cover anonymous routes, `always_allow`, identity snapshots, authentication-cycle protection, and errors before/after `http.response.start`. Configuration and shutdown operations are in the [migration notes](../README.md#streaming-and-access-migration-07-unreleased).
434
+
289
435
  ## In-process messages
290
436
 
291
- Generated modules include `domain_shared/messages/order_observation.py` and
292
- `application/hosted_services/order_observation_handler.py` as an optional external integration
293
- example. Existing callbacks remain direct calls. In that module's `application/module.py`,
294
- import the two types and `observation_identity`, add `MessagingModule` to `dependencies`, then
295
- add this declaration and binding:
437
+ For a business requirement that needs replaceable observations, define the message value in
438
+ `domain_shared/messages/` and its `MessageHandler[OrderObservation]` in
439
+ `application/hosted_services/`. The handler implements async `handle(message)` and owns the
440
+ actual processing; these types are not generated. In the owning `application/module.py`, import
441
+ the message, handler, and business-owned `observation_identity`, add `MessagingModule` to
442
+ `dependencies`, then declare the channel and binding:
296
443
 
297
444
  ```python
298
445
  from python_ddd_framework import MessageChannelDefinition, MessagingModule
@@ -346,7 +493,7 @@ neither capacity nor handler validation, authorization, scope/UoW or receipt sem
346
493
  Run native uv/test/build commands from `backend/`. Installed `pddd` commands locate the backend from the application root or any descendant, including nested foreign Python projects. Read fixtures first: Host tests use disposable PostgreSQL and Redis containers, so Docker must be available.
347
494
 
348
495
  ```sh
349
- # After adding an orders module: its domain tests do not need Docker.
496
+ # After implementing and authorizing domain tests; the skeleton has no test cases.
350
497
  uv run pytest src/modules/orders/tests
351
498
 
352
499
  # Full application tests include real Host infrastructure.