takt 0.49.0 → 0.51.0
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.
- package/README.md +31 -0
- package/builtins/en/config.yaml +6 -0
- package/builtins/en/facets/instructions/ai-antipattern-review.md +5 -0
- package/builtins/en/facets/instructions/findings-manager.md +6 -2
- package/builtins/en/facets/instructions/fix.md +9 -3
- package/builtins/en/facets/instructions/loop-monitor-fix-replan.md +21 -0
- package/builtins/en/facets/instructions/loop-monitor-replan.md +10 -0
- package/builtins/en/facets/instructions/loop-monitor-reviewers-fix.md +6 -0
- package/builtins/en/facets/instructions/plan-maintenance.md +10 -7
- package/builtins/en/facets/instructions/plan.md +17 -8
- package/builtins/en/facets/instructions/review-arch.md +5 -0
- package/builtins/en/facets/instructions/review-coding.md +15 -6
- package/builtins/en/facets/instructions/review-cqrs-es.md +9 -0
- package/builtins/en/facets/instructions/review-implementation-semantics.md +18 -0
- package/builtins/en/facets/instructions/review-merge-readiness.md +14 -0
- package/builtins/en/facets/instructions/supervise-maintenance.md +2 -2
- package/builtins/en/facets/instructions/supervise.md +8 -3
- package/builtins/en/facets/instructions/write-tests-first.md +23 -3
- package/builtins/en/facets/knowledge/architecture.md +36 -0
- package/builtins/en/facets/knowledge/backend.md +74 -0
- package/builtins/en/facets/knowledge/cqrs-es.md +767 -235
- package/builtins/en/facets/knowledge/frontend.md +5 -1
- package/builtins/en/facets/knowledge/implementation-semantics.md +104 -0
- package/builtins/en/facets/knowledge/security.md +3 -0
- package/builtins/en/facets/knowledge/takt.md +10 -2
- package/builtins/en/facets/knowledge/unit-testing.md +14 -0
- package/builtins/en/facets/output-contracts/ai-antipattern-review.md +11 -1
- package/builtins/en/facets/output-contracts/architecture-review.md +6 -1
- package/builtins/en/facets/output-contracts/coding-review.md +11 -1
- package/builtins/en/facets/output-contracts/findings-manager.md +14 -0
- package/builtins/en/facets/output-contracts/implementation-semantics-review-finding-contract.md +28 -0
- package/builtins/en/facets/output-contracts/implementation-semantics-review.md +50 -0
- package/builtins/en/facets/output-contracts/{pure-review-finding-contract.md → merge-readiness-review-finding-contract.md} +14 -8
- package/builtins/en/facets/output-contracts/{pure-review.md → merge-readiness-review.md} +17 -11
- package/builtins/en/facets/output-contracts/supervisor-validation.md +6 -2
- package/builtins/en/facets/output-contracts/test-report.md +25 -0
- package/builtins/en/facets/personas/coder.md +3 -3
- package/builtins/en/facets/personas/implementation-semantics-reviewer.md +27 -0
- package/builtins/en/facets/personas/merge-readiness-reviewer.md +28 -0
- package/builtins/en/facets/personas/supervisor.md +1 -1
- package/builtins/en/facets/policies/ai-antipattern.md +19 -0
- package/builtins/en/facets/policies/coding.md +31 -4
- package/builtins/en/facets/policies/review.md +17 -1
- package/builtins/en/facets/policies/testing.md +6 -2
- package/builtins/en/workflow-categories.yaml +13 -1
- package/builtins/en/workflows/audit-architecture-backend.yaml +1 -0
- package/builtins/en/workflows/audit-architecture-dual.yaml +1 -0
- package/builtins/en/workflows/audit-architecture-frontend.yaml +1 -0
- package/builtins/en/workflows/audit-architecture.yaml +1 -0
- package/builtins/en/workflows/audit-e2e.yaml +1 -0
- package/builtins/en/workflows/audit-security.yaml +1 -0
- package/builtins/en/workflows/audit-unit.yaml +1 -0
- package/builtins/en/workflows/backend-cqrs-for-local-llm.yaml +288 -0
- package/builtins/en/workflows/backend-cqrs-mini.yaml +1 -0
- package/builtins/en/workflows/backend-cqrs.yaml +28 -84
- package/builtins/en/workflows/backend-for-local-llm.yaml +284 -0
- package/builtins/en/workflows/backend-maintenance.yaml +32 -89
- package/builtins/en/workflows/backend-mini.yaml +1 -0
- package/builtins/en/workflows/backend.yaml +25 -83
- package/builtins/en/workflows/{takt-default-with-fc.yaml → cli.yaml} +16 -4
- package/builtins/en/workflows/deep-research.yaml +1 -0
- package/builtins/en/workflows/default-high.yaml +3 -32
- package/builtins/en/workflows/default-peer-review.yaml +16 -52
- package/builtins/en/workflows/default.yaml +2 -1
- package/builtins/en/workflows/draft.yaml +0 -1
- package/builtins/en/workflows/dual-cqrs-mini.yaml +1 -0
- package/builtins/en/workflows/dual-cqrs.yaml +28 -87
- package/builtins/en/workflows/dual-for-local-llm.yaml +312 -0
- package/builtins/en/workflows/dual-mini.yaml +1 -0
- package/builtins/en/workflows/dual.yaml +29 -58
- package/builtins/en/workflows/frontend-for-local-llm.yaml +289 -0
- package/builtins/en/workflows/frontend-maintenance.yaml +34 -91
- package/builtins/en/workflows/frontend-mini.yaml +1 -0
- package/builtins/en/workflows/frontend.yaml +27 -85
- package/builtins/en/workflows/merge-readiness-dual-final-gate.yaml +92 -0
- package/builtins/en/workflows/merge-readiness-final-gate.yaml +79 -0
- package/builtins/en/workflows/{peer-review-with-fc.yaml → peer-review-for-local-llm.yaml} +48 -37
- package/builtins/en/workflows/peer-review.yaml +25 -20
- package/builtins/en/workflows/research.yaml +1 -0
- package/builtins/en/workflows/review-backend-cqrs.yaml +63 -35
- package/builtins/en/workflows/review-backend.yaml +63 -35
- package/builtins/en/workflows/review-default.yaml +138 -107
- package/builtins/en/workflows/review-dual-cqrs.yaml +63 -35
- package/builtins/en/workflows/review-dual.yaml +63 -35
- package/builtins/en/workflows/review-fix-backend-cqrs.yaml +27 -87
- package/builtins/en/workflows/review-fix-backend.yaml +25 -86
- package/builtins/en/workflows/review-fix-default.yaml +17 -87
- package/builtins/en/workflows/review-fix-dual-cqrs.yaml +29 -90
- package/builtins/en/workflows/review-fix-dual.yaml +27 -89
- package/builtins/en/workflows/review-fix-frontend.yaml +25 -87
- package/builtins/en/workflows/review-fix-takt-default.yaml +67 -362
- package/builtins/en/workflows/review-frontend.yaml +63 -35
- package/builtins/en/workflows/review-takt-default.yaml +140 -109
- package/builtins/en/workflows/takt-default-for-local-llm.yaml +283 -0
- package/builtins/en/workflows/takt-default-refresh-all.yaml +20 -59
- package/builtins/en/workflows/takt-default-refresh-fast.yaml +19 -57
- package/builtins/en/workflows/takt-default.yaml +3 -25
- package/builtins/en/workflows/terraform.yaml +81 -44
- package/builtins/ja/config.yaml +6 -0
- package/builtins/ja/facets/instructions/ai-antipattern-review.md +5 -0
- package/builtins/ja/facets/instructions/findings-manager.md +6 -2
- package/builtins/ja/facets/instructions/fix.md +9 -3
- package/builtins/ja/facets/instructions/implement-maintenance.md +4 -15
- package/builtins/ja/facets/instructions/implement.md +2 -14
- package/builtins/ja/facets/instructions/loop-monitor-fix-replan.md +21 -0
- package/builtins/ja/facets/instructions/loop-monitor-replan.md +10 -0
- package/builtins/ja/facets/instructions/loop-monitor-reviewers-fix.md +7 -0
- package/builtins/ja/facets/instructions/plan-maintenance.md +10 -7
- package/builtins/ja/facets/instructions/plan.md +9 -6
- package/builtins/ja/facets/instructions/review-arch.md +5 -0
- package/builtins/ja/facets/instructions/review-coding.md +15 -6
- package/builtins/ja/facets/instructions/review-cqrs-es.md +9 -0
- package/builtins/ja/facets/instructions/review-implementation-semantics.md +17 -0
- package/builtins/ja/facets/instructions/review-merge-readiness.md +14 -0
- package/builtins/ja/facets/instructions/supervise-maintenance.md +2 -2
- package/builtins/ja/facets/instructions/supervise.md +8 -3
- package/builtins/ja/facets/instructions/write-tests-first.md +23 -3
- package/builtins/ja/facets/knowledge/architecture.md +36 -0
- package/builtins/ja/facets/knowledge/backend.md +74 -0
- package/builtins/ja/facets/knowledge/cqrs-es.md +260 -16
- package/builtins/ja/facets/knowledge/frontend.md +5 -1
- package/builtins/ja/facets/knowledge/implementation-semantics.md +104 -0
- package/builtins/ja/facets/knowledge/security.md +3 -0
- package/builtins/ja/facets/knowledge/takt.md +10 -2
- package/builtins/ja/facets/knowledge/unit-testing.md +14 -0
- package/builtins/ja/facets/output-contracts/ai-antipattern-review.md +11 -1
- package/builtins/ja/facets/output-contracts/architecture-review.md +6 -1
- package/builtins/ja/facets/output-contracts/coding-review.md +11 -1
- package/builtins/ja/facets/output-contracts/findings-manager.md +14 -0
- package/builtins/ja/facets/output-contracts/implementation-semantics-review-finding-contract.md +24 -0
- package/builtins/ja/facets/output-contracts/implementation-semantics-review.md +50 -0
- package/builtins/ja/facets/output-contracts/{pure-review-finding-contract.md → merge-readiness-review-finding-contract.md} +14 -8
- package/builtins/ja/facets/output-contracts/{pure-review.md → merge-readiness-review.md} +17 -11
- package/builtins/ja/facets/output-contracts/supervisor-validation.md +6 -2
- package/builtins/ja/facets/output-contracts/test-report.md +25 -0
- package/builtins/ja/facets/partials/instructions/implement-common.md +13 -0
- package/builtins/ja/facets/personas/coder.md +3 -3
- package/builtins/ja/facets/personas/implementation-semantics-reviewer.md +27 -0
- package/builtins/ja/facets/personas/merge-readiness-reviewer.md +28 -0
- package/builtins/ja/facets/personas/supervisor.md +1 -1
- package/builtins/ja/facets/policies/ai-antipattern.md +19 -0
- package/builtins/ja/facets/policies/coding.md +32 -4
- package/builtins/ja/facets/policies/review.md +17 -1
- package/builtins/ja/facets/policies/testing.md +11 -4
- package/builtins/ja/workflow-categories.yaml +13 -1
- package/builtins/ja/workflows/audit-architecture-backend.yaml +1 -0
- package/builtins/ja/workflows/audit-architecture-dual.yaml +1 -0
- package/builtins/ja/workflows/audit-architecture-frontend.yaml +1 -0
- package/builtins/ja/workflows/audit-architecture.yaml +1 -0
- package/builtins/ja/workflows/audit-e2e.yaml +1 -0
- package/builtins/ja/workflows/audit-security.yaml +1 -0
- package/builtins/ja/workflows/audit-unit.yaml +1 -0
- package/builtins/ja/workflows/backend-cqrs-for-local-llm.yaml +287 -0
- package/builtins/ja/workflows/backend-cqrs-mini.yaml +1 -0
- package/builtins/ja/workflows/backend-cqrs.yaml +28 -84
- package/builtins/ja/workflows/backend-for-local-llm.yaml +283 -0
- package/builtins/ja/workflows/backend-maintenance.yaml +32 -89
- package/builtins/ja/workflows/backend-mini.yaml +1 -0
- package/builtins/ja/workflows/backend.yaml +25 -83
- package/builtins/ja/workflows/{takt-default-with-fc.yaml → cli.yaml} +16 -4
- package/builtins/ja/workflows/deep-research.yaml +1 -0
- package/builtins/ja/workflows/default-high.yaml +3 -32
- package/builtins/ja/workflows/default-peer-review.yaml +16 -52
- package/builtins/ja/workflows/default.yaml +2 -1
- package/builtins/ja/workflows/draft.yaml +0 -1
- package/builtins/ja/workflows/dual-cqrs-mini.yaml +1 -0
- package/builtins/ja/workflows/dual-cqrs.yaml +28 -87
- package/builtins/ja/workflows/dual-for-local-llm.yaml +311 -0
- package/builtins/ja/workflows/dual-mini.yaml +1 -0
- package/builtins/ja/workflows/dual.yaml +29 -58
- package/builtins/ja/workflows/frontend-for-local-llm.yaml +288 -0
- package/builtins/ja/workflows/frontend-maintenance.yaml +34 -91
- package/builtins/ja/workflows/frontend-mini.yaml +1 -0
- package/builtins/ja/workflows/frontend.yaml +27 -85
- package/builtins/ja/workflows/merge-readiness-dual-final-gate.yaml +92 -0
- package/builtins/ja/workflows/merge-readiness-final-gate.yaml +79 -0
- package/builtins/ja/workflows/{peer-review-with-fc.yaml → peer-review-for-local-llm.yaml} +48 -37
- package/builtins/ja/workflows/peer-review.yaml +25 -20
- package/builtins/ja/workflows/research.yaml +1 -0
- package/builtins/ja/workflows/review-backend-cqrs.yaml +63 -35
- package/builtins/ja/workflows/review-backend.yaml +63 -35
- package/builtins/ja/workflows/review-default.yaml +111 -79
- package/builtins/ja/workflows/review-dual-cqrs.yaml +63 -35
- package/builtins/ja/workflows/review-dual.yaml +63 -35
- package/builtins/ja/workflows/review-fix-backend-cqrs.yaml +27 -87
- package/builtins/ja/workflows/review-fix-backend.yaml +25 -86
- package/builtins/ja/workflows/review-fix-default.yaml +17 -87
- package/builtins/ja/workflows/review-fix-dual-cqrs.yaml +29 -90
- package/builtins/ja/workflows/review-fix-dual.yaml +27 -89
- package/builtins/ja/workflows/review-fix-frontend.yaml +25 -87
- package/builtins/ja/workflows/review-fix-takt-default.yaml +67 -362
- package/builtins/ja/workflows/review-frontend.yaml +63 -35
- package/builtins/ja/workflows/review-takt-default.yaml +112 -80
- package/builtins/ja/workflows/takt-default-for-local-llm.yaml +282 -0
- package/builtins/ja/workflows/takt-default-refresh-all.yaml +20 -59
- package/builtins/ja/workflows/takt-default-refresh-fast.yaml +19 -57
- package/builtins/ja/workflows/takt-default.yaml +3 -25
- package/builtins/ja/workflows/terraform.yaml +81 -44
- package/builtins/skill/references/yaml-schema.md +21 -1
- package/builtins/skill-codex/references/yaml-schema.md +21 -1
- package/dist/agents/agent-usecases.d.ts +1 -0
- package/dist/agents/agent-usecases.d.ts.map +1 -1
- package/dist/agents/agent-usecases.js +1 -0
- package/dist/agents/agent-usecases.js.map +1 -1
- package/dist/agents/auto-routing-usecase.d.ts +23 -0
- package/dist/agents/auto-routing-usecase.d.ts.map +1 -0
- package/dist/agents/auto-routing-usecase.js +216 -0
- package/dist/agents/auto-routing-usecase.js.map +1 -0
- package/dist/agents/decompose-task-usecase.d.ts +1 -0
- package/dist/agents/decompose-task-usecase.d.ts.map +1 -1
- package/dist/agents/decompose-task-usecase.js +2 -0
- package/dist/agents/decompose-task-usecase.js.map +1 -1
- package/dist/agents/judge-utils.d.ts.map +1 -1
- package/dist/agents/judge-utils.js +4 -3
- package/dist/agents/judge-utils.js.map +1 -1
- package/dist/agents/runner.d.ts.map +1 -1
- package/dist/agents/runner.js +2 -3
- package/dist/agents/runner.js.map +1 -1
- package/dist/agents/structured-caller/prompt-based-structured-caller.d.ts.map +1 -1
- package/dist/agents/structured-caller/prompt-based-structured-caller.js +2 -0
- package/dist/agents/structured-caller/prompt-based-structured-caller.js.map +1 -1
- package/dist/agents/structured-caller/shared.js +1 -1
- package/dist/agents/structured-caller/shared.js.map +1 -1
- package/dist/app/acp/agent.d.ts +28 -0
- package/dist/app/acp/agent.d.ts.map +1 -0
- package/dist/app/acp/agent.js +279 -0
- package/dist/app/acp/agent.js.map +1 -0
- package/dist/app/acp/confirmationBridge.d.ts +5 -0
- package/dist/app/acp/confirmationBridge.d.ts.map +1 -0
- package/dist/app/acp/confirmationBridge.js +194 -0
- package/dist/app/acp/confirmationBridge.js.map +1 -0
- package/dist/app/acp/conversationFactory.d.ts +4 -0
- package/dist/app/acp/conversationFactory.d.ts.map +1 -0
- package/dist/app/acp/conversationFactory.js +33 -0
- package/dist/app/acp/conversationFactory.js.map +1 -0
- package/dist/app/acp/enqueue.d.ts +33 -0
- package/dist/app/acp/enqueue.d.ts.map +1 -0
- package/dist/app/acp/enqueue.js +45 -0
- package/dist/app/acp/enqueue.js.map +1 -0
- package/dist/app/acp/index.d.ts +7 -0
- package/dist/app/acp/index.d.ts.map +1 -0
- package/dist/app/acp/index.js +125 -0
- package/dist/app/acp/index.js.map +1 -0
- package/dist/app/acp/intent.d.ts +10 -0
- package/dist/app/acp/intent.d.ts.map +1 -0
- package/dist/app/acp/intent.js +210 -0
- package/dist/app/acp/intent.js.map +1 -0
- package/dist/app/acp/mcpServers.d.ts +4 -0
- package/dist/app/acp/mcpServers.d.ts.map +1 -0
- package/dist/app/acp/mcpServers.js +51 -0
- package/dist/app/acp/mcpServers.js.map +1 -0
- package/dist/app/acp/promptContent.d.ts +3 -0
- package/dist/app/acp/promptContent.d.ts.map +1 -0
- package/dist/app/acp/promptContent.js +23 -0
- package/dist/app/acp/promptContent.js.map +1 -0
- package/dist/app/acp/sessionStore.d.ts +19 -0
- package/dist/app/acp/sessionStore.d.ts.map +1 -0
- package/dist/app/acp/sessionStore.js +50 -0
- package/dist/app/acp/sessionStore.js.map +1 -0
- package/dist/app/acp/sessionUpdates.d.ts +8 -0
- package/dist/app/acp/sessionUpdates.d.ts.map +1 -0
- package/dist/app/acp/sessionUpdates.js +108 -0
- package/dist/app/acp/sessionUpdates.js.map +1 -0
- package/dist/app/acp/taskContext.d.ts +17 -0
- package/dist/app/acp/taskContext.d.ts.map +1 -0
- package/dist/app/acp/taskContext.js +81 -0
- package/dist/app/acp/taskContext.js.map +1 -0
- package/dist/app/acp/types.d.ts +33 -0
- package/dist/app/acp/types.d.ts.map +1 -0
- package/dist/app/acp/types.js +2 -0
- package/dist/app/acp/types.js.map +1 -0
- package/dist/app/cli/commands.js +28 -1
- package/dist/app/cli/commands.js.map +1 -1
- package/dist/app/cli/helpers.d.ts.map +1 -1
- package/dist/app/cli/helpers.js +13 -1
- package/dist/app/cli/helpers.js.map +1 -1
- package/dist/app/cli/program.d.ts.map +1 -1
- package/dist/app/cli/program.js +6 -5
- package/dist/app/cli/program.js.map +1 -1
- package/dist/app/cli/routing.d.ts.map +1 -1
- package/dist/app/cli/routing.js +56 -47
- package/dist/app/cli/routing.js.map +1 -1
- package/dist/app/mcp/index.d.ts +3 -0
- package/dist/app/mcp/index.d.ts.map +1 -0
- package/dist/app/mcp/index.js +16 -0
- package/dist/app/mcp/index.js.map +1 -0
- package/dist/app/mcp/server.d.ts +2 -0
- package/dist/app/mcp/server.d.ts.map +1 -0
- package/dist/app/mcp/server.js +2 -0
- package/dist/app/mcp/server.js.map +1 -0
- package/dist/core/config/provider-resolution.d.ts +1 -0
- package/dist/core/config/provider-resolution.d.ts.map +1 -1
- package/dist/core/config/provider-resolution.js +19 -0
- package/dist/core/config/provider-resolution.js.map +1 -1
- package/dist/core/logging/providerEvent.d.ts +3 -2
- package/dist/core/logging/providerEvent.d.ts.map +1 -1
- package/dist/core/logging/providerEvent.js.map +1 -1
- package/dist/core/logging/providerEventLogger.d.ts +2 -1
- package/dist/core/logging/providerEventLogger.d.ts.map +1 -1
- package/dist/core/logging/providerEventLogger.js.map +1 -1
- package/dist/core/logging/usageEventLogger.d.ts +2 -1
- package/dist/core/logging/usageEventLogger.d.ts.map +1 -1
- package/dist/core/logging/usageEventLogger.js +3 -0
- package/dist/core/logging/usageEventLogger.js.map +1 -1
- package/dist/core/models/config-schemas.d.ts +301 -97
- package/dist/core/models/config-schemas.d.ts.map +1 -1
- package/dist/core/models/config-schemas.js +8 -5
- package/dist/core/models/config-schemas.js.map +1 -1
- package/dist/core/models/config-types.d.ts +34 -2
- package/dist/core/models/config-types.d.ts.map +1 -1
- package/dist/core/models/finding-schemas.d.ts +167 -24
- package/dist/core/models/finding-schemas.d.ts.map +1 -1
- package/dist/core/models/finding-schemas.js +82 -9
- package/dist/core/models/finding-schemas.js.map +1 -1
- package/dist/core/models/finding-types.d.ts +43 -2
- package/dist/core/models/finding-types.d.ts.map +1 -1
- package/dist/core/models/finding-types.js +3 -2
- package/dist/core/models/finding-types.js.map +1 -1
- package/dist/core/models/index.d.ts +2 -1
- package/dist/core/models/index.d.ts.map +1 -1
- package/dist/core/models/index.js +1 -0
- package/dist/core/models/index.js.map +1 -1
- package/dist/core/models/part.d.ts +1 -0
- package/dist/core/models/part.d.ts.map +1 -1
- package/dist/core/models/schema-base.d.ts +189 -36
- package/dist/core/models/schema-base.d.ts.map +1 -1
- package/dist/core/models/schema-base.js +68 -0
- package/dist/core/models/schema-base.js.map +1 -1
- package/dist/core/models/types.d.ts +3 -2
- package/dist/core/models/types.d.ts.map +1 -1
- package/dist/core/models/types.js +1 -1
- package/dist/core/models/types.js.map +1 -1
- package/dist/core/models/workflow-condition-expression.d.ts +17 -0
- package/dist/core/models/workflow-condition-expression.d.ts.map +1 -1
- package/dist/core/models/workflow-condition-expression.js +85 -2
- package/dist/core/models/workflow-condition-expression.js.map +1 -1
- package/dist/core/models/workflow-provider-options.d.ts +1 -1
- package/dist/core/models/workflow-provider-options.d.ts.map +1 -1
- package/dist/core/models/workflow-schemas.d.ts +1134 -250
- package/dist/core/models/workflow-schemas.d.ts.map +1 -1
- package/dist/core/models/workflow-schemas.js +124 -60
- package/dist/core/models/workflow-schemas.js.map +1 -1
- package/dist/core/models/workflow-session-constraints.d.ts +3 -0
- package/dist/core/models/workflow-session-constraints.d.ts.map +1 -0
- package/dist/core/models/workflow-session-constraints.js +3 -0
- package/dist/core/models/workflow-session-constraints.js.map +1 -0
- package/dist/core/models/workflow-types.d.ts +44 -6
- package/dist/core/models/workflow-types.d.ts.map +1 -1
- package/dist/core/models/workflow-types.js +1 -0
- package/dist/core/models/workflow-types.js.map +1 -1
- package/dist/core/runtime/runtime-environment.d.ts.map +1 -1
- package/dist/core/runtime/runtime-environment.js +19 -0
- package/dist/core/runtime/runtime-environment.js.map +1 -1
- package/dist/core/workflow/auto-routing/resolver.d.ts +43 -0
- package/dist/core/workflow/auto-routing/resolver.d.ts.map +1 -0
- package/dist/core/workflow/auto-routing/resolver.js +218 -0
- package/dist/core/workflow/auto-routing/resolver.js.map +1 -0
- package/dist/core/workflow/auto-routing/workflow-auto-provider.d.ts +16 -0
- package/dist/core/workflow/auto-routing/workflow-auto-provider.d.ts.map +1 -0
- package/dist/core/workflow/auto-routing/workflow-auto-provider.js +64 -0
- package/dist/core/workflow/auto-routing/workflow-auto-provider.js.map +1 -0
- package/dist/core/workflow/engine/LoopMonitorJudgeRunner.d.ts +1 -1
- package/dist/core/workflow/engine/LoopMonitorJudgeRunner.d.ts.map +1 -1
- package/dist/core/workflow/engine/LoopMonitorJudgeRunner.js +15 -1
- package/dist/core/workflow/engine/LoopMonitorJudgeRunner.js.map +1 -1
- package/dist/core/workflow/engine/OptionsBuilder.d.ts +15 -5
- package/dist/core/workflow/engine/OptionsBuilder.d.ts.map +1 -1
- package/dist/core/workflow/engine/OptionsBuilder.js +122 -13
- package/dist/core/workflow/engine/OptionsBuilder.js.map +1 -1
- package/dist/core/workflow/engine/ParallelRunner.d.ts +12 -1
- package/dist/core/workflow/engine/ParallelRunner.d.ts.map +1 -1
- package/dist/core/workflow/engine/ParallelRunner.js +356 -33
- package/dist/core/workflow/engine/ParallelRunner.js.map +1 -1
- package/dist/core/workflow/engine/StepExecutor.d.ts +10 -0
- package/dist/core/workflow/engine/StepExecutor.d.ts.map +1 -1
- package/dist/core/workflow/engine/StepExecutor.js +89 -31
- package/dist/core/workflow/engine/StepExecutor.js.map +1 -1
- package/dist/core/workflow/engine/TeamLeaderRunner.d.ts +10 -2
- package/dist/core/workflow/engine/TeamLeaderRunner.d.ts.map +1 -1
- package/dist/core/workflow/engine/TeamLeaderRunner.js +111 -9
- package/dist/core/workflow/engine/TeamLeaderRunner.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowCallExecutor.d.ts +11 -1
- package/dist/core/workflow/engine/WorkflowCallExecutor.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowCallExecutor.js +43 -7
- package/dist/core/workflow/engine/WorkflowCallExecutor.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowCallRunner.d.ts +9 -0
- package/dist/core/workflow/engine/WorkflowCallRunner.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowCallRunner.js +65 -14
- package/dist/core/workflow/engine/WorkflowCallRunner.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowEngine.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowEngine.js +27 -9
- package/dist/core/workflow/engine/WorkflowEngine.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowEngineSetup.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowEngineSetup.js +29 -20
- package/dist/core/workflow/engine/WorkflowEngineSetup.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowEngineStepCoordinator.d.ts +2 -2
- package/dist/core/workflow/engine/WorkflowEngineStepCoordinator.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowEngineStepCoordinator.js +2 -2
- package/dist/core/workflow/engine/WorkflowEngineStepCoordinator.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowRunLoop.d.ts +1 -1
- package/dist/core/workflow/engine/WorkflowRunLoop.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowRunLoop.js +51 -7
- package/dist/core/workflow/engine/WorkflowRunLoop.js.map +1 -1
- package/dist/core/workflow/engine/WorkflowValidator.d.ts.map +1 -1
- package/dist/core/workflow/engine/WorkflowValidator.js +79 -7
- package/dist/core/workflow/engine/WorkflowValidator.js.map +1 -1
- package/dist/core/workflow/engine/engine-provider-options.d.ts +1 -0
- package/dist/core/workflow/engine/engine-provider-options.d.ts.map +1 -1
- package/dist/core/workflow/engine/engine-provider-options.js +12 -0
- package/dist/core/workflow/engine/engine-provider-options.js.map +1 -1
- package/dist/core/workflow/engine/session-compaction.d.ts +10 -0
- package/dist/core/workflow/engine/session-compaction.d.ts.map +1 -0
- package/dist/core/workflow/engine/session-compaction.js +42 -0
- package/dist/core/workflow/engine/session-compaction.js.map +1 -0
- package/dist/core/workflow/engine/team-leader-part-runner.d.ts.map +1 -1
- package/dist/core/workflow/engine/team-leader-part-runner.js +3 -1
- package/dist/core/workflow/engine/team-leader-part-runner.js.map +1 -1
- package/dist/core/workflow/evaluation/RuleEvaluator.d.ts +0 -1
- package/dist/core/workflow/evaluation/RuleEvaluator.d.ts.map +1 -1
- package/dist/core/workflow/evaluation/RuleEvaluator.js +22 -34
- package/dist/core/workflow/evaluation/RuleEvaluator.js.map +1 -1
- package/dist/core/workflow/evaluation/rule-utils.d.ts +33 -1
- package/dist/core/workflow/evaluation/rule-utils.d.ts.map +1 -1
- package/dist/core/workflow/evaluation/rule-utils.js +77 -5
- package/dist/core/workflow/evaluation/rule-utils.js.map +1 -1
- package/dist/core/workflow/evaluation/when-evaluator.d.ts +1 -0
- package/dist/core/workflow/evaluation/when-evaluator.d.ts.map +1 -1
- package/dist/core/workflow/evaluation/when-evaluator.js +18 -40
- package/dist/core/workflow/evaluation/when-evaluator.js.map +1 -1
- package/dist/core/workflow/findings/context.d.ts +4 -0
- package/dist/core/workflow/findings/context.d.ts.map +1 -1
- package/dist/core/workflow/findings/context.js +27 -0
- package/dist/core/workflow/findings/context.js.map +1 -1
- package/dist/core/workflow/findings/manager-output-validation.d.ts +14 -0
- package/dist/core/workflow/findings/manager-output-validation.d.ts.map +1 -1
- package/dist/core/workflow/findings/manager-output-validation.js +158 -8
- package/dist/core/workflow/findings/manager-output-validation.js.map +1 -1
- package/dist/core/workflow/findings/manager-runner.d.ts +17 -6
- package/dist/core/workflow/findings/manager-runner.d.ts.map +1 -1
- package/dist/core/workflow/findings/manager-runner.js +157 -59
- package/dist/core/workflow/findings/manager-runner.js.map +1 -1
- package/dist/core/workflow/findings/manager-step.d.ts +8 -0
- package/dist/core/workflow/findings/manager-step.d.ts.map +1 -0
- package/dist/core/workflow/findings/manager-step.js +27 -0
- package/dist/core/workflow/findings/manager-step.js.map +1 -0
- package/dist/core/workflow/findings/mechanical-classification.d.ts +24 -0
- package/dist/core/workflow/findings/mechanical-classification.d.ts.map +1 -0
- package/dist/core/workflow/findings/mechanical-classification.js +115 -0
- package/dist/core/workflow/findings/mechanical-classification.js.map +1 -0
- package/dist/core/workflow/findings/reconciler.d.ts +1 -0
- package/dist/core/workflow/findings/reconciler.d.ts.map +1 -1
- package/dist/core/workflow/findings/reconciler.js +90 -4
- package/dist/core/workflow/findings/reconciler.js.map +1 -1
- package/dist/core/workflow/instruction/InstructionBuilder.d.ts.map +1 -1
- package/dist/core/workflow/instruction/InstructionBuilder.js +7 -15
- package/dist/core/workflow/instruction/InstructionBuilder.js.map +1 -1
- package/dist/core/workflow/instruction/ReportInstructionBuilder.d.ts.map +1 -1
- package/dist/core/workflow/instruction/ReportInstructionBuilder.js +9 -11
- package/dist/core/workflow/instruction/ReportInstructionBuilder.js.map +1 -1
- package/dist/core/workflow/instruction/finding-contract-instruction.d.ts +36 -0
- package/dist/core/workflow/instruction/finding-contract-instruction.d.ts.map +1 -0
- package/dist/core/workflow/instruction/finding-contract-instruction.js +39 -0
- package/dist/core/workflow/instruction/finding-contract-instruction.js.map +1 -0
- package/dist/core/workflow/instruction/instruction-context.d.ts +16 -0
- package/dist/core/workflow/instruction/instruction-context.d.ts.map +1 -1
- package/dist/core/workflow/instruction/instruction-context.js +14 -18
- package/dist/core/workflow/instruction/instruction-context.js.map +1 -1
- package/dist/core/workflow/instruction/status-rules.d.ts.map +1 -1
- package/dist/core/workflow/instruction/status-rules.js +7 -2
- package/dist/core/workflow/instruction/status-rules.js.map +1 -1
- package/dist/core/workflow/permission-profile-resolution.d.ts +6 -0
- package/dist/core/workflow/permission-profile-resolution.d.ts.map +1 -1
- package/dist/core/workflow/permission-profile-resolution.js +11 -0
- package/dist/core/workflow/permission-profile-resolution.js.map +1 -1
- package/dist/core/workflow/phase-runner.d.ts +17 -10
- package/dist/core/workflow/phase-runner.d.ts.map +1 -1
- package/dist/core/workflow/phase-runner.js +1 -1
- package/dist/core/workflow/phase-runner.js.map +1 -1
- package/dist/core/workflow/provider-model-requirements.d.ts +6 -0
- package/dist/core/workflow/provider-model-requirements.d.ts.map +1 -0
- package/dist/core/workflow/provider-model-requirements.js +15 -0
- package/dist/core/workflow/provider-model-requirements.js.map +1 -0
- package/dist/core/workflow/provider-options-trace.d.ts +1 -1
- package/dist/core/workflow/provider-options-trace.d.ts.map +1 -1
- package/dist/core/workflow/provider-resolution.d.ts +14 -11
- package/dist/core/workflow/provider-resolution.d.ts.map +1 -1
- package/dist/core/workflow/provider-resolution.js +38 -26
- package/dist/core/workflow/provider-resolution.js.map +1 -1
- package/dist/core/workflow/report-phase-runner.d.ts +5 -2
- package/dist/core/workflow/report-phase-runner.d.ts.map +1 -1
- package/dist/core/workflow/report-phase-runner.js +135 -43
- package/dist/core/workflow/report-phase-runner.js.map +1 -1
- package/dist/core/workflow/status-judgment-phase.d.ts +2 -2
- package/dist/core/workflow/status-judgment-phase.d.ts.map +1 -1
- package/dist/core/workflow/status-judgment-phase.js +1 -7
- package/dist/core/workflow/status-judgment-phase.js.map +1 -1
- package/dist/core/workflow/system/system-step-effect-runner.js +2 -2
- package/dist/core/workflow/system/system-step-effect-runner.js.map +1 -1
- package/dist/core/workflow/system/system-step-services.d.ts +101 -0
- package/dist/core/workflow/system/system-step-services.d.ts.map +1 -1
- package/dist/core/workflow/types.d.ts +25 -8
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/features/analytics/events.d.ts +30 -3
- package/dist/features/analytics/events.d.ts.map +1 -1
- package/dist/features/analytics/events.js +1 -1
- package/dist/features/analytics/index.d.ts +2 -2
- package/dist/features/analytics/index.d.ts.map +1 -1
- package/dist/features/analytics/index.js.map +1 -1
- package/dist/features/analytics/writer.d.ts +8 -14
- package/dist/features/analytics/writer.d.ts.map +1 -1
- package/dist/features/analytics/writer.js +27 -17
- package/dist/features/analytics/writer.js.map +1 -1
- package/dist/features/exec/assistantSession.d.ts +2 -0
- package/dist/features/exec/assistantSession.d.ts.map +1 -1
- package/dist/features/exec/assistantSession.js +1 -1
- package/dist/features/exec/assistantSession.js.map +1 -1
- package/dist/features/exec/command.d.ts.map +1 -1
- package/dist/features/exec/command.js +98 -54
- package/dist/features/exec/command.js.map +1 -1
- package/dist/features/exec/commandAvailability.js +1 -1
- package/dist/features/exec/commandAvailability.js.map +1 -1
- package/dist/features/exec/configOps.d.ts.map +1 -1
- package/dist/features/exec/configOps.js +6 -5
- package/dist/features/exec/configOps.js.map +1 -1
- package/dist/features/exec/configValidation.d.ts +1 -1
- package/dist/features/exec/configValidation.d.ts.map +1 -1
- package/dist/features/exec/configValidation.js +9 -13
- package/dist/features/exec/configValidation.js.map +1 -1
- package/dist/features/exec/runtimeConfig.d.ts.map +1 -1
- package/dist/features/exec/runtimeConfig.js +4 -2
- package/dist/features/exec/runtimeConfig.js.map +1 -1
- package/dist/features/exec/setupMenu.js +2 -2
- package/dist/features/exec/setupMenu.js.map +1 -1
- package/dist/features/exec/workflowRunner.d.ts +2 -1
- package/dist/features/exec/workflowRunner.d.ts.map +1 -1
- package/dist/features/exec/workflowRunner.js +2 -1
- package/dist/features/exec/workflowRunner.js.map +1 -1
- package/dist/features/exec/workflowTemplate.d.ts.map +1 -1
- package/dist/features/exec/workflowTemplate.js +4 -4
- package/dist/features/exec/workflowTemplate.js.map +1 -1
- package/dist/features/interactive/aiCaller.d.ts +4 -2
- package/dist/features/interactive/aiCaller.d.ts.map +1 -1
- package/dist/features/interactive/aiCaller.js +38 -11
- package/dist/features/interactive/aiCaller.js.map +1 -1
- package/dist/features/interactive/assistantConfig.d.ts.map +1 -1
- package/dist/features/interactive/assistantConfig.js +3 -2
- package/dist/features/interactive/assistantConfig.js.map +1 -1
- package/dist/features/interactive/conversationLoop.d.ts.map +1 -1
- package/dist/features/interactive/conversationLoop.js +164 -141
- package/dist/features/interactive/conversationLoop.js.map +1 -1
- package/dist/features/interactive/conversationSession.d.ts +44 -0
- package/dist/features/interactive/conversationSession.d.ts.map +1 -0
- package/dist/features/interactive/conversationSession.js +133 -0
- package/dist/features/interactive/conversationSession.js.map +1 -0
- package/dist/features/interactive/imageAttachments.d.ts +12 -8
- package/dist/features/interactive/imageAttachments.d.ts.map +1 -1
- package/dist/features/interactive/imageAttachments.js +67 -14
- package/dist/features/interactive/imageAttachments.js.map +1 -1
- package/dist/features/interactive/index.d.ts +1 -0
- package/dist/features/interactive/index.d.ts.map +1 -1
- package/dist/features/interactive/index.js +1 -0
- package/dist/features/interactive/index.js.map +1 -1
- package/dist/features/interactive/instructModeTypes.d.ts +2 -2
- package/dist/features/interactive/instructModeTypes.d.ts.map +1 -1
- package/dist/features/interactive/interactive-summary.d.ts.map +1 -1
- package/dist/features/interactive/interactive-summary.js +8 -0
- package/dist/features/interactive/interactive-summary.js.map +1 -1
- package/dist/features/interactive/interactive.d.ts +9 -6
- package/dist/features/interactive/interactive.d.ts.map +1 -1
- package/dist/features/interactive/interactive.js +10 -4
- package/dist/features/interactive/interactive.js.map +1 -1
- package/dist/features/interactive/interactiveApplication.d.ts +5 -0
- package/dist/features/interactive/interactiveApplication.d.ts.map +1 -0
- package/dist/features/interactive/interactiveApplication.js +10 -0
- package/dist/features/interactive/interactiveApplication.js.map +1 -0
- package/dist/features/interactive/passthroughMode.d.ts.map +1 -1
- package/dist/features/interactive/passthroughMode.js +22 -16
- package/dist/features/interactive/passthroughMode.js.map +1 -1
- package/dist/features/interactive/promptSections.js +2 -2
- package/dist/features/interactive/promptSections.js.map +1 -1
- package/dist/features/interactive/quietMode.d.ts.map +1 -1
- package/dist/features/interactive/quietMode.js +55 -40
- package/dist/features/interactive/quietMode.js.map +1 -1
- package/dist/features/interactive/retryMode.d.ts.map +1 -1
- package/dist/features/interactive/retryMode.js +5 -4
- package/dist/features/interactive/retryMode.js.map +1 -1
- package/dist/features/mcp/operations.d.ts +25 -0
- package/dist/features/mcp/operations.d.ts.map +1 -0
- package/dist/features/mcp/operations.js +150 -0
- package/dist/features/mcp/operations.js.map +1 -0
- package/dist/features/mcp/schemas.d.ts +50 -0
- package/dist/features/mcp/schemas.d.ts.map +1 -0
- package/dist/features/mcp/schemas.js +59 -0
- package/dist/features/mcp/schemas.js.map +1 -0
- package/dist/features/mcp/server.d.ts +7 -0
- package/dist/features/mcp/server.d.ts.map +1 -0
- package/dist/features/mcp/server.js +36 -0
- package/dist/features/mcp/server.js.map +1 -0
- package/dist/features/pipeline/steps.d.ts +1 -1
- package/dist/features/pipeline/steps.d.ts.map +1 -1
- package/dist/features/pipeline/steps.js +6 -2
- package/dist/features/pipeline/steps.js.map +1 -1
- package/dist/features/prompt/preview.d.ts.map +1 -1
- package/dist/features/prompt/preview.js +37 -1
- package/dist/features/prompt/preview.js.map +1 -1
- package/dist/features/tasks/add/index.d.ts +8 -20
- package/dist/features/tasks/add/index.d.ts.map +1 -1
- package/dist/features/tasks/add/index.js +42 -61
- package/dist/features/tasks/add/index.js.map +1 -1
- package/dist/features/tasks/attachments.d.ts +4 -10
- package/dist/features/tasks/attachments.d.ts.map +1 -1
- package/dist/features/tasks/attachments.js +14 -42
- package/dist/features/tasks/attachments.js.map +1 -1
- package/dist/features/tasks/execute/analyticsEmitter.d.ts +7 -2
- package/dist/features/tasks/execute/analyticsEmitter.d.ts.map +1 -1
- package/dist/features/tasks/execute/analyticsEmitter.js +76 -2
- package/dist/features/tasks/execute/analyticsEmitter.js.map +1 -1
- package/dist/features/tasks/execute/outputFns.d.ts +1 -1
- package/dist/features/tasks/execute/outputFns.d.ts.map +1 -1
- package/dist/features/tasks/execute/outputFns.js +17 -1
- package/dist/features/tasks/execute/outputFns.js.map +1 -1
- package/dist/features/tasks/execute/postExecution.d.ts +4 -1
- package/dist/features/tasks/execute/postExecution.d.ts.map +1 -1
- package/dist/features/tasks/execute/postExecution.js +33 -14
- package/dist/features/tasks/execute/postExecution.js.map +1 -1
- package/dist/features/tasks/execute/resolveTask.d.ts +9 -3
- package/dist/features/tasks/execute/resolveTask.d.ts.map +1 -1
- package/dist/features/tasks/execute/resolveTask.js +46 -14
- package/dist/features/tasks/execute/resolveTask.js.map +1 -1
- package/dist/features/tasks/execute/runAllTasks.d.ts.map +1 -1
- package/dist/features/tasks/execute/runAllTasks.js +3 -0
- package/dist/features/tasks/execute/runAllTasks.js.map +1 -1
- package/dist/features/tasks/execute/runTaskExecution.d.ts +7 -1
- package/dist/features/tasks/execute/runTaskExecution.d.ts.map +1 -1
- package/dist/features/tasks/execute/runTaskExecution.js +11 -8
- package/dist/features/tasks/execute/runTaskExecution.js.map +1 -1
- package/dist/features/tasks/execute/taskExecution.d.ts +12 -3
- package/dist/features/tasks/execute/taskExecution.d.ts.map +1 -1
- package/dist/features/tasks/execute/taskExecution.js +63 -16
- package/dist/features/tasks/execute/taskExecution.js.map +1 -1
- package/dist/features/tasks/execute/taskResultHandler.d.ts +2 -2
- package/dist/features/tasks/execute/taskResultHandler.d.ts.map +1 -1
- package/dist/features/tasks/execute/taskResultHandler.js +8 -4
- package/dist/features/tasks/execute/taskResultHandler.js.map +1 -1
- package/dist/features/tasks/execute/taskWorkflowExecution.d.ts.map +1 -1
- package/dist/features/tasks/execute/taskWorkflowExecution.js +62 -9
- package/dist/features/tasks/execute/taskWorkflowExecution.js.map +1 -1
- package/dist/features/tasks/execute/traceTaskMetadata.js +3 -6
- package/dist/features/tasks/execute/traceTaskMetadata.js.map +1 -1
- package/dist/features/tasks/execute/types.d.ts +109 -7
- package/dist/features/tasks/execute/types.d.ts.map +1 -1
- package/dist/features/tasks/execute/workflowExecution.d.ts +3 -1
- package/dist/features/tasks/execute/workflowExecution.d.ts.map +1 -1
- package/dist/features/tasks/execute/workflowExecution.js +49 -5
- package/dist/features/tasks/execute/workflowExecution.js.map +1 -1
- package/dist/features/tasks/execute/workflowExecutionApi.d.ts +6 -0
- package/dist/features/tasks/execute/workflowExecutionApi.d.ts.map +1 -0
- package/dist/features/tasks/execute/workflowExecutionApi.js +25 -0
- package/dist/features/tasks/execute/workflowExecutionApi.js.map +1 -0
- package/dist/features/tasks/execute/workflowExecutionBootstrap.d.ts +3 -1
- package/dist/features/tasks/execute/workflowExecutionBootstrap.d.ts.map +1 -1
- package/dist/features/tasks/execute/workflowExecutionBootstrap.js +51 -20
- package/dist/features/tasks/execute/workflowExecutionBootstrap.js.map +1 -1
- package/dist/features/tasks/execute/workflowExecutionContext.d.ts.map +1 -1
- package/dist/features/tasks/execute/workflowExecutionContext.js +1 -1
- package/dist/features/tasks/execute/workflowExecutionContext.js.map +1 -1
- package/dist/features/tasks/execute/workflowExecutionEvents.d.ts +14 -1
- package/dist/features/tasks/execute/workflowExecutionEvents.d.ts.map +1 -1
- package/dist/features/tasks/execute/workflowExecutionEvents.js +246 -4
- package/dist/features/tasks/execute/workflowExecutionEvents.js.map +1 -1
- package/dist/features/tasks/list/instructMode.d.ts.map +1 -1
- package/dist/features/tasks/list/instructMode.js +5 -4
- package/dist/features/tasks/list/instructMode.js.map +1 -1
- package/dist/features/tasks/list/requeueHelpers.d.ts +1 -0
- package/dist/features/tasks/list/requeueHelpers.d.ts.map +1 -1
- package/dist/features/tasks/list/requeueHelpers.js +3 -0
- package/dist/features/tasks/list/requeueHelpers.js.map +1 -1
- package/dist/features/tasks/list/taskInstructionActions.d.ts.map +1 -1
- package/dist/features/tasks/list/taskInstructionActions.js +33 -25
- package/dist/features/tasks/list/taskInstructionActions.js.map +1 -1
- package/dist/features/tasks/list/taskRetryActions.d.ts.map +1 -1
- package/dist/features/tasks/list/taskRetryActions.js +34 -30
- package/dist/features/tasks/list/taskRetryActions.js.map +1 -1
- package/dist/features/tasks/resume/directInstructMode.d.ts.map +1 -1
- package/dist/features/tasks/resume/directInstructMode.js +11 -2
- package/dist/features/tasks/resume/directInstructMode.js.map +1 -1
- package/dist/features/tasks/resume/index.d.ts.map +1 -1
- package/dist/features/tasks/resume/index.js +73 -24
- package/dist/features/tasks/resume/index.js.map +1 -1
- package/dist/features/tasks/{list/retryTaskSpecAttachments.d.ts → retryTaskSpecAttachments.d.ts} +3 -1
- package/dist/features/tasks/retryTaskSpecAttachments.d.ts.map +1 -0
- package/dist/features/tasks/{list/retryTaskSpecAttachments.js → retryTaskSpecAttachments.js} +4 -3
- package/dist/features/tasks/retryTaskSpecAttachments.js.map +1 -0
- package/dist/features/tasks/taskContextValidation.d.ts +10 -0
- package/dist/features/tasks/taskContextValidation.d.ts.map +1 -0
- package/dist/features/tasks/taskContextValidation.js +73 -0
- package/dist/features/tasks/taskContextValidation.js.map +1 -0
- package/dist/features/tasks/watch/index.d.ts.map +1 -1
- package/dist/features/tasks/watch/index.js +1 -0
- package/dist/features/tasks/watch/index.js.map +1 -1
- package/dist/features/workflowAuthoring/doctor.d.ts.map +1 -1
- package/dist/features/workflowAuthoring/doctor.js +56 -1
- package/dist/features/workflowAuthoring/doctor.js.map +1 -1
- package/dist/infra/claude/image-input.d.ts.map +1 -1
- package/dist/infra/claude/image-input.js +3 -2
- package/dist/infra/claude/image-input.js.map +1 -1
- package/dist/infra/codex/client.d.ts.map +1 -1
- package/dist/infra/codex/client.js +29 -10
- package/dist/infra/codex/client.js.map +1 -1
- package/dist/infra/config/configNormalizers.d.ts +24 -2
- package/dist/infra/config/configNormalizers.d.ts.map +1 -1
- package/dist/infra/config/configNormalizers.js +77 -4
- package/dist/infra/config/configNormalizers.js.map +1 -1
- package/dist/infra/config/env/global-current-env-specs.d.ts.map +1 -1
- package/dist/infra/config/env/global-current-env-specs.js +2 -0
- package/dist/infra/config/env/global-current-env-specs.js.map +1 -1
- package/dist/infra/config/env/project-current-env-specs.d.ts.map +1 -1
- package/dist/infra/config/env/project-current-env-specs.js +2 -0
- package/dist/infra/config/env/project-current-env-specs.js.map +1 -1
- package/dist/infra/config/global/globalConfig.d.ts +1 -1
- package/dist/infra/config/global/globalConfig.d.ts.map +1 -1
- package/dist/infra/config/global/globalConfig.js +1 -1
- package/dist/infra/config/global/globalConfig.js.map +1 -1
- package/dist/infra/config/global/globalConfigAccessors.d.ts +6 -0
- package/dist/infra/config/global/globalConfigAccessors.d.ts.map +1 -1
- package/dist/infra/config/global/globalConfigAccessors.js +80 -1
- package/dist/infra/config/global/globalConfigAccessors.js.map +1 -1
- package/dist/infra/config/global/globalConfigCore.d.ts.map +1 -1
- package/dist/infra/config/global/globalConfigCore.js +5 -3
- package/dist/infra/config/global/globalConfigCore.js.map +1 -1
- package/dist/infra/config/global/globalConfigSerializer.d.ts.map +1 -1
- package/dist/infra/config/global/globalConfigSerializer.js +9 -1
- package/dist/infra/config/global/globalConfigSerializer.js.map +1 -1
- package/dist/infra/config/global/index.d.ts +1 -1
- package/dist/infra/config/global/index.d.ts.map +1 -1
- package/dist/infra/config/global/index.js +1 -1
- package/dist/infra/config/global/index.js.map +1 -1
- package/dist/infra/config/loaders/resource-resolver.d.ts.map +1 -1
- package/dist/infra/config/loaders/resource-resolver.js +22 -7
- package/dist/infra/config/loaders/resource-resolver.js.map +1 -1
- package/dist/infra/config/loaders/workflowCallContractValidator.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowCallContractValidator.js +11 -4
- package/dist/infra/config/loaders/workflowCallContractValidator.js.map +1 -1
- package/dist/infra/config/loaders/workflowCallContracts.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowCallContracts.js +5 -0
- package/dist/infra/config/loaders/workflowCallContracts.js.map +1 -1
- package/dist/infra/config/loaders/workflowCallResolver.d.ts +2 -2
- package/dist/infra/config/loaders/workflowCallResolver.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowCallResolver.js +3 -10
- package/dist/infra/config/loaders/workflowCallResolver.js.map +1 -1
- package/dist/infra/config/loaders/workflowCallableArgResolver.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowCallableArgResolver.js +27 -0
- package/dist/infra/config/loaders/workflowCallableArgResolver.js.map +1 -1
- package/dist/infra/config/loaders/workflowCallableRuleValidation.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowCallableRuleValidation.js +5 -0
- package/dist/infra/config/loaders/workflowCallableRuleValidation.js.map +1 -1
- package/dist/infra/config/loaders/workflowLoopMonitorNormalizer.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowLoopMonitorNormalizer.js +12 -1
- package/dist/infra/config/loaders/workflowLoopMonitorNormalizer.js.map +1 -1
- package/dist/infra/config/loaders/workflowPackageScope.d.ts +1 -0
- package/dist/infra/config/loaders/workflowPackageScope.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowPackageScope.js +20 -2
- package/dist/infra/config/loaders/workflowPackageScope.js.map +1 -1
- package/dist/infra/config/loaders/workflowParser.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowParser.js +17 -7
- package/dist/infra/config/loaders/workflowParser.js.map +1 -1
- package/dist/infra/config/loaders/workflowPreview.d.ts +3 -0
- package/dist/infra/config/loaders/workflowPreview.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowPreview.js +43 -5
- package/dist/infra/config/loaders/workflowPreview.js.map +1 -1
- package/dist/infra/config/loaders/workflowRuleNormalizer.d.ts +10 -0
- package/dist/infra/config/loaders/workflowRuleNormalizer.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowRuleNormalizer.js +70 -1
- package/dist/infra/config/loaders/workflowRuleNormalizer.js.map +1 -1
- package/dist/infra/config/loaders/workflowStepNormalizer.d.ts.map +1 -1
- package/dist/infra/config/loaders/workflowStepNormalizer.js +41 -13
- package/dist/infra/config/loaders/workflowStepNormalizer.js.map +1 -1
- package/dist/infra/config/project/projectConfig.d.ts.map +1 -1
- package/dist/infra/config/project/projectConfig.js +19 -3
- package/dist/infra/config/project/projectConfig.js.map +1 -1
- package/dist/infra/config/project/sessionStore.d.ts +2 -2
- package/dist/infra/config/project/sessionStore.d.ts.map +1 -1
- package/dist/infra/config/project/sessionStore.js +12 -4
- package/dist/infra/config/project/sessionStore.js.map +1 -1
- package/dist/infra/config/providerModelRequirements.d.ts +2 -0
- package/dist/infra/config/providerModelRequirements.d.ts.map +1 -0
- package/dist/infra/config/providerModelRequirements.js +2 -0
- package/dist/infra/config/providerModelRequirements.js.map +1 -0
- package/dist/infra/config/resolveConfigValue.d.ts.map +1 -1
- package/dist/infra/config/resolveConfigValue.js +1 -0
- package/dist/infra/config/resolveConfigValue.js.map +1 -1
- package/dist/infra/config/traced/tracedConfigSchema.d.ts.map +1 -1
- package/dist/infra/config/traced/tracedConfigSchema.js +6 -0
- package/dist/infra/config/traced/tracedConfigSchema.js.map +1 -1
- package/dist/infra/git/format.d.ts +1 -0
- package/dist/infra/git/format.d.ts.map +1 -1
- package/dist/infra/git/format.js +21 -0
- package/dist/infra/git/format.js.map +1 -1
- package/dist/infra/git/index.d.ts +1 -1
- package/dist/infra/git/index.d.ts.map +1 -1
- package/dist/infra/git/types.d.ts +16 -4
- package/dist/infra/git/types.d.ts.map +1 -1
- package/dist/infra/github/GitHubProvider.d.ts +2 -1
- package/dist/infra/github/GitHubProvider.d.ts.map +1 -1
- package/dist/infra/github/GitHubProvider.js +4 -1
- package/dist/infra/github/GitHubProvider.js.map +1 -1
- package/dist/infra/github/issue.d.ts +2 -1
- package/dist/infra/github/issue.d.ts.map +1 -1
- package/dist/infra/github/issue.js +23 -2
- package/dist/infra/github/issue.js.map +1 -1
- package/dist/infra/gitlab/GitLabProvider.d.ts +2 -1
- package/dist/infra/gitlab/GitLabProvider.d.ts.map +1 -1
- package/dist/infra/gitlab/GitLabProvider.js +4 -1
- package/dist/infra/gitlab/GitLabProvider.js.map +1 -1
- package/dist/infra/gitlab/issue.d.ts +2 -1
- package/dist/infra/gitlab/issue.d.ts.map +1 -1
- package/dist/infra/gitlab/issue.js +30 -2
- package/dist/infra/gitlab/issue.js.map +1 -1
- package/dist/infra/opencode/OpenCodeStreamHandler.d.ts +4 -0
- package/dist/infra/opencode/OpenCodeStreamHandler.d.ts.map +1 -1
- package/dist/infra/opencode/OpenCodeStreamHandler.js.map +1 -1
- package/dist/infra/opencode/allowedTools.d.ts.map +1 -1
- package/dist/infra/opencode/allowedTools.js +0 -1
- package/dist/infra/opencode/allowedTools.js.map +1 -1
- package/dist/infra/opencode/client.d.ts +5 -1
- package/dist/infra/opencode/client.d.ts.map +1 -1
- package/dist/infra/opencode/client.js +519 -174
- package/dist/infra/opencode/client.js.map +1 -1
- package/dist/infra/opencode/index.d.ts +2 -2
- package/dist/infra/opencode/index.d.ts.map +1 -1
- package/dist/infra/opencode/index.js +1 -1
- package/dist/infra/opencode/index.js.map +1 -1
- package/dist/infra/opencode/types.d.ts +42 -0
- package/dist/infra/opencode/types.d.ts.map +1 -1
- package/dist/infra/opencode/types.js +88 -1
- package/dist/infra/opencode/types.js.map +1 -1
- package/dist/infra/opencode/unavailable-tool-loop.d.ts +25 -0
- package/dist/infra/opencode/unavailable-tool-loop.d.ts.map +1 -1
- package/dist/infra/opencode/unavailable-tool-loop.js +74 -0
- package/dist/infra/opencode/unavailable-tool-loop.js.map +1 -1
- package/dist/infra/providers/claude-headless.d.ts.map +1 -1
- package/dist/infra/providers/claude-headless.js +0 -2
- package/dist/infra/providers/claude-headless.js.map +1 -1
- package/dist/infra/providers/claude-terminal.d.ts.map +1 -1
- package/dist/infra/providers/claude-terminal.js +0 -2
- package/dist/infra/providers/claude-terminal.js.map +1 -1
- package/dist/infra/providers/claude.d.ts.map +1 -1
- package/dist/infra/providers/claude.js +0 -2
- package/dist/infra/providers/claude.js.map +1 -1
- package/dist/infra/providers/copilot.d.ts.map +1 -1
- package/dist/infra/providers/copilot.js +3 -0
- package/dist/infra/providers/copilot.js.map +1 -1
- package/dist/infra/providers/cursor.d.ts.map +1 -1
- package/dist/infra/providers/cursor.js +3 -0
- package/dist/infra/providers/cursor.js.map +1 -1
- package/dist/infra/providers/imageAttachmentPrompt.d.ts +0 -1
- package/dist/infra/providers/imageAttachmentPrompt.d.ts.map +1 -1
- package/dist/infra/providers/imageAttachmentPrompt.js +19 -2
- package/dist/infra/providers/imageAttachmentPrompt.js.map +1 -1
- package/dist/infra/providers/imageAttachments.d.ts +3 -0
- package/dist/infra/providers/imageAttachments.d.ts.map +1 -0
- package/dist/infra/providers/imageAttachments.js +47 -0
- package/dist/infra/providers/imageAttachments.js.map +1 -0
- package/dist/infra/providers/index.d.ts +1 -1
- package/dist/infra/providers/index.d.ts.map +1 -1
- package/dist/infra/providers/index.js.map +1 -1
- package/dist/infra/providers/mock.d.ts.map +1 -1
- package/dist/infra/providers/mock.js +5 -0
- package/dist/infra/providers/mock.js.map +1 -1
- package/dist/infra/providers/opencode.d.ts +3 -2
- package/dist/infra/providers/opencode.d.ts.map +1 -1
- package/dist/infra/providers/opencode.js +31 -6
- package/dist/infra/providers/opencode.js.map +1 -1
- package/dist/infra/providers/types.d.ts +8 -0
- package/dist/infra/providers/types.d.ts.map +1 -1
- package/dist/infra/task/clone-exec.d.ts.map +1 -1
- package/dist/infra/task/clone-exec.js +31 -0
- package/dist/infra/task/clone-exec.js.map +1 -1
- package/dist/infra/task/enqueueService.d.ts +115 -0
- package/dist/infra/task/enqueueService.d.ts.map +1 -0
- package/dist/infra/task/enqueueService.js +191 -0
- package/dist/infra/task/enqueueService.js.map +1 -0
- package/dist/infra/task/enqueuedTaskFile.d.ts +6 -0
- package/dist/infra/task/enqueuedTaskFile.d.ts.map +1 -0
- package/dist/infra/task/enqueuedTaskFile.js +55 -0
- package/dist/infra/task/enqueuedTaskFile.js.map +1 -0
- package/dist/infra/task/issueTask.d.ts +20 -0
- package/dist/infra/task/issueTask.d.ts.map +1 -0
- package/dist/{features/tasks/add → infra/task}/issueTask.js +61 -43
- package/dist/infra/task/issueTask.js.map +1 -0
- package/dist/infra/task/listSerializer.d.ts +1 -0
- package/dist/infra/task/listSerializer.d.ts.map +1 -1
- package/dist/infra/task/listSerializer.js +1 -0
- package/dist/infra/task/listSerializer.js.map +1 -1
- package/dist/infra/task/mapper.d.ts.map +1 -1
- package/dist/infra/task/mapper.js +1 -0
- package/dist/infra/task/mapper.js.map +1 -1
- package/dist/infra/task/taskExecutionSchemas.d.ts +3 -0
- package/dist/infra/task/taskExecutionSchemas.d.ts.map +1 -1
- package/dist/infra/task/taskExecutionSchemas.js +4 -2
- package/dist/infra/task/taskExecutionSchemas.js.map +1 -1
- package/dist/infra/task/taskRecordSchemas.d.ts +2 -0
- package/dist/infra/task/taskRecordSchemas.d.ts.map +1 -1
- package/dist/infra/workflow/system/DefaultSystemStepServices.d.ts.map +1 -1
- package/dist/infra/workflow/system/DefaultSystemStepServices.js +7 -7
- package/dist/infra/workflow/system/DefaultSystemStepServices.js.map +1 -1
- package/dist/infra/workflow/system/system-enqueue-effect.d.ts.map +1 -1
- package/dist/infra/workflow/system/system-enqueue-effect.js +78 -20
- package/dist/infra/workflow/system/system-enqueue-effect.js.map +1 -1
- package/dist/infra/workflow/system/system-git-context.d.ts +6 -6
- package/dist/infra/workflow/system/system-git-context.d.ts.map +1 -1
- package/dist/infra/workflow/system/system-git-context.js +13 -13
- package/dist/infra/workflow/system/system-git-context.js.map +1 -1
- package/dist/infra/workflow/system/system-issue-input-resolver.d.ts +3 -3
- package/dist/infra/workflow/system/system-issue-input-resolver.d.ts.map +1 -1
- package/dist/infra/workflow/system/system-issue-input-resolver.js +8 -8
- package/dist/infra/workflow/system/system-issue-input-resolver.js.map +1 -1
- package/dist/infra/workflow/system/system-pr-effects.d.ts.map +1 -1
- package/dist/infra/workflow/system/system-pr-effects.js +6 -3
- package/dist/infra/workflow/system/system-pr-effects.js.map +1 -1
- package/dist/infra/workflow/system/system-pr-input-resolver.d.ts +3 -3
- package/dist/infra/workflow/system/system-pr-input-resolver.d.ts.map +1 -1
- package/dist/infra/workflow/system/system-pr-input-resolver.js +8 -8
- package/dist/infra/workflow/system/system-pr-input-resolver.js.map +1 -1
- package/dist/infra/workflow/system/system-sync-effects.js +2 -2
- package/dist/infra/workflow/system/system-sync-effects.js.map +1 -1
- package/dist/shared/package-info.d.ts +2 -0
- package/dist/shared/package-info.d.ts.map +1 -0
- package/dist/shared/package-info.js +5 -0
- package/dist/shared/package-info.js.map +1 -0
- package/dist/shared/prompts/en/exec_assistant_instruct.md +2 -0
- package/dist/shared/prompts/en/finding_manager_instruction.md +9 -2
- package/dist/shared/prompts/en/opencode_review_agent_prompt.md +20 -23
- package/dist/shared/prompts/en/parts/finding_contract_instruction.md +25 -0
- package/dist/shared/prompts/en/parts/git_rules.md +5 -0
- package/dist/shared/prompts/{ja → en/parts}/structured_json_schema_instruction.md +2 -0
- package/dist/shared/prompts/en/{structured_json_step_instruction.md → parts/structured_json_step_instruction.md} +2 -0
- package/dist/shared/prompts/ja/exec_assistant_instruct.md +2 -0
- package/dist/shared/prompts/ja/finding_manager_instruction.md +9 -2
- package/dist/shared/prompts/ja/opencode_review_agent_prompt.md +20 -23
- package/dist/shared/prompts/ja/parts/finding_contract_instruction.md +25 -0
- package/dist/shared/prompts/ja/parts/git_rules.md +5 -0
- package/dist/shared/prompts/{en → ja/parts}/structured_json_schema_instruction.md +4 -2
- package/dist/shared/prompts/ja/{structured_json_step_instruction.md → parts/structured_json_step_instruction.md} +4 -2
- package/dist/shared/types/image-attachments.d.ts +10 -0
- package/dist/shared/types/image-attachments.d.ts.map +1 -0
- package/dist/shared/types/image-attachments.js +2 -0
- package/dist/shared/types/image-attachments.js.map +1 -0
- package/dist/shared/utils/entrypoint.d.ts +2 -0
- package/dist/shared/utils/entrypoint.d.ts.map +1 -0
- package/dist/shared/utils/entrypoint.js +15 -0
- package/dist/shared/utils/entrypoint.js.map +1 -0
- package/dist/shared/utils/imageAttachmentReferences.d.ts +7 -0
- package/dist/shared/utils/imageAttachmentReferences.d.ts.map +1 -0
- package/dist/shared/utils/imageAttachmentReferences.js +64 -0
- package/dist/shared/utils/imageAttachmentReferences.js.map +1 -0
- package/dist/shared/utils/index.d.ts +1 -0
- package/dist/shared/utils/index.d.ts.map +1 -1
- package/dist/shared/utils/index.js +1 -0
- package/dist/shared/utils/index.js.map +1 -1
- package/dist/shared/utils/safeExternalErrorMessage.d.ts +2 -0
- package/dist/shared/utils/safeExternalErrorMessage.d.ts.map +1 -0
- package/dist/shared/utils/safeExternalErrorMessage.js +22 -0
- package/dist/shared/utils/safeExternalErrorMessage.js.map +1 -0
- package/package.json +32 -13
- package/builtins/en/facets/instructions/review-pure.md +0 -9
- package/builtins/en/facets/personas/pure-reviewer.md +0 -22
- package/builtins/ja/facets/instructions/review-pure.md +0 -9
- package/builtins/ja/facets/personas/pure-reviewer.md +0 -22
- package/dist/core/workflow/claude-effort-compatibility.d.ts +0 -3
- package/dist/core/workflow/claude-effort-compatibility.d.ts.map +0 -1
- package/dist/core/workflow/claude-effort-compatibility.js +0 -28
- package/dist/core/workflow/claude-effort-compatibility.js.map +0 -1
- package/dist/core/workflow/provider-model-compatibility.d.ts +0 -6
- package/dist/core/workflow/provider-model-compatibility.d.ts.map +0 -1
- package/dist/core/workflow/provider-model-compatibility.js +0 -20
- package/dist/core/workflow/provider-model-compatibility.js.map +0 -1
- package/dist/features/tasks/add/issueTask.d.ts +0 -7
- package/dist/features/tasks/add/issueTask.d.ts.map +0 -1
- package/dist/features/tasks/add/issueTask.js.map +0 -1
- package/dist/features/tasks/list/retryTaskSpecAttachments.d.ts.map +0 -1
- package/dist/features/tasks/list/retryTaskSpecAttachments.js.map +0 -1
- package/dist/infra/config/providerModelCompatibility.d.ts +0 -2
- package/dist/infra/config/providerModelCompatibility.d.ts.map +0 -1
- package/dist/infra/config/providerModelCompatibility.js +0 -2
- package/dist/infra/config/providerModelCompatibility.js.map +0 -1
- /package/dist/shared/prompts/en/{source_context_section_guidance.md → parts/source_context_section_guidance.md} +0 -0
- /package/dist/shared/prompts/en/{source_context_system_guard.md → parts/source_context_system_guard.md} +0 -0
- /package/dist/shared/prompts/ja/{source_context_section_guidance.md → parts/source_context_section_guidance.md} +0 -0
- /package/dist/shared/prompts/ja/{source_context_system_guard.md → parts/source_context_system_guard.md} +0 -0
|
@@ -1,29 +1,126 @@
|
|
|
1
1
|
# CQRS+ES Knowledge
|
|
2
2
|
|
|
3
|
+
## CQRS+ES Adoption Decision
|
|
4
|
+
|
|
5
|
+
CQRS+ES is a design in which state changes are stored as domain events, and current state and Read Models are derived from them. Even when the backend as a whole or the workflow handles CQRS+ES, not every new feature needs to be implemented with event sourcing.
|
|
6
|
+
|
|
7
|
+
| Criteria | Judgment |
|
|
8
|
+
|----------|----------|
|
|
9
|
+
| User request, design material, or existing boundaries explicitly require CQRS+ES | Adopt CQRS+ES |
|
|
10
|
+
| State transitions, lifecycle, and business invariants are central to the feature | Consider CQRS+ES |
|
|
11
|
+
| Change events trigger other Aggregates, Sagas, or downstream processes | Consider CQRS+ES |
|
|
12
|
+
| Restoring state at a past point, event replay, or audit evidence itself is a requirement | Consider CQRS+ES |
|
|
13
|
+
| Read Models need to be projected asynchronously for multiple uses | Consider CQRS+ES |
|
|
14
|
+
| The feature is complete with current-value reference and updates only | Prefer CRUD |
|
|
15
|
+
| Security settings, feature flags, allowlists, thresholds, or similar values require immediate reflection | Prefer CRUD |
|
|
16
|
+
| There is no domain vocabulary beyond "create/update/delete" | Prefer CRUD |
|
|
17
|
+
| The task is merely being implemented in a CQRS+ES workflow | Do not use as adoption rationale |
|
|
18
|
+
| Adding CQRS+ES requirements to a task specification when they were absent from the original task | REJECT |
|
|
19
|
+
|
|
20
|
+
Adopt CQRS+ES based on requirements. An existing system containing CQRS+ES can justify aligning dependencies and boundaries, but it does not justify event-sourcing simple settings tables.
|
|
21
|
+
|
|
22
|
+
### Requirement Transformation
|
|
23
|
+
|
|
24
|
+
If the original task or user request states only CRUD-equivalent business requirements, do not add "commands, events, and projections" as new requirements in the task specification. If it is unclear whether CQRS+ES is needed, state the adoption rationale or leave it as an open question.
|
|
25
|
+
|
|
26
|
+
| Original Request | How to Convert into a Specification |
|
|
27
|
+
|------------------|-------------------------------------|
|
|
28
|
+
| "Manage allowed IPs per facility" | Treat as CRUD-managed settings. The domain vocabulary is only "add/delete" and has no business rules |
|
|
29
|
+
| "Manage order approval, cancellation, and returns, and trigger billing or inventory depending on state" | Candidate for CQRS+ES. Complex state transitions and business invariants, with multiple Aggregates interacting |
|
|
30
|
+
| "For insurance contract changes, review rules differ by change type, and past assessment history affects future decisions" | Candidate for CQRS+ES. Business rules are complex and change over time; history itself is input to business decisions |
|
|
31
|
+
| "Show who changed what and when on the screen" | Check whether CRUD + audit logs is enough. If the requirement is only displaying change history, audit columns are often sufficient |
|
|
32
|
+
| "Toggle notification settings on/off" | Treat as CRUD-managed settings. It only references and updates current values |
|
|
33
|
+
|
|
34
|
+
CQRS+ES is most valuable in complex business domains, such as finance, insurance, or healthcare, where business rules are complex and change over time. Simple audit requirements or technical asynchronous processing alone are not sufficient conditions for CQRS+ES. The decision axis is business-logic complexity.
|
|
35
|
+
|
|
3
36
|
## Aggregate Design
|
|
4
37
|
|
|
5
38
|
Aggregates hold only fields necessary for decision-making.
|
|
6
39
|
|
|
7
|
-
Command Model (Aggregate) role is to "receive commands, make decisions, and emit events". Query data
|
|
40
|
+
The Command Model (Aggregate) role is to "receive commands, make decisions, and emit events". Query data belongs to the Read Model (Projection).
|
|
8
41
|
|
|
9
|
-
"Necessary for decision" means:
|
|
10
|
-
- Used in `if
|
|
11
|
-
- Field
|
|
42
|
+
"Necessary for decision-making" means:
|
|
43
|
+
- Used in `if` / `require` conditional branches
|
|
44
|
+
- Field values are referenced by instance methods when emitting events
|
|
12
45
|
|
|
13
46
|
| Criteria | Judgment |
|
|
14
47
|
|----------|----------|
|
|
15
48
|
| Aggregate spans multiple transaction boundaries | REJECT |
|
|
16
|
-
| Direct references between Aggregates
|
|
49
|
+
| Direct references between Aggregates, not ID references | REJECT |
|
|
17
50
|
| Aggregate exceeds 100 lines | Consider splitting |
|
|
18
|
-
| Business invariants exist outside Aggregate | REJECT |
|
|
51
|
+
| Business invariants exist outside the Aggregate | REJECT |
|
|
19
52
|
| Holding fields not used for decisions | REJECT |
|
|
53
|
+
| Branching state transitions with origin metadata such as `source` / `input` / `origin` / `channel` / `type` | REJECT by default |
|
|
54
|
+
| Rejecting, only for a specific input source, a state allowed by the existing Aggregate's normal lifecycle | REJECT |
|
|
55
|
+
| Keeping the creation-time caller in Aggregate state and reusing it as the actor of later events | REJECT. Pass the performer with each command |
|
|
56
|
+
|
|
57
|
+
Being used in an `if` / `require` branch is not enough to justify keeping a field in Aggregate state. First verify that the branch or validation is an essential invariant of the whole Aggregate.
|
|
58
|
+
|
|
59
|
+
### Origin Metadata and Invariants
|
|
60
|
+
|
|
61
|
+
Origin metadata such as input source, channel, producer, or integration source can be needed for display, search, audit, or integration tracing. That need alone does not justify restoring it as Aggregate state.
|
|
62
|
+
|
|
63
|
+
| Criteria | Judgment |
|
|
64
|
+
|----------|----------|
|
|
65
|
+
| Origin metadata is needed only for display, search, audit, or integration tracing | Keep it in the Event payload or Read Model |
|
|
66
|
+
| A branch based on origin metadata creates constraints that differ from the existing Aggregate's normal lifecycle | REJECT |
|
|
67
|
+
| A field that is normally optional becomes required only for one input source | REJECT |
|
|
68
|
+
| Invariants truly differ by input source | Consider a separate Aggregate, Command, or UseCase boundary |
|
|
69
|
+
| Adding origin metadata to Aggregate state only to support a `require` | REJECT |
|
|
70
|
+
|
|
71
|
+
```kotlin
|
|
72
|
+
// NG - Origin metadata narrows the normal lifecycle of an existing Aggregate
|
|
73
|
+
data class Note(
|
|
74
|
+
val noteId: String,
|
|
75
|
+
val sourceType: SourceType?,
|
|
76
|
+
val targetIds: List<String>,
|
|
77
|
+
) {
|
|
78
|
+
fun update(text: String, targetIds: List<String>): NoteUpdatedEvent {
|
|
79
|
+
if (sourceType == SourceType.EXTERNAL_IMPORT) {
|
|
80
|
+
require(targetIds.isNotEmpty())
|
|
81
|
+
}
|
|
82
|
+
return NoteUpdatedEvent(noteId, text, targetIds)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// OK - Track origin in events/read models and align Aggregate invariants with the normal lifecycle
|
|
87
|
+
data class Note(
|
|
88
|
+
val noteId: String,
|
|
89
|
+
val confirmed: Boolean,
|
|
90
|
+
) {
|
|
91
|
+
fun update(text: String, targetIds: List<String>): NoteUpdatedEvent {
|
|
92
|
+
check(!confirmed)
|
|
93
|
+
return NoteUpdatedEvent(noteId, text, targetIds)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
data class NoteCreatedEvent(
|
|
98
|
+
val noteId: String,
|
|
99
|
+
val text: String,
|
|
100
|
+
val targetIds: List<String>,
|
|
101
|
+
val sourceType: SourceType?, // Origin fact used by projections or audit
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Existing Lifecycle Priority
|
|
106
|
+
|
|
107
|
+
When integrating a new input flow into an existing Aggregate, prefer the existing normal lifecycle. Do not add input-source-specific commands, wrappers, services, or deletion paths merely because the input source differs.
|
|
108
|
+
|
|
109
|
+
| Criteria | Judgment |
|
|
110
|
+
|----------|----------|
|
|
111
|
+
| The existing Aggregate's normal command / event can represent the same fact | Use the existing lifecycle |
|
|
112
|
+
| An input-source-specific wrapper only thinly delegates to a normal command | REJECT |
|
|
113
|
+
| An input-source-specific command adds stricter required fields than the normal lifecycle | REJECT |
|
|
114
|
+
| Existing Aggregate deletion or update events can trigger derived processing | Separate into EventHandler |
|
|
115
|
+
| Display or search fields needed only by a specialized flow | Keep them in the Read Model |
|
|
116
|
+
| State transitions or invariants truly differ by input source | Consider a separate Aggregate / bounded context |
|
|
20
117
|
|
|
21
118
|
Good Aggregate:
|
|
22
119
|
```kotlin
|
|
23
120
|
// Only fields necessary for decisions
|
|
24
121
|
data class Order(
|
|
25
122
|
val orderId: String, // Used when emitting events
|
|
26
|
-
val status: OrderStatus // Used for state
|
|
123
|
+
val status: OrderStatus // Used for state checks
|
|
27
124
|
) {
|
|
28
125
|
fun confirm(confirmedBy: String): OrderConfirmedEvent {
|
|
29
126
|
require(status == OrderStatus.PENDING) { "Cannot confirm in this state" }
|
|
@@ -38,15 +135,15 @@ data class Order(
|
|
|
38
135
|
// Holding fields not used for decisions (NG)
|
|
39
136
|
data class Order(
|
|
40
137
|
val orderId: String,
|
|
41
|
-
val customerId: String,
|
|
138
|
+
val customerId: String, // Not used for decisions
|
|
42
139
|
val shippingAddress: Address, // Not used for decisions
|
|
43
140
|
val status: OrderStatus
|
|
44
141
|
)
|
|
45
142
|
```
|
|
46
143
|
|
|
47
|
-
Aggregates with no additional operations have ID
|
|
144
|
+
Aggregates with no additional operations have only an ID:
|
|
48
145
|
```kotlin
|
|
49
|
-
//
|
|
146
|
+
// Creation only, no additional operations
|
|
50
147
|
data class Notification(val notificationId: String) {
|
|
51
148
|
companion object {
|
|
52
149
|
fun create(customerId: String, message: String): NotificationCreatedEvent {
|
|
@@ -60,61 +157,216 @@ data class Notification(val notificationId: String) {
|
|
|
60
157
|
}
|
|
61
158
|
```
|
|
62
159
|
|
|
160
|
+
### Adapter Pattern: Separating Domain from Framework
|
|
161
|
+
|
|
162
|
+
Do not put framework annotations such as `@Aggregate` or `@CommandHandler` directly on domain models. Adapter classes handle framework integration, and domain models focus on business logic.
|
|
163
|
+
|
|
164
|
+
```kotlin
|
|
165
|
+
// Domain model: framework-independent business logic only
|
|
166
|
+
data class Order(
|
|
167
|
+
val orderId: String,
|
|
168
|
+
val status: OrderStatus = OrderStatus.PENDING
|
|
169
|
+
) {
|
|
170
|
+
companion object {
|
|
171
|
+
fun place(orderId: String, customerId: String): OrderPlacedEvent {
|
|
172
|
+
require(customerId.isNotBlank()) { "Customer ID cannot be blank" }
|
|
173
|
+
return OrderPlacedEvent(orderId, customerId)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fun from(event: OrderPlacedEvent): Order {
|
|
177
|
+
return Order(orderId = event.orderId, status = OrderStatus.PENDING)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fun confirm(confirmedBy: String): OrderConfirmedEvent {
|
|
182
|
+
require(status == OrderStatus.PENDING) { "Cannot confirm in this state" }
|
|
183
|
+
return OrderConfirmedEvent(orderId, confirmedBy, LocalDateTime.now())
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
fun apply(event: OrderEvent): Order = when (event) {
|
|
187
|
+
is OrderPlacedEvent -> from(event)
|
|
188
|
+
is OrderConfirmedEvent -> copy(status = OrderStatus.CONFIRMED)
|
|
189
|
+
is OrderCancelledEvent -> copy(status = OrderStatus.CANCELLED)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Adapter: framework integration. Domain call -> event publication bridge
|
|
194
|
+
@Aggregate
|
|
195
|
+
class OrderAggregateAdapter() {
|
|
196
|
+
private var order: Order? = null
|
|
197
|
+
|
|
198
|
+
@AggregateIdentifier
|
|
199
|
+
fun orderId(): String? = order?.orderId
|
|
200
|
+
|
|
201
|
+
@CommandHandler
|
|
202
|
+
constructor(command: PlaceOrderCommand) : this() {
|
|
203
|
+
val event = Order.place(command.orderId, command.customerId)
|
|
204
|
+
AggregateLifecycle.apply(event)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@CommandHandler
|
|
208
|
+
fun handle(command: ConfirmOrderCommand) {
|
|
209
|
+
val event = order!!.confirm(command.confirmedBy)
|
|
210
|
+
AggregateLifecycle.apply(event)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@EventSourcingHandler
|
|
214
|
+
fun on(event: OrderEvent) {
|
|
215
|
+
this.order = when (event) {
|
|
216
|
+
is OrderPlacedEvent -> Order.from(event)
|
|
217
|
+
else -> order?.apply(event)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Benefits of separation:
|
|
224
|
+
- Domain models can be unit-tested without a framework
|
|
225
|
+
- Domain models do not need to change when the framework changes
|
|
226
|
+
- Adapters are boilerplate for receiving commands, calling the domain, and publishing events
|
|
227
|
+
|
|
228
|
+
### apply/from Pattern: Event Replay
|
|
229
|
+
|
|
230
|
+
A pattern in which a domain model rebuilds its own state from events.
|
|
231
|
+
|
|
232
|
+
- `from(event)`: factory that builds initial state from a creation event
|
|
233
|
+
- `apply(event)`: returns new state from an event, using immutable updates with `copy()`
|
|
234
|
+
- `when` expressions plus sealed interfaces let the compiler guarantee exhaustiveness over event types
|
|
235
|
+
|
|
236
|
+
```kotlin
|
|
237
|
+
fun apply(event: OrderEvent): Order = when (event) {
|
|
238
|
+
is OrderPlacedEvent -> from(event)
|
|
239
|
+
is OrderConfirmedEvent -> copy(status = OrderStatus.CONFIRMED)
|
|
240
|
+
is OrderShippedEvent -> copy(status = OrderStatus.SHIPPED)
|
|
241
|
+
// Because the interface is sealed, adding an event type without handling it is a compile error
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
| Criteria | Judgment |
|
|
246
|
+
|----------|----------|
|
|
247
|
+
| Business logic such as validation inside `apply` | REJECT. `apply` is state restoration only |
|
|
248
|
+
| `apply` has side effects such as DB operations or event emission | REJECT |
|
|
249
|
+
| `apply` throws exceptions | REJECT. Replay failures are not acceptable |
|
|
250
|
+
|
|
63
251
|
## Event Design
|
|
64
252
|
|
|
65
253
|
| Criteria | Judgment |
|
|
66
254
|
|----------|----------|
|
|
67
|
-
| Event not in past tense
|
|
255
|
+
| Event is not in past tense, such as Created -> Create | REJECT |
|
|
68
256
|
| Event contains logic | REJECT |
|
|
69
|
-
| Event contains internal state of
|
|
70
|
-
| Event schema not
|
|
71
|
-
| CRUD-style events
|
|
257
|
+
| Event contains internal state of another Aggregate | REJECT |
|
|
258
|
+
| Event schema is not versioned | Warning |
|
|
259
|
+
| CRUD-style events such as Updated or Deleted | Needs review |
|
|
72
260
|
|
|
73
|
-
Good
|
|
261
|
+
Good events:
|
|
74
262
|
```kotlin
|
|
75
|
-
// Good:
|
|
263
|
+
// Good: domain intent is clear
|
|
76
264
|
OrderPlaced, PaymentReceived, ItemShipped
|
|
77
265
|
|
|
78
266
|
// Bad: CRUD style
|
|
79
267
|
OrderUpdated, OrderDeleted
|
|
80
268
|
```
|
|
81
269
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
270
|
+
### Fact Events vs Request Events
|
|
271
|
+
|
|
272
|
+
Events express facts that occurred, and their names come from business meaning. A `...Requested` suffix or the current number of consumers does not by itself distinguish a fact event from a command in disguise. Accepting or starting a request can be a business fact; a message whose only meaning is instructing a known destination to execute work is a command.
|
|
273
|
+
|
|
274
|
+
| Decision axis | Fact event | Consider a command |
|
|
275
|
+
|---------------|------------|--------------------|
|
|
276
|
+
| Business meaning | An occurrence such as acceptance, start, or rejection that matters to audit or replay | Its only purpose is to make a specific process run |
|
|
277
|
+
| Emitter lifecycle | Used by later decisions such as waiting, duplicate rejection, or timeout | The emitter tracks neither state nor outcome |
|
|
278
|
+
| Outcome | Continues to another uncertain fact such as completion or failure | Can run immediately within the same boundary and return its result there |
|
|
279
|
+
| Consumers | Consumers may change without changing event meaning | Destination and operation are the message's meaning |
|
|
280
|
+
|
|
281
|
+
Split events by independent business facts, not by technical consumers. Do not emit duplicate state and trigger events for the same occurrence; EventHandlers and projections subscribe to the fact owned by the emitting Aggregate. Multiple events are appropriate only when independently named, audited, and replayed facts occur together. Do not pack another Aggregate's internal state or initialization details into the event; resolve stable IDs or references at the boundary.
|
|
282
|
+
|
|
283
|
+
```kotlin
|
|
284
|
+
// NG - One business fact duplicated as separate state and technical trigger events
|
|
285
|
+
fun addItem(itemId: String, productId: String, quantity: Int): List<OrderEvent> = listOf(
|
|
286
|
+
OrderItemLinkedEvent(orderId, itemId), // for state
|
|
287
|
+
OrderItemCreationRequestedEvent(orderId, itemId, productId, quantity), // for triggering (a command in effect)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
// OK - A single event carrying the content that is factual for the emitting Aggregate
|
|
291
|
+
fun addItem(itemId: String, productId: String, quantity: Int): OrderItemAddedEvent =
|
|
292
|
+
OrderItemAddedEvent(orderId, itemId, productId, quantity)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
| Criteria | Judgment |
|
|
296
|
+
|----------|----------|
|
|
297
|
+
| Classifying a message as an event or command from its suffix or current consumer count alone | REJECT. Judge business meaning and lifecycle |
|
|
298
|
+
| Splitting the same business fact into a state event (Linked, etc.) and a trigger event (Requested, etc.) | REJECT. Merge them into the fact owned by the emitting Aggregate |
|
|
299
|
+
| An asynchronous request to an external service or another context where acceptance or waiting is a business fact and completion/failure is tracked | OK. It can be expressed as the fact that the request was accepted |
|
|
300
|
+
| Adding a dedicated request event for processing that an existing fact event (confirmed, approved, etc.) can drive | REJECT. Have an EventHandler, and a domain policy when needed, subscribe to the existing fact |
|
|
301
|
+
| Recording an occurrence that only changes another Aggregate's state as an event in one's own stream | REJECT. Facts belong to the stream of the Aggregate where they happened |
|
|
302
|
+
| Routing an operation unrelated to an Aggregate's own state or lifecycle through it and relaying it to the target Aggregate | REJECT. Send the command to the target Aggregate and perform membership or existence checks at the boundary that owns the invariant |
|
|
303
|
+
|
|
304
|
+
### Event Type Hierarchy with sealed interface
|
|
305
|
+
|
|
306
|
+
Aggregate events should use a sealed interface type hierarchy. The Aggregate root ID should be required as a common field, enabling exhaustive `when` checks.
|
|
307
|
+
|
|
308
|
+
```kotlin
|
|
309
|
+
sealed interface OrderEvent {
|
|
310
|
+
val orderId: String // Required on every event
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
data class OrderPlacedEvent(
|
|
314
|
+
override val orderId: String,
|
|
315
|
+
val customerId: String
|
|
316
|
+
) : OrderEvent
|
|
317
|
+
|
|
318
|
+
data class OrderConfirmedEvent(
|
|
319
|
+
override val orderId: String,
|
|
320
|
+
val approvalInfo: ApprovalInfo
|
|
321
|
+
) : OrderEvent
|
|
322
|
+
|
|
323
|
+
data class OrderCancelledEvent(
|
|
324
|
+
override val orderId: String,
|
|
325
|
+
val cancellationInfo: CancellationInfo
|
|
326
|
+
) : OrderEvent
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Benefits:
|
|
330
|
+
- A `when (event)` expression must list every event type, otherwise compilation fails. This is especially important in `apply`
|
|
331
|
+
- The compiler guarantees that the Aggregate root ID exists
|
|
332
|
+
- Event-handler branching by type is safer
|
|
333
|
+
|
|
334
|
+
Event granularity:
|
|
335
|
+
- Too fine: `OrderFieldChanged` -> domain intent is unclear
|
|
336
|
+
- Appropriate: `ShippingAddressChanged` -> intent is clear
|
|
337
|
+
- Too coarse: `OrderModified` -> unclear what changed
|
|
86
338
|
|
|
87
339
|
## Event Evolution
|
|
88
340
|
|
|
89
|
-
Events are persisted contracts.
|
|
341
|
+
Events are persisted contracts. If the current event type changes, old events must still be replayable. Translating old events belongs in the upcaster / migration layer at the event-store restoration boundary, not in event classes or domain logic.
|
|
90
342
|
|
|
91
343
|
| Criteria | Judgment |
|
|
92
344
|
|----------|----------|
|
|
93
|
-
| Persisted event type or fields changed
|
|
94
|
-
| Current event type keeps aliases or compatibility-only properties
|
|
95
|
-
| Aggregate or apply directly interprets old event shapes | REJECT. Convert to current events before replay |
|
|
96
|
-
| Event
|
|
97
|
-
| Upcaster converts old payloads
|
|
98
|
-
| Tests verify old payloads
|
|
345
|
+
| Persisted event type or fields changed without a translation path | REJECT |
|
|
346
|
+
| Current event type keeps old field aliases or compatibility-only properties | REJECT. Keep history compatibility in upcasters |
|
|
347
|
+
| Aggregate or `apply` directly interprets old event shapes | REJECT. Convert to current events before replay |
|
|
348
|
+
| Event adds "previous value" only for compatibility | REJECT. Events represent the fact after it happened |
|
|
349
|
+
| Upcaster converts old payloads into current event meaning | OK |
|
|
350
|
+
| Tests verify conversion from old payloads to current events | OK |
|
|
99
351
|
|
|
100
|
-
|
|
352
|
+
Responsibilities in event evolution:
|
|
101
353
|
|
|
102
|
-
| Responsibility |
|
|
103
|
-
|
|
354
|
+
| Responsibility | Location |
|
|
355
|
+
|----------------|----------|
|
|
104
356
|
| Current event meaning and fields | Event type |
|
|
105
|
-
|
|
|
106
|
-
| State restoration
|
|
107
|
-
| Guarantee that old events can
|
|
357
|
+
| Reading old payloads | Upcaster / migration layer |
|
|
358
|
+
| State restoration from event replay | Aggregate `apply` |
|
|
359
|
+
| Guarantee that old events can be converted to current events | Upcaster tests |
|
|
108
360
|
|
|
109
361
|
```kotlin
|
|
110
|
-
// NG -
|
|
362
|
+
// NG - Mixing old field compatibility into the current event type
|
|
111
363
|
data class OrderAssignedEvent(
|
|
112
364
|
val orderId: String,
|
|
113
365
|
@JsonAlias("assigneeId")
|
|
114
366
|
val assigneeIds: List<String>
|
|
115
367
|
)
|
|
116
368
|
|
|
117
|
-
// OK -
|
|
369
|
+
// OK - Current event type represents only the current contract
|
|
118
370
|
data class OrderAssignedEvent(
|
|
119
371
|
val orderId: String,
|
|
120
372
|
val assigneeIds: List<String>
|
|
@@ -122,7 +374,7 @@ data class OrderAssignedEvent(
|
|
|
122
374
|
```
|
|
123
375
|
|
|
124
376
|
```kotlin
|
|
125
|
-
// OK -
|
|
377
|
+
// OK - Convert old payloads to current payloads in an upcaster
|
|
126
378
|
when (eventType) {
|
|
127
379
|
OrderAssignedEvent::class.java.typeName -> {
|
|
128
380
|
event.moveTextFieldToArray("assigneeId", "assigneeIds")
|
|
@@ -130,135 +382,326 @@ when (eventType) {
|
|
|
130
382
|
}
|
|
131
383
|
```
|
|
132
384
|
|
|
133
|
-
Whether to keep old event
|
|
385
|
+
Whether to keep old event types in application code depends on the framework and operational policy. In general, it is better not to treat old types as normal domain events. Instead, test the old serialized type and payload as input contracts for the upcaster, keeping the current model clean.
|
|
386
|
+
|
|
387
|
+
### Migration Scope Decomposition
|
|
388
|
+
|
|
389
|
+
CQRS+ES migration must distinguish DB schema migration, data migration, event upcasters, Read Model rebuilds, and API compatibility work. Do not treat "migrate / do not migrate" as a single decision.
|
|
390
|
+
|
|
391
|
+
| Criteria | Judgment |
|
|
392
|
+
|----------|----------|
|
|
393
|
+
| The user says migration is unnecessary, but the migration type is not decomposed | REJECT |
|
|
394
|
+
| DB schema migration or data migration is added while the scope is unclear | REJECT |
|
|
395
|
+
| An upcaster is added even though no persisted old events exist | REJECT |
|
|
396
|
+
| Data migration is created even though the Read Model can be rebuilt from events | REJECT. Check whether rebuild is enough |
|
|
397
|
+
| Add an upcaster only when event payload compatibility is required | OK |
|
|
398
|
+
| Treat API compatibility and event compatibility as separate decisions | OK |
|
|
134
399
|
|
|
135
400
|
## Command Handlers
|
|
136
401
|
|
|
137
402
|
| Criteria | Judgment |
|
|
138
403
|
|----------|----------|
|
|
139
|
-
| Handler directly manipulates DB | REJECT |
|
|
140
|
-
| Handler
|
|
141
|
-
|
|
|
404
|
+
| Handler directly manipulates the DB | REJECT |
|
|
405
|
+
| Handler changes multiple Aggregates | REJECT |
|
|
406
|
+
| Command has no validation | REJECT |
|
|
142
407
|
| Handler executes queries to make decisions | Needs review |
|
|
408
|
+
| The return contract does not match the number of events an operation can produce | Needs review. Choose a single, optional, collection, or result type from domain cardinality and language/framework conventions |
|
|
409
|
+
|
|
410
|
+
### Contract Lifetimes of Commands and Events
|
|
411
|
+
|
|
412
|
+
Events are long-lived contracts persisted as history, so stored type identifiers and payloads must remain replayable. Whether the type identifier is a fully qualified class name or a logical name, and whether changes use an upcaster, alias, or migration, depends on the event store and serialization strategy.
|
|
143
413
|
|
|
144
|
-
|
|
414
|
+
Commands are usually short-lived messages created and handled at the application boundary, but some architectures persist them for scheduling, outbox delivery, retries, dead-letter handling, or audit. Decide placement and compatibility from responsibility and the actual storage contract, not from an assumption that every command is transient. Domain models should not depend on transport- or framework-specific command types; translate them into domain arguments and value objects at the application or adapter boundary.
|
|
415
|
+
|
|
416
|
+
| Criteria | Judgment |
|
|
417
|
+
|----------|----------|
|
|
418
|
+
| A domain model directly receives a transport- or framework-specific command type | REJECT. Translate it into domain input at the application or adapter boundary |
|
|
419
|
+
| An application message unused by domain classes sits in the domain package | Move it to the application boundary |
|
|
420
|
+
| A command package is moved or renamed without checking for persisted references | REJECT. Check scheduling, outbox, retry, dead-letter, and audit storage contracts |
|
|
421
|
+
| A stored event type identifier or payload changes without a conversion path | REJECT. Provide compatibility for the identifier and serialization strategy in use. Direct change is allowed only when no persisted events exist yet; release status is not the criterion |
|
|
422
|
+
|
|
423
|
+
Good command handler:
|
|
145
424
|
```
|
|
146
|
-
1. Receive command
|
|
147
|
-
2. Restore Aggregate from event store
|
|
148
|
-
3. Apply command to Aggregate
|
|
149
|
-
4.
|
|
425
|
+
1. Receive a command
|
|
426
|
+
2. Restore the Aggregate from the event store
|
|
427
|
+
3. Apply the command to the Aggregate
|
|
428
|
+
4. Store the emitted events
|
|
150
429
|
```
|
|
151
430
|
|
|
431
|
+
### Multi-layer Validation
|
|
432
|
+
|
|
433
|
+
Validation responsibilities differ by layer. Do not collect every validation in one place.
|
|
434
|
+
|
|
435
|
+
| Layer | Responsibility | Means | Example |
|
|
436
|
+
|-------|----------------|-------|---------|
|
|
437
|
+
| API layer | Structural validation | `@NotBlank`, `init` block | Required fields, type, format |
|
|
438
|
+
| UseCase layer | Business-rule validation | Querying Read Models | Duplicate checks, existence of prerequisites |
|
|
439
|
+
| Domain layer | State-transition invariants | `require` | "Can only approve when PENDING" |
|
|
440
|
+
|
|
152
441
|
### Aggregate Decision Boundary
|
|
153
442
|
|
|
154
|
-
Aggregates make decisions only from state
|
|
443
|
+
Aggregates make decisions only from state restored from their own event history and facts explicitly supplied as commands. They are not the place to interpret, normalize, or verify ownership of boundary-originated input.
|
|
444
|
+
|
|
445
|
+
Validation inside an Aggregate must be limited to state that can be reproduced solely by event replay. Other validation should be resolved at the boundary before command dispatch, and resolved facts should be passed to the Aggregate.
|
|
446
|
+
|
|
447
|
+
| Decision Target | Location |
|
|
448
|
+
|-----------------|----------|
|
|
449
|
+
| Whether the operation is possible in the current state | Aggregate |
|
|
450
|
+
| Whether command executor matches Aggregate owner | Aggregate |
|
|
451
|
+
| HTTP/API input shape is valid | API layer |
|
|
452
|
+
| Interpreting formats of external identifiers such as object keys, URLs, paths | UseCase layer or boundary policy/verifier |
|
|
453
|
+
| External identifier belongs to the current user/tenant | UseCase layer or boundary policy/verifier |
|
|
454
|
+
| Checking another Aggregate's Read Model or external facts | UseCase layer |
|
|
455
|
+
| State-transition decisions based on the same Aggregate's current state | Aggregate |
|
|
456
|
+
| Entity exists in an external service | Application-layer external-service integration |
|
|
155
457
|
|
|
156
|
-
|
|
458
|
+
Example: in an upload-completion command, the Aggregate decides whether the session owner matches the executor and whether the current state allows completion. The string shape of the object key and whether that key belongs to the current user/tenant area are verified in the UseCase layer before the command is sent.
|
|
157
459
|
|
|
158
|
-
|
|
159
|
-
|-----------------|-------|
|
|
160
|
-
| Whether the current state allows the operation | Aggregate |
|
|
161
|
-
| Whether the command requester matches the Aggregate owner | Aggregate |
|
|
162
|
-
| Whether HTTP/API input shape is valid | API layer |
|
|
163
|
-
| Parsing external identifiers such as object keys, URLs, or paths | UseCase layer or boundary-side Policy/Verifier |
|
|
164
|
-
| Whether an external identifier belongs to the current user/tenant | UseCase layer or boundary-side Policy/Verifier |
|
|
165
|
-
| Checking Read Models or other Aggregate state | UseCase layer |
|
|
166
|
-
| Checking that an external resource exists | Application-layer integration with the external service |
|
|
460
|
+
### Command Intent and Pre-querying
|
|
167
461
|
|
|
168
|
-
|
|
462
|
+
A Command represents what the user or external process intends to do, not which command should be selected after reading the current state. Decisions such as Add / Update / Delete / Noop based on current state should be pushed into the restored Aggregate, not decided from the same Aggregate's Read Model.
|
|
169
463
|
|
|
170
|
-
|
|
464
|
+
| Criteria | Judgment |
|
|
465
|
+
|----------|----------|
|
|
466
|
+
| Check existence or scope of other Aggregates or external facts, then pass resolved facts to the command | OK |
|
|
467
|
+
| Read the same Aggregate's Read Model to choose the command type | REJECT |
|
|
468
|
+
| The UseCase decides "update if it exists, add if it does not" from Query results | REJECT. Send an intent command such as Set / Attach / Upsert to the Aggregate |
|
|
469
|
+
| Aggregate or AggregateAdapter decides existence and transition validity from restored state | OK |
|
|
470
|
+
| EventHandler or UseCase suppresses duplicate commands with a pre-query even though the Aggregate could ignore them idempotently | REJECT. Protect with Aggregate state transitions |
|
|
171
471
|
|
|
172
|
-
|
|
472
|
+
```kotlin
|
|
473
|
+
// NG - Query result chooses the command type
|
|
474
|
+
if (readService.exists(orderId)) {
|
|
475
|
+
commandGateway.send(UpdateOrderCommand(orderId, value))
|
|
476
|
+
} else {
|
|
477
|
+
commandGateway.send(AddOrderCommand(orderId, value))
|
|
478
|
+
}
|
|
173
479
|
|
|
480
|
+
// OK - Send an intent command; the Aggregate decides from restored state
|
|
481
|
+
commandGateway.send(SetOrderValueCommand(orderId, value))
|
|
174
482
|
```
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
483
|
+
|
|
484
|
+
```kotlin
|
|
485
|
+
// API layer: structural validation
|
|
486
|
+
data class OrderPostRequest(
|
|
487
|
+
@field:NotBlank val customerId: String,
|
|
488
|
+
@field:NotNull val items: List<OrderItemRequest>
|
|
489
|
+
) {
|
|
490
|
+
init {
|
|
491
|
+
require(items.isNotEmpty()) { "An order must have at least one item" }
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// UseCase layer: business-rule validation by Read Model reference
|
|
496
|
+
@Service
|
|
497
|
+
class PlaceOrderUseCase(
|
|
498
|
+
private val commandGateway: CommandGateway,
|
|
499
|
+
private val customerRepository: CustomerRepository,
|
|
500
|
+
private val inventoryRepository: InventoryRepository
|
|
501
|
+
) {
|
|
502
|
+
fun execute(input: PlaceOrderInput): Mono<PlaceOrderOutput> {
|
|
503
|
+
return Mono.fromCallable {
|
|
504
|
+
// Customer existence check
|
|
505
|
+
customerRepository.findById(input.customerId)
|
|
506
|
+
?: throw CustomerNotFoundException("Customer does not exist")
|
|
507
|
+
// Inventory precheck
|
|
508
|
+
validateInventory(input.items)
|
|
509
|
+
// Command dispatch
|
|
510
|
+
val orderId = UUID.randomUUID().toString()
|
|
511
|
+
commandGateway.send<Any>(PlaceOrderCommand(orderId, input.customerId, input.items))
|
|
512
|
+
PlaceOrderOutput(orderId)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Domain layer: state-transition invariant
|
|
518
|
+
fun confirm(confirmedBy: String): OrderConfirmedEvent {
|
|
519
|
+
require(status == OrderStatus.PENDING) { "Cannot confirm in this state" }
|
|
520
|
+
return OrderConfirmedEvent(orderId, confirmedBy, LocalDateTime.now())
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
| Criteria | Judgment |
|
|
525
|
+
|----------|----------|
|
|
526
|
+
| Domain-layer validation exists in API layer | REJECT. State-transition rules belong in the domain |
|
|
527
|
+
| UseCase-layer validation exists in Controller | REJECT. Separate into UseCase layer |
|
|
528
|
+
| API-layer validation such as `@NotBlank` exists in domain | REJECT. Structural validation belongs in API layer |
|
|
529
|
+
|
|
530
|
+
## UseCase Layer: Orchestration
|
|
531
|
+
|
|
532
|
+
Place a UseCase layer between Controller and CommandGateway. The UseCase layer gathers facts that must be resolved at the boundary and normally sends one intent command. Subsequent state changes are driven by EventHandlers for committed events.
|
|
533
|
+
|
|
534
|
+
```
|
|
535
|
+
Controller -> UseCase -> CommandGateway -> Aggregate
|
|
536
|
+
|
|
|
537
|
+
QueryGateway / Repository (Read Model reference)
|
|
178
538
|
```
|
|
179
539
|
|
|
180
|
-
Cases
|
|
181
|
-
- Read Model
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
- External integration or multiple command dispatches
|
|
540
|
+
Cases that need a UseCase:
|
|
541
|
+
- Checking another Aggregate's Read Model or external facts before command dispatch
|
|
542
|
+
- Running multiple validations sequentially
|
|
543
|
+
- Waiting for eventual consistency after command dispatch only for a synchronous API contract
|
|
185
544
|
|
|
186
|
-
Cases
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
545
|
+
Cases that do not need a UseCase:
|
|
546
|
+
- A simple operation where the Controller sends one command and is done
|
|
547
|
+
- A simple read where the Controller queries the Query side and converts to a response
|
|
548
|
+
- An operation that only checks existence/scope of an existing resource and then sends one command
|
|
190
549
|
|
|
191
550
|
| Criteria | Judgment |
|
|
192
551
|
|----------|----------|
|
|
193
552
|
| Controller directly references Repository for validation | Separate into UseCase layer |
|
|
194
|
-
| UseCase depends on HTTP
|
|
195
|
-
| UseCase directly
|
|
196
|
-
| UseCase
|
|
197
|
-
| UseCase
|
|
553
|
+
| UseCase depends on HTTP request/response | REJECT. UseCase must be protocol-independent |
|
|
554
|
+
| UseCase directly changes Aggregate internal state | REJECT. Use CommandGateway |
|
|
555
|
+
| UseCase sends multiple commands sequentially for the same state transition | REJECT. Separate into EventHandlers for committed events |
|
|
556
|
+
| UseCase queries the same Aggregate's state to choose the command type | REJECT. Push the decision into the Aggregate |
|
|
557
|
+
| UseCase validates another Aggregate or external facts and passes resolved facts to one command | OK |
|
|
558
|
+
| EventHandler receives a committed event and sends a command to another Aggregate | OK |
|
|
559
|
+
| processStore / ProcessStore / operationProcess / completeStep stores projection completion or procedural progress | REJECT. Model it with Projection and EventHandlers |
|
|
560
|
+
| There is an explicit long-running business process, retry, compensation, or user-visible progress | Consider Saga / Process Manager |
|
|
561
|
+
| UseCase only thinly delegates to another query layer or command dispatch | Consider removing |
|
|
562
|
+
|
|
563
|
+
## Event-driven Chaining
|
|
564
|
+
|
|
565
|
+
In CQRS+ES, chains of state changes start from committed events. Application Services, UseCases, and Controllers must not synchronously control the order of multiple Aggregate changes by sending commands sequentially for the same state transition.
|
|
566
|
+
|
|
567
|
+
Basic shape:
|
|
568
|
+
|
|
569
|
+
```text
|
|
570
|
+
UseCase -> Command -> Aggregate -> Event
|
|
571
|
+
|
|
|
572
|
+
EventHandler -> Command -> another Aggregate
|
|
573
|
+
|
|
|
574
|
+
Projection -> Read Model
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
| Criteria | Judgment |
|
|
578
|
+
|----------|----------|
|
|
579
|
+
| UseCase sends command B immediately after command A for the same state transition | REJECT. Let an EventHandler receive A's event and send B |
|
|
580
|
+
| Another command is sent after `sendAndWait` returns to create consistency | REJECT. Separate into event chaining |
|
|
581
|
+
| A normal event from an existing Aggregate becomes the trigger for derived processing | OK |
|
|
582
|
+
| EventHandler receives a committed event and sends an idempotent command to another Aggregate | OK |
|
|
583
|
+
| Projection update and next-command dispatch are mixed in the same handler | REJECT. Separate Projection from EventHandler |
|
|
584
|
+
| There is contention, compensation, long-running retry, or user-visible progress | Consider Saga / Process Manager |
|
|
585
|
+
| processStore is created only to remember intermediate progress | REJECT. Split responsibilities into Aggregate events, Projections, or Saga |
|
|
198
586
|
|
|
199
587
|
## Projection Design
|
|
200
588
|
|
|
201
589
|
| Criteria | Judgment |
|
|
202
590
|
|----------|----------|
|
|
203
|
-
| Projection
|
|
204
|
-
| Projection references Write
|
|
205
|
-
|
|
|
206
|
-
|
|
|
591
|
+
| Projection dispatches commands | REJECT |
|
|
592
|
+
| Projection references Write Model | REJECT |
|
|
593
|
+
| One projection supports multiple use cases | Needs review |
|
|
594
|
+
| Cannot be rebuilt | REJECT |
|
|
595
|
+
|
|
596
|
+
Good projections:
|
|
597
|
+
- Optimized for a specific read use case
|
|
598
|
+
- Rebuildable idempotently from events
|
|
599
|
+
- Completely independent from Write Model
|
|
600
|
+
|
|
601
|
+
### Distinguishing Projections from EventHandlers for Side Effects
|
|
207
602
|
|
|
208
|
-
|
|
209
|
-
- Optimized for specific read use case
|
|
210
|
-
- Idempotently reconstructible from events
|
|
211
|
-
- Completely independent from Write model
|
|
603
|
+
Both use `@EventHandler`, but their responsibilities differ. Do not confuse them.
|
|
212
604
|
|
|
213
|
-
|
|
605
|
+
| Type | Responsibility | Does | Does Not Do |
|
|
606
|
+
|------|----------------|------|-------------|
|
|
607
|
+
| Projection | Read Model update | Save/update Entity | Dispatch commands, call external APIs |
|
|
608
|
+
| EventHandler | Side effect | Dispatch commands to other Aggregates | Update Read Models |
|
|
214
609
|
|
|
215
|
-
|
|
610
|
+
```kotlin
|
|
611
|
+
// Projection: Read Model update only
|
|
612
|
+
@Component
|
|
613
|
+
class OrderProjection(private val orderRepository: OrderRepository) {
|
|
614
|
+
@EventHandler
|
|
615
|
+
fun on(event: OrderPlacedEvent) {
|
|
616
|
+
val entity = OrderEntity(
|
|
617
|
+
orderId = event.orderId,
|
|
618
|
+
customerId = event.customerId,
|
|
619
|
+
status = OrderStatus.PENDING
|
|
620
|
+
)
|
|
621
|
+
orderRepository.save(entity)
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
@EventHandler
|
|
625
|
+
fun on(event: OrderConfirmedEvent) {
|
|
626
|
+
orderRepository.findById(event.orderId).ifPresent { entity ->
|
|
627
|
+
entity.status = OrderStatus.CONFIRMED
|
|
628
|
+
orderRepository.save(entity)
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// EventHandler: side effect, command dispatch to another Aggregate
|
|
634
|
+
@Component
|
|
635
|
+
class InventoryReleaseHandler(private val commandGateway: CommandGateway) {
|
|
636
|
+
@EventHandler
|
|
637
|
+
fun on(event: OrderCancelledEvent) {
|
|
638
|
+
val command = ReleaseInventoryCommand(
|
|
639
|
+
productId = event.productId,
|
|
640
|
+
quantity = event.quantity
|
|
641
|
+
)
|
|
642
|
+
commandGateway.send<Any>(command)
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
| Criteria | Judgment |
|
|
648
|
+
|----------|----------|
|
|
649
|
+
| Projection uses CommandGateway | REJECT. Separate into EventHandler |
|
|
650
|
+
| EventHandler saves with Repository | REJECT. Separate into Projection |
|
|
651
|
+
| One class mixes Projection and EventHandler responsibilities | REJECT. Split classes |
|
|
652
|
+
|
|
653
|
+
### Starting External Processing
|
|
654
|
+
|
|
655
|
+
Starting external workers or asynchronous processing should be triggered by a domain event that an Aggregate has committed. An Application Service or Coordinator must not bundle command dispatch and external side effects in the same control flow for the same state transition.
|
|
216
656
|
|
|
217
657
|
| Criteria | Judgment |
|
|
218
658
|
|----------|----------|
|
|
219
|
-
| Application Service or Coordinator
|
|
220
|
-
| Aggregate emits an event that represents generation or processing start, and
|
|
221
|
-
| EventHandler
|
|
222
|
-
|
|
|
223
|
-
|
|
|
224
|
-
| Saga is used
|
|
659
|
+
| Application Service or Coordinator starts external processing immediately after command dispatch for the same state transition | REJECT. Separate into EventHandler for committed events |
|
|
660
|
+
| Aggregate emits an event that represents generation start or processing start, and EventHandler starts external processing | OK |
|
|
661
|
+
| EventHandler reports external-processing start failure back to the Aggregate with a failure command | OK |
|
|
662
|
+
| Input needed for external processing is represented by the event or stable IDs that can be reloaded | OK |
|
|
663
|
+
| External-processing input exists only in local variables during command processing | REJECT. Move to events or reloadable references |
|
|
664
|
+
| Saga is used for simple external processing with no contention or compensation | REJECT. EventHandler is enough |
|
|
225
665
|
|
|
226
|
-
## Query
|
|
666
|
+
## Query-side Design
|
|
227
667
|
|
|
228
|
-
Query side operates
|
|
668
|
+
The Query side operates as an event-driven PubSub model. Projections update Read Models with EventHandlers, and the Query side references those Read Models.
|
|
229
669
|
|
|
230
|
-
Event
|
|
670
|
+
Event delivery should be PubSub, through a message broker, to all instances. Do not rely on mechanisms that deliver only to the same instance unless delivery guarantees are confirmed.
|
|
231
671
|
|
|
232
|
-
- **Subscription Query** (
|
|
233
|
-
- **Subscribing event processor** (
|
|
672
|
+
- **Subscription Query** (for example Axon's `subscriptionQuery()`): a mechanism that returns change notifications for query results to the subscriber. Use it only when it is already adopted as infrastructure and delivery to subscribers is guaranteed. In systems based on tracking processors or trackers, do not introduce subscription query only for a feature implementation.
|
|
673
|
+
- **Subscribing event processor** (for example Axon's `SubscribingEventProcessor`): depends on direct subscription from the local event bus, so only the instance that published the event receives it. In distributed environments, projections on other instances are not updated. Configure PubSub delivery to all instances.
|
|
234
674
|
|
|
235
675
|
| Criteria | Judgment |
|
|
236
676
|
|----------|----------|
|
|
237
|
-
|
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
677
|
+
| Use of Subscription Query already adopted as infrastructure with confirmed delivery guarantee, such as Axon Server `subscriptionQuery()` | OK |
|
|
678
|
+
| Introducing Subscription Query only for a feature implementation | REJECT. Use the existing tracker / Read Model polling |
|
|
679
|
+
| Use of Subscription Query with unknown delivery guarantee, such as Axon `subscriptionQuery()` | REJECT. Use the existing tracker / Read Model polling |
|
|
680
|
+
| Use of Subscribing event processor, such as Axon `SubscribingEventProcessor` | REJECT. Local delivery only; other instances are not updated in distributed environments |
|
|
681
|
+
| Controller directly references Repository | REJECT. Go through UseCase layer |
|
|
682
|
+
| Query side references Command Model | REJECT |
|
|
683
|
+
| QueryHandler dispatches commands | REJECT |
|
|
242
684
|
| Query-side service or handler saves, deletes, or calls external APIs | REJECT |
|
|
243
|
-
| Command and Query
|
|
244
|
-
| Query side
|
|
685
|
+
| Command and Query are mixed in the same service | REJECT. Separate responsibilities and naming |
|
|
686
|
+
| Query side or ReadService reads Query results to choose the command type for the same Aggregate | REJECT |
|
|
687
|
+
| Query side checks existence/scope of another Aggregate or external facts, and the caller dispatches one command | OK |
|
|
245
688
|
|
|
246
689
|
### QueryHandler and ApplicationService Naming
|
|
247
690
|
|
|
248
|
-
In CQRS, the component that receives
|
|
691
|
+
In CQRS, the component that receives queries is called a QueryHandler, and the entry point that sends queries is treated as QueryGateway / QueryBus. A facade called from a Controller to coordinate read use cases should be named ApplicationService or ReadService so it is not confused with QueryHandler.
|
|
249
692
|
|
|
250
693
|
| Criteria | Judgment |
|
|
251
694
|
|----------|----------|
|
|
252
|
-
| Receives a Query,
|
|
253
|
-
| Coordinates multiple Queries, authorization
|
|
254
|
-
|
|
|
255
|
-
| QueryHandler knows HTTP
|
|
256
|
-
|
|
|
695
|
+
| Receives a Query, references Read Model, and returns a query-result type | QueryHandler |
|
|
696
|
+
| Coordinates multiple Queries, authorization boundary, paging, and DTO assembly from Controller | ApplicationService or ReadService |
|
|
697
|
+
| A class that only sends queries or coordinates reads is called QueryService | Warning. Easy to confuse with QueryHandler |
|
|
698
|
+
| QueryHandler knows HTTP request/response or Controller-specific error conversion | REJECT |
|
|
699
|
+
| Adds a simple read wrapper with no additional decision | Consider removing. Controller may call QueryGateway directly |
|
|
257
700
|
|
|
258
701
|
Types between layers:
|
|
259
|
-
- `application/query/` -
|
|
260
|
-
- `adapter/protocol/` - REST response
|
|
261
|
-
- QueryHandler returns application
|
|
702
|
+
- `application/query/` - query-result type, for example `OrderDetail`
|
|
703
|
+
- `adapter/protocol/` - REST response type, for example `OrderDetailResponse`
|
|
704
|
+
- QueryHandler returns application-layer types; Controller converts them to adapter-layer types
|
|
262
705
|
|
|
263
706
|
```kotlin
|
|
264
707
|
// application/query/OrderDetail.kt
|
|
@@ -275,14 +718,14 @@ data class OrderDetailResponse(...) {
|
|
|
275
718
|
}
|
|
276
719
|
}
|
|
277
720
|
|
|
278
|
-
// QueryHandler - returns application
|
|
721
|
+
// QueryHandler - returns application-layer type
|
|
279
722
|
@QueryHandler
|
|
280
723
|
fun handle(query: GetOrderDetailQuery): OrderDetail? {
|
|
281
724
|
val entity = repository.findById(query.id) ?: return null
|
|
282
725
|
return OrderDetail(...)
|
|
283
726
|
}
|
|
284
727
|
|
|
285
|
-
// Controller -
|
|
728
|
+
// Controller - simple reference can return synchronously
|
|
286
729
|
@GetMapping("/{id}")
|
|
287
730
|
fun getById(@PathVariable id: String): ResponseEntity<OrderDetailResponse> {
|
|
288
731
|
val detail = queryGateway.query(
|
|
@@ -296,45 +739,57 @@ fun getById(@PathVariable id: String): ResponseEntity<OrderDetailResponse> {
|
|
|
296
739
|
|
|
297
740
|
Structure:
|
|
298
741
|
```
|
|
299
|
-
Controller (adapter)
|
|
300
|
-
|
|
742
|
+
Controller (adapter) -> QueryGateway -> QueryHandler (application) -> Repository
|
|
743
|
+
| |
|
|
301
744
|
Response.from(detail) OrderDetail
|
|
302
745
|
|
|
303
746
|
Event flow (PubSub):
|
|
304
|
-
Aggregate
|
|
305
|
-
|
|
306
|
-
QueryHandler
|
|
747
|
+
Aggregate -> Event Bus -> Projection(@EventHandler) -> Repository(Read Model)
|
|
748
|
+
^
|
|
749
|
+
QueryHandler references this
|
|
307
750
|
```
|
|
308
751
|
|
|
309
|
-
###
|
|
752
|
+
### Asynchronous Callbacks and Concurrency Control
|
|
310
753
|
|
|
311
|
-
|
|
754
|
+
Design asynchronous completion callbacks assuming duplicates, delays, and ordering inversions. Protect with Aggregate state transitions and command idempotency, not Controller or single-process locks.
|
|
312
755
|
|
|
313
756
|
| Criteria | Judgment |
|
|
314
757
|
|----------|----------|
|
|
315
|
-
| Controller or application-process
|
|
316
|
-
|
|
|
317
|
-
| Aggregate verifies callback attempt
|
|
318
|
-
|
|
|
758
|
+
| Prevent duplicate callbacks with Controller or application-process locks | REJECT. Does not work across instances |
|
|
759
|
+
| Determine processing state from Aggregate state | OK |
|
|
760
|
+
| Aggregate verifies callback attempt ID or generation | OK |
|
|
761
|
+
| Idempotently ignore old or duplicate callbacks by state transition | OK |
|
|
319
762
|
| Concurrency control is duplicated across Controller, UseCase, and Aggregate | REJECT |
|
|
320
763
|
|
|
321
764
|
## Eventual Consistency
|
|
322
765
|
|
|
323
|
-
|
|
766
|
+
Wait for Projection updates after command dispatch only when there is an explicit synchronous contract to return the updated Read Model in the same API response. If the client can keep the input values or generated ID, the server should not wait; Read Model convergence is handled through normal read APIs.
|
|
324
767
|
|
|
325
768
|
| Criteria | Judgment |
|
|
326
769
|
|----------|----------|
|
|
327
|
-
|
|
|
328
|
-
|
|
|
329
|
-
|
|
|
330
|
-
|
|
|
770
|
+
| No explicit contract to return the updated Read Model in the same response | Do not wait |
|
|
771
|
+
| The client or caller can keep the command input or generated ID | Do not wait |
|
|
772
|
+
| There is infrastructure guaranteeing Projection update notification delivery to the waiting process | OK. Notification-driven waiting is acceptable |
|
|
773
|
+
| Existing infrastructure such as Subscription Query confirms update notifications reach subscribers | OK |
|
|
774
|
+
| Kafka or similar guarantees destination, redelivery, and missing-message handling operationally | OK |
|
|
775
|
+
| Subscription Query or event notification destination assumes single process/single instance, or guarantee is unknown | REJECT. Use the existing tracker / Read Model polling |
|
|
776
|
+
| `Thread.sleep` or equivalent blocks request threads while waiting for Projection updates | REJECT. Causes thread starvation under high concurrency |
|
|
777
|
+
| `delayedExecutor` / `CompletableFuture` is used to implement custom Projection-wait retry | REJECT. Use a reactive HTTP stack or the existing tracker |
|
|
778
|
+
| processStore / ProcessStore / materialStore / completeStep manages Projection update progress | REJECT. Projections should update idempotently from events |
|
|
779
|
+
| Updated state must be returned in the same HTTP response | Wait non-blockingly on a reactive HTTP stack |
|
|
780
|
+
| Same response does not need to wait | `202 Accepted` plus frontend long polling, normal polling, SSE, or WebSocket |
|
|
781
|
+
| UI expects immediate update | Frontend polling, SSE, or WebSocket. Server-side waiting only for a synchronous API contract |
|
|
782
|
+
| Consistency delay exceeds acceptable range | Reconsider architecture |
|
|
783
|
+
| Compensation transaction is undefined | Require failure-scenario review |
|
|
331
784
|
|
|
332
785
|
### Reactive Polling
|
|
333
786
|
|
|
334
|
-
|
|
787
|
+
Reactive polling is the pattern of dispatching a command and then waiting non-blockingly for Projection update completion. It does not occupy a request thread and is not a synchronous `while` loop with `Thread.sleep`.
|
|
788
|
+
|
|
789
|
+
The polling condition should be checked by re-fetching the Read Model and testing whether it has reached the expected state, not by event notifications. Re-fetch at a fixed interval until the condition is met, timeout occurs, or max attempts are reached.
|
|
335
790
|
|
|
336
791
|
```kotlin
|
|
337
|
-
// UseCase:
|
|
792
|
+
// UseCase: command dispatch -> wait for completion with polling
|
|
338
793
|
fun execute(input: PlaceOrderInput): Mono<PlaceOrderOutput> {
|
|
339
794
|
val orderId = UUID.randomUUID().toString()
|
|
340
795
|
return Mono.fromCallable { validatePreConditions(input) }
|
|
@@ -359,40 +814,56 @@ private fun pollForCompletion(orderId: String): Mono<Void> {
|
|
|
359
814
|
}
|
|
360
815
|
```
|
|
361
816
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
817
|
+
Avoid blocking waits:
|
|
818
|
+
|
|
819
|
+
```kotlin
|
|
820
|
+
// NG - Occupies request threads and causes thread starvation under load
|
|
821
|
+
while (Instant.now().isBefore(deadline)) {
|
|
822
|
+
val order = orderRepository.findById(orderId).orElse(null)
|
|
823
|
+
if (order?.status == OrderStatus.CONFIRMED) return PlaceOrderOutput(orderId)
|
|
824
|
+
Thread.sleep(100)
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// OK - If the same response must wait, put it on reactive waiting
|
|
828
|
+
return pollForCompletion(orderId).thenReturn(PlaceOrderOutput(orderId))
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
Cases where polling is appropriate:
|
|
832
|
+
- The response should not return until Saga completion
|
|
833
|
+
- The command dispatch creates a resource ID and the response needs to return it
|
|
365
834
|
|
|
366
|
-
|
|
367
|
-
-
|
|
368
|
-
- UI does not
|
|
835
|
+
Cases where polling is unnecessary:
|
|
836
|
+
- A simple operation where command dispatch alone completes the work and the result is not waited on
|
|
837
|
+
- The UI does not need real-time update
|
|
838
|
+
|
|
839
|
+
If the server does not wait, return `202 Accepted` with a tracking ID after accepting the command, and let the frontend use long polling or normal polling on the read API. SSE or WebSocket can also be considered if the user experience requires immediacy.
|
|
369
840
|
|
|
370
841
|
## Saga vs EventHandler
|
|
371
842
|
|
|
372
|
-
Saga
|
|
843
|
+
Use Saga only for operations involving contention between multiple Aggregates.
|
|
373
844
|
|
|
374
|
-
Cases
|
|
845
|
+
Cases that need Saga:
|
|
375
846
|
```
|
|
376
|
-
|
|
377
|
-
Example:
|
|
847
|
+
Multiple actors compete for the same resource
|
|
848
|
+
Example: inventory reservation, where 10 people order the same product at the same time
|
|
378
849
|
|
|
379
850
|
OrderPlacedEvent
|
|
380
|
-
|
|
381
|
-
ReserveInventoryCommand
|
|
382
|
-
|
|
383
|
-
InventoryReservedEvent
|
|
384
|
-
InventoryReservationFailedEvent
|
|
851
|
+
-> InventoryReservationSaga
|
|
852
|
+
ReserveInventoryCommand -> Inventory Aggregate (serializes concurrency)
|
|
853
|
+
->
|
|
854
|
+
InventoryReservedEvent -> ConfirmOrderCommand
|
|
855
|
+
InventoryReservationFailedEvent -> CancelOrderCommand
|
|
385
856
|
```
|
|
386
857
|
|
|
387
|
-
Cases
|
|
858
|
+
Cases that do not need Saga:
|
|
388
859
|
```
|
|
389
|
-
|
|
390
|
-
Example:
|
|
860
|
+
Operation with no contention
|
|
861
|
+
Example: releasing inventory on order cancellation
|
|
391
862
|
|
|
392
863
|
OrderCancelledEvent
|
|
393
|
-
|
|
864
|
+
-> InventoryReleaseHandler (simple EventHandler)
|
|
394
865
|
ReleaseInventoryCommand
|
|
395
|
-
|
|
866
|
+
->
|
|
396
867
|
InventoryReleasedEvent
|
|
397
868
|
```
|
|
398
869
|
|
|
@@ -401,35 +872,35 @@ Decision criteria:
|
|
|
401
872
|
| Situation | Saga | EventHandler |
|
|
402
873
|
|-----------|------|--------------|
|
|
403
874
|
| Resource contention exists | Use | - |
|
|
404
|
-
|
|
|
405
|
-
|
|
|
406
|
-
| Retry on failure
|
|
875
|
+
| Compensation transaction is needed | Use | - |
|
|
876
|
+
| Simple integration with no contention | - | Use |
|
|
877
|
+
| Retry is enough on failure | - | Use |
|
|
407
878
|
|
|
408
879
|
Anti-pattern:
|
|
409
880
|
```kotlin
|
|
410
881
|
// NG - Using Saga for lifecycle management
|
|
411
882
|
@Saga
|
|
412
883
|
class OrderLifecycleSaga {
|
|
413
|
-
//
|
|
414
|
-
// PLACED
|
|
884
|
+
// Tracks every order state transition
|
|
885
|
+
// PLACED -> CONFIRMED -> SHIPPED -> DELIVERED
|
|
415
886
|
}
|
|
416
887
|
|
|
417
|
-
// OK - Saga only for operations
|
|
888
|
+
// OK - Saga only for operations that need eventual consistency
|
|
418
889
|
@Saga
|
|
419
890
|
class InventoryReservationSaga {
|
|
420
|
-
//
|
|
891
|
+
// Concurrency control for inventory reservation only
|
|
421
892
|
}
|
|
422
893
|
```
|
|
423
894
|
|
|
424
|
-
Saga is not a lifecycle
|
|
895
|
+
Saga is not a lifecycle-management tool. Create it for an operation that needs eventual consistency.
|
|
425
896
|
|
|
426
|
-
## Exception vs Event
|
|
897
|
+
## Exception vs Event: Failure Choice
|
|
427
898
|
|
|
428
|
-
Failures not
|
|
899
|
+
Failures that do not require audit are exceptions; failures that require audit are events.
|
|
429
900
|
|
|
430
|
-
Exception approach
|
|
901
|
+
Exception approach, recommended in most cases:
|
|
431
902
|
```kotlin
|
|
432
|
-
// Domain model:
|
|
903
|
+
// Domain model: throw an exception on validation failure
|
|
433
904
|
fun reserveInventory(orderId: String, quantity: Int): InventoryReservedEvent {
|
|
434
905
|
if (availableQuantity < quantity) {
|
|
435
906
|
throw InsufficientInventoryException("Insufficient inventory")
|
|
@@ -437,7 +908,7 @@ fun reserveInventory(orderId: String, quantity: Int): InventoryReservedEvent {
|
|
|
437
908
|
return InventoryReservedEvent(productId, orderId, quantity)
|
|
438
909
|
}
|
|
439
910
|
|
|
440
|
-
// Saga:
|
|
911
|
+
// Saga: catch with exceptionally and issue compensation action
|
|
441
912
|
commandGateway.send<Any>(command)
|
|
442
913
|
.exceptionally { ex ->
|
|
443
914
|
commandGateway.send<Any>(CancelOrderCommand(
|
|
@@ -448,7 +919,7 @@ commandGateway.send<Any>(command)
|
|
|
448
919
|
}
|
|
449
920
|
```
|
|
450
921
|
|
|
451
|
-
Event approach
|
|
922
|
+
Event approach, rare cases:
|
|
452
923
|
```kotlin
|
|
453
924
|
// Only when audit is required
|
|
454
925
|
data class PaymentFailedEvent(
|
|
@@ -462,37 +933,37 @@ Decision criteria:
|
|
|
462
933
|
|
|
463
934
|
| Question | Exception | Event |
|
|
464
935
|
|----------|-----------|-------|
|
|
465
|
-
|
|
|
466
|
-
|
|
|
467
|
-
|
|
|
468
|
-
| Is there value in
|
|
936
|
+
| Does this failure need to be reviewed later? | No | Yes |
|
|
937
|
+
| Is a record required by regulation or compliance? | No | Yes |
|
|
938
|
+
| Is only the Saga interested in the failure? | Yes | No |
|
|
939
|
+
| Is there value in storing it in the Event Store? | No | Yes |
|
|
469
940
|
|
|
470
|
-
Default
|
|
941
|
+
Default to the exception approach. Consider events only when there is an audit requirement.
|
|
471
942
|
|
|
472
|
-
## Abstraction
|
|
943
|
+
## Abstraction-level Evaluation
|
|
473
944
|
|
|
474
|
-
**
|
|
945
|
+
**Detecting bloated conditional branches**
|
|
475
946
|
|
|
476
947
|
| Pattern | Judgment |
|
|
477
948
|
|---------|----------|
|
|
478
|
-
| Same if-else pattern in 3
|
|
479
|
-
| switch/case
|
|
480
|
-
|
|
|
481
|
-
|
|
|
949
|
+
| Same if-else pattern appears in 3 or more places | Abstract with polymorphism -> REJECT |
|
|
950
|
+
| switch/case has 5 or more branches | Consider Strategy/Map pattern |
|
|
951
|
+
| Branching by event type grows repeatedly | Split EventHandlers -> REJECT |
|
|
952
|
+
| State branching inside Aggregate is complex | Consider State Pattern |
|
|
482
953
|
|
|
483
|
-
**
|
|
954
|
+
**Detecting mismatched abstraction levels**
|
|
484
955
|
|
|
485
956
|
| Pattern | Problem | Fix |
|
|
486
957
|
|---------|---------|-----|
|
|
487
|
-
| DB operation details in CommandHandler | Responsibility violation | Separate
|
|
958
|
+
| DB operation details in CommandHandler | Responsibility violation | Separate into Repository layer |
|
|
488
959
|
| Business logic in EventHandler | Responsibility violation | Extract to domain service |
|
|
489
|
-
| Persistence in Aggregate | Layer violation |
|
|
960
|
+
| Persistence processing in Aggregate | Layer violation | Move behind EventStore |
|
|
490
961
|
| Calculation logic in Projection | Hard to maintain | Extract to dedicated service |
|
|
491
962
|
|
|
492
963
|
Good abstraction examples:
|
|
493
964
|
|
|
494
965
|
```kotlin
|
|
495
|
-
// Event
|
|
966
|
+
// Event-type branching grows repeatedly (NG)
|
|
496
967
|
@EventHandler
|
|
497
968
|
fun on(event: DomainEvent) {
|
|
498
969
|
when (event) {
|
|
@@ -503,7 +974,7 @@ fun on(event: DomainEvent) {
|
|
|
503
974
|
}
|
|
504
975
|
}
|
|
505
976
|
|
|
506
|
-
//
|
|
977
|
+
// Split by event (OK)
|
|
507
978
|
@EventHandler
|
|
508
979
|
fun on(event: OrderPlacedEvent) { ... }
|
|
509
980
|
|
|
@@ -520,11 +991,11 @@ fun process(command: ProcessCommand) {
|
|
|
520
991
|
when (status) {
|
|
521
992
|
PENDING -> if (command.type == "approve") { ... } else if (command.type == "reject") { ... }
|
|
522
993
|
APPROVED -> if (command.type == "ship") { ... }
|
|
523
|
-
// ...
|
|
994
|
+
// ...complexity grows
|
|
524
995
|
}
|
|
525
996
|
}
|
|
526
997
|
|
|
527
|
-
//
|
|
998
|
+
// State Pattern (OK)
|
|
528
999
|
sealed class OrderState {
|
|
529
1000
|
abstract fun handle(command: ProcessCommand): List<DomainEvent>
|
|
530
1001
|
}
|
|
@@ -539,37 +1010,37 @@ class PendingState : OrderState() {
|
|
|
539
1010
|
|
|
540
1011
|
## Anti-pattern Detection
|
|
541
1012
|
|
|
542
|
-
|
|
1013
|
+
Reject when any of the following is found:
|
|
543
1014
|
|
|
544
1015
|
| Anti-pattern | Problem |
|
|
545
1016
|
|--------------|---------|
|
|
546
|
-
| CRUD
|
|
547
|
-
| Anemic Domain Model | Aggregate is
|
|
548
|
-
| Event Soup | Meaningless events
|
|
1017
|
+
| CRUD disguise | Only mimics the shape of CQRS while implementing CRUD |
|
|
1018
|
+
| Anemic Domain Model | Aggregate is only a data structure |
|
|
1019
|
+
| Event Soup | Meaningless events are emitted repeatedly |
|
|
549
1020
|
| Temporal Coupling | Implicit dependency on event order |
|
|
550
1021
|
| Missing Events | Important domain events are missing |
|
|
551
|
-
| God Aggregate |
|
|
1022
|
+
| God Aggregate | One Aggregate concentrates all responsibilities |
|
|
552
1023
|
|
|
553
1024
|
## Test Strategy
|
|
554
1025
|
|
|
555
|
-
Separate test
|
|
1026
|
+
Separate test strategy by layer.
|
|
556
1027
|
|
|
557
|
-
Test
|
|
1028
|
+
Test pyramid:
|
|
558
1029
|
```
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1030
|
+
+-------------+
|
|
1031
|
+
| E2E Test | <- Few: full-flow confirmation
|
|
1032
|
+
+-------------+
|
|
1033
|
+
| Integration | <- Command -> Event -> Projection -> Query integration
|
|
1034
|
+
+-------------+
|
|
1035
|
+
| Unit Test | <- Many: each layer isolated
|
|
1036
|
+
+-------------+
|
|
566
1037
|
```
|
|
567
1038
|
|
|
568
1039
|
Command side (Aggregate):
|
|
569
1040
|
```kotlin
|
|
570
1041
|
// Using AggregateTestFixture
|
|
571
1042
|
@Test
|
|
572
|
-
fun `
|
|
1043
|
+
fun `confirmation command emits event`() {
|
|
573
1044
|
fixture
|
|
574
1045
|
.given(OrderPlacedEvent(...))
|
|
575
1046
|
.`when`(ConfirmOrderCommand(orderId, confirmedBy))
|
|
@@ -582,11 +1053,11 @@ Query side:
|
|
|
582
1053
|
```kotlin
|
|
583
1054
|
// Direct Read Model setup + QueryGateway
|
|
584
1055
|
@Test
|
|
585
|
-
fun `
|
|
586
|
-
// Given:
|
|
1056
|
+
fun `order detail is returned`() {
|
|
1057
|
+
// Given: set up Read Model directly
|
|
587
1058
|
orderRepository.save(OrderEntity(...))
|
|
588
1059
|
|
|
589
|
-
// When:
|
|
1060
|
+
// When: execute query through QueryGateway
|
|
590
1061
|
val detail = queryGateway.query(GetOrderDetailQuery(orderId), ...).join()
|
|
591
1062
|
|
|
592
1063
|
// Then
|
|
@@ -596,41 +1067,102 @@ fun `can get order details`() {
|
|
|
596
1067
|
|
|
597
1068
|
Checklist:
|
|
598
1069
|
|
|
599
|
-
|
|
|
600
|
-
|
|
601
|
-
| Aggregate tests verify events not state | Required |
|
|
602
|
-
| Query
|
|
603
|
-
| Integration tests
|
|
1070
|
+
| Perspective | Judgment |
|
|
1071
|
+
|-------------|----------|
|
|
1072
|
+
| Aggregate tests verify events, not state | Required |
|
|
1073
|
+
| Query-side tests do not create data through commands | Recommended |
|
|
1074
|
+
| Integration tests account for Axon asynchronous processing | Required |
|
|
1075
|
+
|
|
1076
|
+
## Value Object Design
|
|
1077
|
+
|
|
1078
|
+
Use value objects as Aggregate and event components. Do not rely only on primitive types such as String or Int.
|
|
604
1079
|
|
|
605
|
-
|
|
1080
|
+
```kotlin
|
|
1081
|
+
// NG - primitives only
|
|
1082
|
+
data class OrderPlacedEvent(
|
|
1083
|
+
val orderId: String,
|
|
1084
|
+
val categoryId: String, // Just a string
|
|
1085
|
+
val from: LocalDateTime, // Meaning is unclear
|
|
1086
|
+
val to: LocalDateTime
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
// OK - Value objects express meaning and constraints
|
|
1090
|
+
data class OrderPlacedEvent(
|
|
1091
|
+
val orderId: String,
|
|
1092
|
+
val categoryId: CategoryId,
|
|
1093
|
+
val period: OrderPeriod
|
|
1094
|
+
)
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
Value object design rules:
|
|
1098
|
+
- Use `data class` to auto-generate equals/hashCode, comparing by value
|
|
1099
|
+
- Guarantee invariants in `init` blocks, validating at creation
|
|
1100
|
+
- Do not include domain logic such as state transitions; keep them as pure data holders
|
|
1101
|
+
- Use `@JsonValue` to control serialization
|
|
1102
|
+
|
|
1103
|
+
```kotlin
|
|
1104
|
+
// ID type: single-value wrapper
|
|
1105
|
+
data class CategoryId(@get:JsonValue val value: String) {
|
|
1106
|
+
init {
|
|
1107
|
+
require(value.isNotBlank()) { "Category ID cannot be blank" }
|
|
1108
|
+
}
|
|
1109
|
+
override fun toString(): String = value
|
|
1110
|
+
}
|
|
606
1111
|
|
|
607
|
-
|
|
1112
|
+
// Range type: invariant over multiple values
|
|
1113
|
+
data class OrderPeriod(
|
|
1114
|
+
val from: LocalDateTime,
|
|
1115
|
+
val to: LocalDateTime
|
|
1116
|
+
) {
|
|
1117
|
+
init {
|
|
1118
|
+
require(!to.isBefore(from)) { "End date must be on or after start date" }
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Metadata type: associated data in event payload
|
|
1123
|
+
data class ApprovalInfo(
|
|
1124
|
+
val approvedBy: String,
|
|
1125
|
+
val approvalTime: LocalDateTime
|
|
1126
|
+
)
|
|
1127
|
+
```
|
|
608
1128
|
|
|
609
|
-
|
|
1129
|
+
| Criteria | Judgment |
|
|
1130
|
+
|----------|----------|
|
|
1131
|
+
| Reusing IDs as raw String | Consider value object |
|
|
1132
|
+
| Same field combination, such as from/to, appears in multiple places | Extract value object |
|
|
1133
|
+
| Value object contains business logic such as state transitions | REJECT. Aggregate responsibility |
|
|
1134
|
+
| No `init` block to guarantee invariants | REJECT |
|
|
1135
|
+
|
|
1136
|
+
## Master Data, Settings, and CRUD Use
|
|
610
1137
|
|
|
611
|
-
|
|
1138
|
+
Even inside a CQRS+ES system, not everything needs to be event-sourced. Simple master data, reference data, managed settings, and allowlists are often simpler and easier to maintain as normal CRUD.
|
|
612
1139
|
|
|
613
|
-
|
|
614
|
-
|--------|-----------|---------------|
|
|
615
|
-
| Business requirements | Just "manage X" with no special mentions | Specific business rules or constraints |
|
|
616
|
-
| Logic evolution | Simple reference/update, no foreseeable complexity | State transitions or lifecycle may grow complex |
|
|
617
|
-
| Change history / audit | No need to track "who changed what when" | Change history or audit trail required |
|
|
618
|
-
| Domain events | Changes don't affect other aggregates or processes | Changes trigger downstream processes |
|
|
619
|
-
| Consistency scope | Self-contained, no cross-aggregate consistency needed | Must maintain consistency with other aggregates |
|
|
620
|
-
| Point-in-time queries | No "what was the state at time T" queries | Point-in-time queries required |
|
|
1140
|
+
Do not mechanically decide "master data means CRUD". The more the following criteria apply, the more suitable CRUD is. Conversely, if explicit requirements match CQRS+ES adoption criteria, consider CQRS+ES.
|
|
621
1141
|
|
|
622
|
-
**
|
|
623
|
-
- Code masters such as prefecture/country codes
|
|
624
|
-
- Classification masters such as categories and tags
|
|
625
|
-
- Configuration values, constant tables
|
|
1142
|
+
**Criteria for deciding CRUD is enough:**
|
|
626
1143
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1144
|
+
| Perspective | CRUD-leaning | CQRS+ES-leaning |
|
|
1145
|
+
|-------------|--------------|-----------------|
|
|
1146
|
+
| Business requirement | Around "manage X" with no special mention | Specific business rules or constraints exist |
|
|
1147
|
+
| Logic evolution | Simple reference/update completes it, unlikely to evolve | State transitions or lifecycle can become complex |
|
|
1148
|
+
| Change history/audit | No need to track who changed what and when | Need change-history reference or audit evidence |
|
|
1149
|
+
| Domain events | This change does not affect other Aggregates or processes | Change triggers downstream processes |
|
|
1150
|
+
| Consistency scope | Self-contained, no need for consistency with other Aggregates | Needs consistency with other Aggregates |
|
|
1151
|
+
| Point-in-time reference | No question asks for "state at a past point" | Point-in-time queries are needed |
|
|
1152
|
+
|
|
1153
|
+
**Typical CRUD targets:**
|
|
1154
|
+
- Prefecture and country-code master data
|
|
1155
|
+
- Category and tag classification master data
|
|
1156
|
+
- Settings and constant tables
|
|
1157
|
+
- Current-value managed settings such as IP allowlists, feature flags, and notification settings
|
|
1158
|
+
|
|
1159
|
+
**Examples where CQRS+ES can be justified:**
|
|
1160
|
+
- Product master data where price-change history must be tracked
|
|
1161
|
+
- Organization master data where changes trigger permission recalculation
|
|
1162
|
+
- Customer/vendor master data with credit-review state transitions
|
|
631
1163
|
|
|
632
1164
|
```kotlin
|
|
633
|
-
// CRUD is
|
|
1165
|
+
// CRUD is enough: simple category master
|
|
634
1166
|
@Entity
|
|
635
1167
|
data class Category(
|
|
636
1168
|
@Id val categoryId: String,
|
|
@@ -638,7 +1170,7 @@ data class Category(
|
|
|
638
1170
|
val displayOrder: Int
|
|
639
1171
|
)
|
|
640
1172
|
|
|
641
|
-
// CQRS+ES is appropriate:
|
|
1173
|
+
// CQRS+ES is appropriate: product requiring price-change history
|
|
642
1174
|
data class Product(
|
|
643
1175
|
val productId: String,
|
|
644
1176
|
val currentPrice: Money
|
|
@@ -650,12 +1182,12 @@ data class Product(
|
|
|
650
1182
|
}
|
|
651
1183
|
```
|
|
652
1184
|
|
|
653
|
-
|
|
1185
|
+
When implementing with CRUD, other Aggregates in a CQRS+ES system should still reference it by ID. It is the same principle that CRUD entities must not directly reference Aggregate internal state.
|
|
654
1186
|
|
|
655
1187
|
## Infrastructure Layer
|
|
656
1188
|
|
|
657
|
-
|
|
658
|
-
- Is event
|
|
659
|
-
- Does messaging infrastructure
|
|
660
|
-
- Is snapshot strategy defined?
|
|
661
|
-
- Is event serialization format appropriate?
|
|
1189
|
+
Checklist:
|
|
1190
|
+
- Is the event-store choice appropriate?
|
|
1191
|
+
- Does the messaging infrastructure satisfy requirements?
|
|
1192
|
+
- Is the snapshot strategy defined?
|
|
1193
|
+
- Is the event serialization format appropriate?
|