eva4j 1.0.16 → 1.0.18
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/AGENTS.md +220 -5
- package/DOMAIN_YAML_GUIDE.md +188 -3
- package/FUTURE_FEATURES.md +33 -52
- package/QUICK_REFERENCE.md +8 -4
- package/bin/eva4j.js +70 -2
- package/config/defaults.json +1 -0
- package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
- package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
- package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
- package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
- package/docs/commands/EVALUATE_SYSTEM.md +290 -10
- package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
- package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
- package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
- package/docs/commands/INDEX.md +27 -3
- package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
- package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
- package/docs/prototype/system/RISKS.md +277 -0
- package/docs/prototype/system/customers.yaml +133 -0
- package/docs/prototype/system/inventory.yaml +109 -0
- package/docs/prototype/system/notifications.yaml +131 -0
- package/docs/prototype/system/orders.yaml +241 -0
- package/docs/prototype/system/payments.yaml +256 -0
- package/docs/prototype/system/products.yaml +168 -0
- package/docs/prototype/system/system.yaml +269 -0
- package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
- package/examples/domain-events.yaml +26 -0
- package/examples/domain-read-models.yaml +113 -0
- package/examples/system/customer.yaml +89 -0
- package/examples/system/orders.yaml +119 -0
- package/examples/system/product.yaml +27 -0
- package/examples/system/system.yaml +80 -0
- package/package.json +1 -1
- package/read-model-spec.md +664 -0
- package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
- package/src/agents/design-gap-analyst.agent.md +383 -0
- package/src/agents/design-reviewer-temporal.agent.md +412 -0
- package/src/agents/design-reviewer.agent.md +34 -5
- package/src/agents/implement-use-cases.prompt.md +179 -0
- package/src/agents/ux-gap-analyst.agent.md +412 -0
- package/src/commands/add-rabbitmq-client.js +261 -0
- package/src/commands/add-temporal-client.js +22 -2
- package/src/commands/build.js +267 -11
- package/src/commands/evaluate-system.js +700 -13
- package/src/commands/generate-entities.js +560 -24
- package/src/commands/generate-http-exchange.js +3 -0
- package/src/commands/generate-kafka-event.js +3 -0
- package/src/commands/generate-kafka-listener.js +3 -0
- package/src/commands/generate-rabbitmq-event.js +665 -0
- package/src/commands/generate-rabbitmq-listener.js +205 -0
- package/src/commands/generate-record.js +2 -2
- package/src/commands/generate-resource.js +4 -1
- package/src/commands/generate-temporal-activity.js +970 -33
- package/src/commands/generate-temporal-flow.js +98 -38
- package/src/commands/generate-temporal-system.js +708 -0
- package/src/commands/generate-usecase.js +4 -1
- package/src/skills/build-system-yaml/SKILL.md +343 -2
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +253 -26
- package/src/skills/build-system-yaml/references/module-spec.md +90 -9
- package/src/skills/build-system-yaml/references/system-yaml-spec.md +36 -0
- package/src/skills/build-temporal-system/SKILL.md +752 -0
- package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
- package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
- package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
- package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
- package/src/skills/implement-use-case/SKILL.md +350 -0
- package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
- package/src/skills/requirements-elicitation/SKILL.md +228 -0
- package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
- package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
- package/src/utils/bounded-context-diagram.js +844 -0
- package/src/utils/config-manager.js +4 -2
- package/src/utils/domain-validator.js +495 -17
- package/src/utils/naming.js +20 -0
- package/src/utils/system-validator.js +169 -11
- package/src/utils/system-yaml-parser.js +318 -0
- package/src/utils/temporal-validator.js +497 -0
- package/src/utils/validator.js +3 -1
- package/src/utils/yaml-to-entity.js +281 -9
- package/templates/aggregate/AggregateRepository.java.ejs +4 -0
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +8 -0
- package/templates/aggregate/AggregateRoot.java.ejs +38 -4
- package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
- package/templates/aggregate/JpaAggregateRoot.java.ejs +4 -4
- package/templates/aggregate/JpaEntity.java.ejs +2 -2
- package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
- package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
- package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
- package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
- package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
- package/templates/base/root/AGENTS.md.ejs +1 -1
- package/templates/crud/DeleteCommandHandler.java.ejs +19 -1
- package/templates/crud/EndpointsController.java.ejs +1 -1
- package/templates/crud/ScaffoldCommand.java.ejs +5 -2
- package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
- package/templates/crud/ScaffoldQuery.java.ejs +5 -2
- package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
- package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
- package/templates/crud/UpdateCommandHandler.java.ejs +53 -2
- package/templates/evaluate/report.html.ejs +1447 -90
- package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
- package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
- package/templates/ports/PortAclMapper.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +7 -22
- package/templates/ports/PortFeignClient.java.ejs +4 -0
- package/templates/ports/PortResponseDto.java.ejs +1 -1
- package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
- package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
- package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
- package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
- package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
- package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
- package/templates/read-model/ReadModelDomain.java.ejs +46 -0
- package/templates/read-model/ReadModelJpa.java.ejs +58 -0
- package/templates/read-model/ReadModelJpaRepository.java.ejs +13 -0
- package/templates/read-model/ReadModelKafkaListener.java.ejs +64 -0
- package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
- package/templates/read-model/ReadModelRepository.java.ejs +42 -0
- package/templates/read-model/ReadModelRepositoryImpl.java.ejs +85 -0
- package/templates/read-model/ReadModelSyncHandler.java.ejs +54 -0
- package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
- package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
- package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
- package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
- package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
- package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
- package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/NestedType.java.ejs +12 -0
- package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
- package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
- package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
- package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
- package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
- package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
- package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
- package/COMMAND_EVALUATION.md +0 -911
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Temporal Communication Patterns — Quick Reference
|
|
2
|
+
|
|
3
|
+
Reference for understanding how modules communicate in a Temporal-based system.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Pattern Summary
|
|
8
|
+
|
|
9
|
+
| Pattern | Coupling | Direction | Waits for response | Use case |
|
|
10
|
+
|---------|---------|-----------|-------------------|----------|
|
|
11
|
+
| **Remote Activity** | Medium | Request → Response | ✅ Yes (blocking) | Atomic operations cross-service |
|
|
12
|
+
| **Remote Activity + Async** | Medium | Request → Response (parallel) | ✅ Yes (non-blocking) | Multiple independent operations in parallel |
|
|
13
|
+
| **Child Workflow** | High | Parent → Child | ✅ Yes | Subprocesses with own lifecycle |
|
|
14
|
+
| **Signal External** | Low | Fire & Forget | ❌ No | Notifications between active workflows |
|
|
15
|
+
| **Signal + Await** | Low | Bidirectional | ✅ Yes (with wait) | Wait for external event with timeout |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Remote Activity (most common)
|
|
20
|
+
|
|
21
|
+
A workflow invokes an activity whose implementation lives in another service. Temporal routes the task via Task Queue.
|
|
22
|
+
|
|
23
|
+
**When to use:**
|
|
24
|
+
- Atomic operations without own lifecycle
|
|
25
|
+
- Workflow needs the result to continue
|
|
26
|
+
- Read data from another service
|
|
27
|
+
- Simple write in another service
|
|
28
|
+
|
|
29
|
+
**Each microservice needs:**
|
|
30
|
+
```
|
|
31
|
+
invoker (e.g., orders):
|
|
32
|
+
├── ActivityInterface.java ← interface (contract only)
|
|
33
|
+
└── DTOs (input/output)
|
|
34
|
+
|
|
35
|
+
executor (e.g., inventory):
|
|
36
|
+
├── ActivityInterface.java ← same interface
|
|
37
|
+
├── ActivityImpl.java ← implementation with DB access
|
|
38
|
+
└── Worker.java ← registers activity in queue
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. Parallel Execution with Async.function()
|
|
44
|
+
|
|
45
|
+
Execute multiple independent activities simultaneously.
|
|
46
|
+
|
|
47
|
+
**When to use:**
|
|
48
|
+
- 2+ activities are independent of each other
|
|
49
|
+
- Want to reduce total latency (max of individual times vs sum)
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Sequential: A(2s) → B(1s) → C(1.5s) = 4.5s
|
|
53
|
+
Parallel: A(2s) | B(1s) | C(1.5s) = 2s (max)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**In system.yaml:** mark steps with `parallel: true`
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 3. Child Workflow
|
|
61
|
+
|
|
62
|
+
A workflow launches a sub-workflow with its own lifecycle, history, and signals.
|
|
63
|
+
|
|
64
|
+
**When to use:**
|
|
65
|
+
- Subprocess has **own lifecycle** (can be cancelled, queried, retried)
|
|
66
|
+
- Complex logic with multiple internal steps
|
|
67
|
+
- Need to **limit cancellation scope**
|
|
68
|
+
- Parent history would be too long if inlined
|
|
69
|
+
|
|
70
|
+
**Difference from Remote Activity:**
|
|
71
|
+
```
|
|
72
|
+
Remote Activity:
|
|
73
|
+
- One operation → result
|
|
74
|
+
- No internal state
|
|
75
|
+
- No signals or queries
|
|
76
|
+
|
|
77
|
+
Child Workflow:
|
|
78
|
+
- Multiple steps with internal state
|
|
79
|
+
- Accepts signals
|
|
80
|
+
- Can be queried
|
|
81
|
+
- Own history in Temporal Web
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 4. Signal External (Fire & Forget)
|
|
87
|
+
|
|
88
|
+
Send a signal to an already-running workflow. No response expected.
|
|
89
|
+
|
|
90
|
+
**When to use:**
|
|
91
|
+
- Notify an active workflow that something happened
|
|
92
|
+
- No response needed
|
|
93
|
+
- Services are completely independent
|
|
94
|
+
|
|
95
|
+
**The sender does NOT need the receiver's interface** if using untyped stub — lowest coupling pattern.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 5. Signal + Await (Wait with Timeout)
|
|
100
|
+
|
|
101
|
+
Combine Signal with `Workflow.await()` for request-wait pattern between services.
|
|
102
|
+
|
|
103
|
+
**When to use:**
|
|
104
|
+
- Need response from another service but don't want to block with activity
|
|
105
|
+
- Other service may take minutes, hours, or days
|
|
106
|
+
- Need a timeout if response doesn't arrive
|
|
107
|
+
- Human approvals, external verifications, batch processes
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Mapping to system.yaml
|
|
112
|
+
|
|
113
|
+
| Pattern | Representation in system.yaml |
|
|
114
|
+
|---------|------------------------------|
|
|
115
|
+
| Remote Activity | `steps: [{ activity: X, target: Y, type: sync }]` |
|
|
116
|
+
| Parallel Activity | `steps: [{ activity: X, parallel: true }]` |
|
|
117
|
+
| Async Activity | `steps: [{ activity: X, type: async }]` |
|
|
118
|
+
| Child Workflow | Not in system.yaml directly — declare in domain.yaml `workflows:` |
|
|
119
|
+
| Signal | Implicit in `wait:` steps in domain.yaml workflows |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Activity Data Flow Rules
|
|
124
|
+
|
|
125
|
+
### Rule 0: Activities are invoked ONLY from workflows
|
|
126
|
+
|
|
127
|
+
Activities are Temporal constructs — they can ONLY be invoked from within a workflow execution context. Handlers, use cases, and REST controllers do NOT have access to activity stubs. If a REST endpoint needs data from another module, the handler must emit a Domain Event that triggers a workflow, and the workflow orchestrates the activity calls.
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
✅ Workflow invokes activity:
|
|
131
|
+
PlaceOrderWorkflow → GetProductsByIds (via Temporal stub)
|
|
132
|
+
|
|
133
|
+
❌ Handler invokes activity:
|
|
134
|
+
AddToCartCommandHandler → GetProductById (WRONG — not a workflow context)
|
|
135
|
+
|
|
136
|
+
✅ Correct handler pattern:
|
|
137
|
+
AddToCartCommandHandler → save(cart) → DomainEvent → AddToCartWorkflow → GetProductById
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This applies to ALL activities — both cross-module and local. Even a module's own activities are invoked from its single-module `workflows:` in domain.yaml, never from handlers.
|
|
141
|
+
|
|
142
|
+
### Rule 1: Activities access ONLY their own module's DB
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
✅ inventory.ReserveStock → reads inventory.stock table
|
|
146
|
+
❌ inventory.ReserveStock → reads customers.customer table
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Rule 2: Workflows assemble cross-module data
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
Workflow:
|
|
153
|
+
Step 1: GetCustomerById → customers (get email, name)
|
|
154
|
+
Step 2: GetProductsByIds → products (get prices)
|
|
155
|
+
Step 3: ReserveStock → inventory (pass assembled data)
|
|
156
|
+
Step 4: NotifyOrderPlaced → notifications (pass customer email, name, total)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Rule 3: Notification activities receive ALL data as input
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
✅ NotifyOrderPlaced(orderId, email, firstName, totalAmount)
|
|
163
|
+
→ Has everything it needs, zero lookups
|
|
164
|
+
|
|
165
|
+
❌ NotifyOrderPlaced(orderId, customerId)
|
|
166
|
+
→ Would need to look up customer data — HIDDEN COUPLING
|
|
167
|
+
```
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# Temporal domain.yaml per module — Complete Specification
|
|
2
|
+
|
|
3
|
+
Reference for building `system/{module}.yaml` in a Temporal-based system. This file is the input for `eva g entities <module>`.
|
|
4
|
+
|
|
5
|
+
> **PATH RULE:** Each module file MUST be saved inside the `system/` directory as `system/{module-name}.yaml` (e.g., `system/orders.yaml`, `system/payments.yaml`). NEVER save module YAML files at the project root.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Expert role per module
|
|
10
|
+
|
|
11
|
+
When building each `system/{module}.yaml`, activate the domain expert role:
|
|
12
|
+
|
|
13
|
+
- **`orders`** → lifecycles, states, invariants, item relationships, calculated totals
|
|
14
|
+
- **`payments`** → payment methods, retries, terminal states, double-charge prevention
|
|
15
|
+
- **`inventory`** → available vs. reserved stock, movements, replenishment
|
|
16
|
+
- **`notifications`** → channels, templates, idempotency, retries
|
|
17
|
+
|
|
18
|
+
Propose necessary fields not mentioned, expressive Value Objects, implicit invariants, realistic state transitions. If you need specific business rules, ask.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Absolute restrictions
|
|
23
|
+
|
|
24
|
+
1. ❌ **No `@ManyToOne`/`@OneToMany` between aggregates** — cross-aggregate references are IDs with `reference:`
|
|
25
|
+
2. ❌ **No audit fields in `fields:`** — `audit.enabled: true` generates them
|
|
26
|
+
3. ❌ **No `defaultValue` on non `readOnly` fields**
|
|
27
|
+
4. ❌ **No `transitions` without `initialValue`** in the enum
|
|
28
|
+
5. ❌ **No invented modules in `reference.module`** — only those in `system/system.yaml`
|
|
29
|
+
6. ❌ **No duplicate `endpoints:` from `system.yaml → exposes:`**
|
|
30
|
+
7. ❌ **`endpoints:` NEVER a flat list** — always `{ basePath, versions: [{ version, operations }] }`. If the module has **2+ aggregates**, use `basePath: ""` and absolute paths per operation
|
|
31
|
+
8. ❌ **No `listeners:` section** — Temporal replaces Kafka consumers
|
|
32
|
+
9. ❌ **No `readModels:` section** — on-demand reads via Activities replace local projections
|
|
33
|
+
10. ❌ **No `ports:` for internal modules** — only for external services (non-Temporal)
|
|
34
|
+
11. ❌ **All in English**
|
|
35
|
+
12. ❌ **No `lifecycle:` and `triggers:` on the same event** — mutually exclusive
|
|
36
|
+
13. ❌ **No `lifecycle: softDelete` without `hasSoftDelete: true`** on root entity
|
|
37
|
+
14. ❌ **No `lifecycle: delete` with `hasSoftDelete: true`**
|
|
38
|
+
15. ❌ **No fields in lifecycle events not in the root entity** — `C2-010`
|
|
39
|
+
16. ❌ **No `topic:` on events** — Temporal replaces Kafka topics
|
|
40
|
+
17. ❌ **Events with `notifies:` must reference workflows in system.yaml** — never arbitrary names
|
|
41
|
+
18. ❌ **Activities must not do cross-module data lookups** — all data comes via input
|
|
42
|
+
19. ❌ **Activities invoked ONLY from workflows** — never from handlers, use cases, or REST controllers. If a handler needs cross-module data, emit an event with `notifies:` to trigger a workflow that invokes the read activity. Even local activities are invoked from single-module `workflows:` in domain.yaml, not from handlers
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Inference from system.yaml
|
|
47
|
+
|
|
48
|
+
| Source in system.yaml | Destination in domain.yaml |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `modules[x].exposes[]` | `endpoints:` with `basePath` + `versions[].operations[]` |
|
|
51
|
+
| `workflows[].steps[]` where `target = module` | `activities:` — the module's capabilities |
|
|
52
|
+
| `workflows[].trigger` where `module = module` | `events:` with `notifies:` |
|
|
53
|
+
| None — internal processes | `workflows:` — single-module internal flows |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Complete module.yaml structure
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
aggregates:
|
|
61
|
+
- name: Order # PascalCase
|
|
62
|
+
entities:
|
|
63
|
+
- name: order # camelCase — root entity
|
|
64
|
+
isRoot: true
|
|
65
|
+
tableName: orders # snake_case
|
|
66
|
+
hasSoftDelete: false
|
|
67
|
+
audit:
|
|
68
|
+
enabled: true
|
|
69
|
+
trackUser: false
|
|
70
|
+
fields:
|
|
71
|
+
- name: id
|
|
72
|
+
type: String
|
|
73
|
+
- name: customerId
|
|
74
|
+
type: String
|
|
75
|
+
reference:
|
|
76
|
+
aggregate: Customer
|
|
77
|
+
module: customers
|
|
78
|
+
- name: totalAmount
|
|
79
|
+
type: BigDecimal
|
|
80
|
+
readOnly: true
|
|
81
|
+
defaultValue: "0.00"
|
|
82
|
+
- name: status
|
|
83
|
+
type: OrderStatus
|
|
84
|
+
readOnly: true
|
|
85
|
+
relationships:
|
|
86
|
+
- type: OneToMany
|
|
87
|
+
target: OrderItem
|
|
88
|
+
mappedBy: order
|
|
89
|
+
cascade: [PERSIST, MERGE, REMOVE]
|
|
90
|
+
fetch: LAZY
|
|
91
|
+
|
|
92
|
+
- name: orderItem
|
|
93
|
+
tableName: order_items
|
|
94
|
+
fields:
|
|
95
|
+
- name: id
|
|
96
|
+
type: String
|
|
97
|
+
- name: productId
|
|
98
|
+
type: String
|
|
99
|
+
reference:
|
|
100
|
+
aggregate: Product
|
|
101
|
+
module: products
|
|
102
|
+
|
|
103
|
+
valueObjects:
|
|
104
|
+
- name: ShippingAddress
|
|
105
|
+
fields:
|
|
106
|
+
- name: street
|
|
107
|
+
type: String
|
|
108
|
+
- name: city
|
|
109
|
+
type: String
|
|
110
|
+
|
|
111
|
+
enums:
|
|
112
|
+
- name: OrderStatus
|
|
113
|
+
initialValue: PENDING
|
|
114
|
+
transitions:
|
|
115
|
+
- from: PENDING
|
|
116
|
+
to: CONFIRMED
|
|
117
|
+
method: confirm
|
|
118
|
+
- from: PENDING
|
|
119
|
+
to: CANCELLED
|
|
120
|
+
method: cancel
|
|
121
|
+
values: [PENDING, CONFIRMED, CANCELLED]
|
|
122
|
+
|
|
123
|
+
events:
|
|
124
|
+
# Events WITH notifies: → trigger cross-module workflows
|
|
125
|
+
- name: OrderPlacedEvent
|
|
126
|
+
lifecycle: create
|
|
127
|
+
fields:
|
|
128
|
+
- name: orderId
|
|
129
|
+
type: String
|
|
130
|
+
- name: placedAt
|
|
131
|
+
type: LocalDateTime
|
|
132
|
+
notifies:
|
|
133
|
+
- workflow: PlaceOrderWorkflow
|
|
134
|
+
|
|
135
|
+
# Events with triggers: → state transitions
|
|
136
|
+
- name: OrderCancelledEvent
|
|
137
|
+
triggers:
|
|
138
|
+
- cancel
|
|
139
|
+
fields:
|
|
140
|
+
- name: orderId
|
|
141
|
+
type: String
|
|
142
|
+
notifies:
|
|
143
|
+
- workflow: CancelOrderWorkflow
|
|
144
|
+
|
|
145
|
+
# Events WITHOUT notifies: → internal Domain Events
|
|
146
|
+
- name: CustomerUpdatedEvent
|
|
147
|
+
lifecycle: update
|
|
148
|
+
fields:
|
|
149
|
+
- name: customerId
|
|
150
|
+
type: String
|
|
151
|
+
# NO notifies → Domain Event internal
|
|
152
|
+
|
|
153
|
+
# ─── Activities this module EXPOSES ──────────────────────────────────────────
|
|
154
|
+
activities:
|
|
155
|
+
- name: GetOrderDetails
|
|
156
|
+
type: light
|
|
157
|
+
description: "Gets full order details including items"
|
|
158
|
+
input:
|
|
159
|
+
- name: orderId
|
|
160
|
+
type: String
|
|
161
|
+
output:
|
|
162
|
+
- name: customerId
|
|
163
|
+
type: String
|
|
164
|
+
- name: items
|
|
165
|
+
type: List<OrderItemDetail>
|
|
166
|
+
- name: totalAmount
|
|
167
|
+
type: BigDecimal
|
|
168
|
+
nestedTypes:
|
|
169
|
+
- name: OrderItemDetail
|
|
170
|
+
fields:
|
|
171
|
+
- name: productId
|
|
172
|
+
type: String
|
|
173
|
+
- name: quantity
|
|
174
|
+
type: Integer
|
|
175
|
+
- name: unitPrice
|
|
176
|
+
type: BigDecimal
|
|
177
|
+
timeout: 5s
|
|
178
|
+
|
|
179
|
+
- name: ConfirmOrder
|
|
180
|
+
type: light
|
|
181
|
+
description: "Confirms the order after successful payment"
|
|
182
|
+
input:
|
|
183
|
+
- name: orderId
|
|
184
|
+
type: String
|
|
185
|
+
timeout: 5s
|
|
186
|
+
|
|
187
|
+
- name: CancelExpiredOrder
|
|
188
|
+
type: light
|
|
189
|
+
description: "Cancels order that did not receive payment in time"
|
|
190
|
+
input:
|
|
191
|
+
- name: orderId
|
|
192
|
+
type: String
|
|
193
|
+
timeout: 5s
|
|
194
|
+
|
|
195
|
+
# ─── Single-module workflows (internal) ─────────────────────────────────────
|
|
196
|
+
workflows:
|
|
197
|
+
- name: ExpireOrderWorkflow
|
|
198
|
+
description: "Cancels order if payment not received within timeout"
|
|
199
|
+
trigger:
|
|
200
|
+
on: orderCreated
|
|
201
|
+
taskQueue: ORDER_WORKFLOW_QUEUE
|
|
202
|
+
steps:
|
|
203
|
+
- wait: paymentCompleted
|
|
204
|
+
timeout: 30m
|
|
205
|
+
- activity: CancelExpiredOrder
|
|
206
|
+
timeout: 5s
|
|
207
|
+
|
|
208
|
+
endpoints:
|
|
209
|
+
basePath: /orders
|
|
210
|
+
versions:
|
|
211
|
+
- version: v1
|
|
212
|
+
operations:
|
|
213
|
+
- useCase: CreateOrder
|
|
214
|
+
method: POST
|
|
215
|
+
path: /
|
|
216
|
+
- useCase: GetOrder
|
|
217
|
+
method: GET
|
|
218
|
+
path: /{id}
|
|
219
|
+
- useCase: FindAllOrders
|
|
220
|
+
method: GET
|
|
221
|
+
path: /
|
|
222
|
+
|
|
223
|
+
# ─── Multi-aggregate module example ─────────────────────────────────────────────
|
|
224
|
+
# When a module contains 2+ aggregates (e.g. Product + Category),
|
|
225
|
+
# use basePath: "" (empty string, NOT "/") and absolute paths:
|
|
226
|
+
#
|
|
227
|
+
# endpoints:
|
|
228
|
+
# basePath: ""
|
|
229
|
+
# versions:
|
|
230
|
+
# - version: v1
|
|
231
|
+
# operations:
|
|
232
|
+
# - useCase: CreateProduct
|
|
233
|
+
# method: POST
|
|
234
|
+
# path: /products
|
|
235
|
+
# - useCase: GetProduct
|
|
236
|
+
# method: GET
|
|
237
|
+
# path: /products/{id}
|
|
238
|
+
# - useCase: CreateCategory
|
|
239
|
+
# method: POST
|
|
240
|
+
# path: /categories
|
|
241
|
+
# - useCase: FindProductsByCategory
|
|
242
|
+
# method: GET
|
|
243
|
+
# path: /categories/{id}/products
|
|
244
|
+
|
|
245
|
+
# ─── ports: ONLY for EXTERNAL services ──────────────────────────────────────
|
|
246
|
+
# ports:
|
|
247
|
+
# - name: processCharge
|
|
248
|
+
# service: PaymentGatewayService
|
|
249
|
+
# target: payment-gateway
|
|
250
|
+
# baseUrl: https://api.payments.example.com
|
|
251
|
+
# http: POST /charges
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Activities section — detailed specification
|
|
257
|
+
|
|
258
|
+
### Activities vs Ports — invocation rules
|
|
259
|
+
|
|
260
|
+
| Aspect | `ports:` | `activities:` |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| What it represents | External service clients (Feign) | Module capabilities (Temporal) |
|
|
263
|
+
| Target | Payment gateways, email providers, third-party APIs | Other modules or same module |
|
|
264
|
+
| Who can invoke | Handlers, use cases, activity implementations | **ONLY workflows** (via Temporal stubs) |
|
|
265
|
+
| Where declared | `ports:` in domain.yaml | `activities:` in domain.yaml |
|
|
266
|
+
| Invocation mechanism | Feign HTTP call | Temporal task queue dispatch |
|
|
267
|
+
|
|
268
|
+
> **Key takeaway:** A handler that needs data from another module must trigger a workflow (via Domain Event + `notifies:`). The workflow then invokes the appropriate read activity. Handlers NEVER inject or call activity interfaces.
|
|
269
|
+
|
|
270
|
+
### Activity properties
|
|
271
|
+
|
|
272
|
+
| Property | Required | Description |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
| `name` | ✅ | PascalCase activity name |
|
|
275
|
+
| `type` | ✅ | `light` (< 30s) or `heavy` (up to 2min) |
|
|
276
|
+
| `description` | ❌ | What the activity does |
|
|
277
|
+
| `input` | ✅ | List of input fields with `name` and `type` |
|
|
278
|
+
| `output` | ❌ | List of output fields (omit for void activities) |
|
|
279
|
+
| `timeout` | ❌ | Activity timeout (e.g., `5s`, `30s`) |
|
|
280
|
+
| `compensation` | ❌ | Name of the compensating activity |
|
|
281
|
+
| `retryPolicy` | ❌ | Retry configuration |
|
|
282
|
+
| `nestedTypes` | ❌ | Complex types used in input/output |
|
|
283
|
+
| `externalTypes` | ❌ | Types defined in another module |
|
|
284
|
+
|
|
285
|
+
### Retry policy
|
|
286
|
+
|
|
287
|
+
```yaml
|
|
288
|
+
retryPolicy:
|
|
289
|
+
maxAttempts: 3
|
|
290
|
+
initialInterval: 1s
|
|
291
|
+
backoffCoefficient: 2.0
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### External types
|
|
295
|
+
|
|
296
|
+
When an activity receives types defined in another module:
|
|
297
|
+
|
|
298
|
+
```yaml
|
|
299
|
+
activities:
|
|
300
|
+
- name: ReserveStock
|
|
301
|
+
input:
|
|
302
|
+
- name: items
|
|
303
|
+
type: List<OrderItemDetail>
|
|
304
|
+
externalTypes:
|
|
305
|
+
- name: OrderItemDetail
|
|
306
|
+
module: orders
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Activity type classification
|
|
310
|
+
|
|
311
|
+
| Module role | Activity types | Examples |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| Data provider | Read activities | `GetCustomerById`, `GetProductsByIds` |
|
|
314
|
+
| Executor | Write + compensation | `ReserveStock` / `ReleaseStock` |
|
|
315
|
+
| Reactor | Notification activities | `NotifyOrderPlaced` |
|
|
316
|
+
| Orchestrator (local) | State transition activities | `ConfirmOrder`, `CancelExpiredOrder` |
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Events with `notifies:` — detailed specification
|
|
321
|
+
|
|
322
|
+
### When to use `notifies:`
|
|
323
|
+
|
|
324
|
+
- The event triggers a **cross-module workflow** defined in `system.yaml`
|
|
325
|
+
- There is a **real business effect** in another module (not data sync)
|
|
326
|
+
|
|
327
|
+
### When NOT to use `notifies:`
|
|
328
|
+
|
|
329
|
+
- The event is purely internal (no cross-module reaction)
|
|
330
|
+
- The data is only needed for sync (use on-demand reads instead)
|
|
331
|
+
|
|
332
|
+
```yaml
|
|
333
|
+
events:
|
|
334
|
+
# ✅ Real business effect → notifies
|
|
335
|
+
- name: OrderPlacedEvent
|
|
336
|
+
lifecycle: create
|
|
337
|
+
notifies:
|
|
338
|
+
- workflow: PlaceOrderWorkflow
|
|
339
|
+
|
|
340
|
+
# ✅ Internal event → NO notifies
|
|
341
|
+
- name: CustomerUpdatedEvent
|
|
342
|
+
lifecycle: update
|
|
343
|
+
# On-demand reads: GetCustomerById activity
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Workflows section (single-module) — detailed specification
|
|
349
|
+
|
|
350
|
+
### When a workflow is single-module
|
|
351
|
+
|
|
352
|
+
- All its activities belong to the **same module**
|
|
353
|
+
- It doesn't need data from other bounded contexts
|
|
354
|
+
- It's an internal process (retry, timeout, scheduling, verification)
|
|
355
|
+
- Other modules don't need to know it exists
|
|
356
|
+
|
|
357
|
+
### Step types in single-module workflows
|
|
358
|
+
|
|
359
|
+
| Step type | Property | Description |
|
|
360
|
+
|---|---|---|
|
|
361
|
+
| Activity | `activity:` | Invoke a local activity |
|
|
362
|
+
| Wait | `wait:` + `timeout:` | Wait for a signal with timeout |
|
|
363
|
+
|
|
364
|
+
```yaml
|
|
365
|
+
workflows:
|
|
366
|
+
- name: RetryChargeWorkflow
|
|
367
|
+
trigger:
|
|
368
|
+
on: chargeFailed
|
|
369
|
+
taskQueue: PAYMENT_WORKFLOW_QUEUE
|
|
370
|
+
steps:
|
|
371
|
+
- activity: RetryCharge
|
|
372
|
+
retryPolicy:
|
|
373
|
+
maxAttempts: 3
|
|
374
|
+
initialInterval: 2s
|
|
375
|
+
backoffCoefficient: 2.0
|
|
376
|
+
- activity: MarkPaymentFailed
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Visibility of fields
|
|
382
|
+
|
|
383
|
+
| Configuration | Business constructor | CreateDto | ResponseDto |
|
|
384
|
+
|---|---|---|---|
|
|
385
|
+
| Normal | ✅ | ✅ | ✅ |
|
|
386
|
+
| `readOnly: true` | ❌ | ❌ | ✅ |
|
|
387
|
+
| `readOnly` + `defaultValue` | ⚡ assigned with default | ❌ | ✅ |
|
|
388
|
+
| `hidden: true` | ✅ | ✅ | ❌ |
|
|
389
|
+
| Both flags | ❌ | ❌ | ❌ |
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Supported data types
|
|
394
|
+
|
|
395
|
+
| YAML | Java |
|
|
396
|
+
|---|---|
|
|
397
|
+
| `String` | String |
|
|
398
|
+
| `Integer` | Integer |
|
|
399
|
+
| `Long` | Long |
|
|
400
|
+
| `BigDecimal` | BigDecimal |
|
|
401
|
+
| `Boolean` | Boolean |
|
|
402
|
+
| `LocalDate` | LocalDate |
|
|
403
|
+
| `LocalDateTime` | LocalDateTime |
|
|
404
|
+
| `LocalTime` | LocalTime |
|
|
405
|
+
| `Instant` | Instant |
|
|
406
|
+
| `UUID` | UUID |
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## JSR-303 Validations
|
|
411
|
+
|
|
412
|
+
Declare in `fields[].validations` — applied **only** in Command and CreateDto.
|
|
413
|
+
|
|
414
|
+
```yaml
|
|
415
|
+
validations:
|
|
416
|
+
- type: NotBlank
|
|
417
|
+
message: "Required"
|
|
418
|
+
- type: Email
|
|
419
|
+
message: "Invalid email"
|
|
420
|
+
- type: Size
|
|
421
|
+
min: 3
|
|
422
|
+
max: 50
|
|
423
|
+
- type: Positive
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Checklist per module
|
|
429
|
+
|
|
430
|
+
- [ ] Root entity has `id` field of type String
|
|
431
|
+
- [ ] `isRoot: true` on exactly one entity per aggregate
|
|
432
|
+
- [ ] `audit:` section present on root entity
|
|
433
|
+
- [ ] `readOnly:` fields with `defaultValue:` where appropriate
|
|
434
|
+
- [ ] `reference:` on cross-aggregate ID fields
|
|
435
|
+
- [ ] `endpoints:` as object with `basePath` + `versions` (NOT flat list)
|
|
436
|
+
- [ ] Module with 1 aggregate → `basePath: /resource` and relative paths
|
|
437
|
+
- [ ] Module with 2+ aggregates → `basePath: ""` and absolute paths per operation
|
|
438
|
+
- [ ] `events:` with `notifies:` only for cross-module workflow triggers
|
|
439
|
+
- [ ] `events:` without `notifies:` for internal Domain Events
|
|
440
|
+
- [ ] `activities:` section declares all capabilities referenced in system.yaml workflows
|
|
441
|
+
- [ ] Activity `input:` contains all data the activity needs (no cross-module lookups)
|
|
442
|
+
- [ ] `compensation:` declared for reversible activities
|
|
443
|
+
- [ ] No handler or use case directly invokes an activity — all activity calls go through workflows
|
|
444
|
+
- [ ] `workflows:` only for single-module internal flows
|
|
445
|
+
- [ ] No `listeners:` section (Temporal replaces it)
|
|
446
|
+
- [ ] No `readModels:` section (on-demand reads replace it)
|
|
447
|
+
- [ ] `ports:` only for external services (if any)
|
|
448
|
+
- [ ] No `topic:` on events
|
|
449
|
+
- [ ] All in English
|