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.
Files changed (151) hide show
  1. package/AGENTS.md +220 -5
  2. package/DOMAIN_YAML_GUIDE.md +188 -3
  3. package/FUTURE_FEATURES.md +33 -52
  4. package/QUICK_REFERENCE.md +8 -4
  5. package/bin/eva4j.js +70 -2
  6. package/config/defaults.json +1 -0
  7. package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
  8. package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
  9. package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
  10. package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
  11. package/docs/commands/EVALUATE_SYSTEM.md +290 -10
  12. package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
  13. package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
  14. package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
  15. package/docs/commands/INDEX.md +27 -3
  16. package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
  17. package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
  18. package/docs/prototype/system/RISKS.md +277 -0
  19. package/docs/prototype/system/customers.yaml +133 -0
  20. package/docs/prototype/system/inventory.yaml +109 -0
  21. package/docs/prototype/system/notifications.yaml +131 -0
  22. package/docs/prototype/system/orders.yaml +241 -0
  23. package/docs/prototype/system/payments.yaml +256 -0
  24. package/docs/prototype/system/products.yaml +168 -0
  25. package/docs/prototype/system/system.yaml +269 -0
  26. package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
  27. package/examples/domain-events.yaml +26 -0
  28. package/examples/domain-read-models.yaml +113 -0
  29. package/examples/system/customer.yaml +89 -0
  30. package/examples/system/orders.yaml +119 -0
  31. package/examples/system/product.yaml +27 -0
  32. package/examples/system/system.yaml +80 -0
  33. package/package.json +1 -1
  34. package/read-model-spec.md +664 -0
  35. package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
  36. package/src/agents/design-gap-analyst.agent.md +383 -0
  37. package/src/agents/design-reviewer-temporal.agent.md +412 -0
  38. package/src/agents/design-reviewer.agent.md +34 -5
  39. package/src/agents/implement-use-cases.prompt.md +179 -0
  40. package/src/agents/ux-gap-analyst.agent.md +412 -0
  41. package/src/commands/add-rabbitmq-client.js +261 -0
  42. package/src/commands/add-temporal-client.js +22 -2
  43. package/src/commands/build.js +267 -11
  44. package/src/commands/evaluate-system.js +700 -13
  45. package/src/commands/generate-entities.js +560 -24
  46. package/src/commands/generate-http-exchange.js +3 -0
  47. package/src/commands/generate-kafka-event.js +3 -0
  48. package/src/commands/generate-kafka-listener.js +3 -0
  49. package/src/commands/generate-rabbitmq-event.js +665 -0
  50. package/src/commands/generate-rabbitmq-listener.js +205 -0
  51. package/src/commands/generate-record.js +2 -2
  52. package/src/commands/generate-resource.js +4 -1
  53. package/src/commands/generate-temporal-activity.js +970 -33
  54. package/src/commands/generate-temporal-flow.js +98 -38
  55. package/src/commands/generate-temporal-system.js +708 -0
  56. package/src/commands/generate-usecase.js +4 -1
  57. package/src/skills/build-system-yaml/SKILL.md +343 -2
  58. package/src/skills/build-system-yaml/references/domain-yaml-spec.md +253 -26
  59. package/src/skills/build-system-yaml/references/module-spec.md +90 -9
  60. package/src/skills/build-system-yaml/references/system-yaml-spec.md +36 -0
  61. package/src/skills/build-temporal-system/SKILL.md +752 -0
  62. package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
  63. package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
  64. package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
  65. package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
  66. package/src/skills/implement-use-case/SKILL.md +350 -0
  67. package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
  68. package/src/skills/requirements-elicitation/SKILL.md +228 -0
  69. package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
  70. package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
  71. package/src/utils/bounded-context-diagram.js +844 -0
  72. package/src/utils/config-manager.js +4 -2
  73. package/src/utils/domain-validator.js +495 -17
  74. package/src/utils/naming.js +20 -0
  75. package/src/utils/system-validator.js +169 -11
  76. package/src/utils/system-yaml-parser.js +318 -0
  77. package/src/utils/temporal-validator.js +497 -0
  78. package/src/utils/validator.js +3 -1
  79. package/src/utils/yaml-to-entity.js +281 -9
  80. package/templates/aggregate/AggregateRepository.java.ejs +4 -0
  81. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +8 -0
  82. package/templates/aggregate/AggregateRoot.java.ejs +38 -4
  83. package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
  84. package/templates/aggregate/JpaAggregateRoot.java.ejs +4 -4
  85. package/templates/aggregate/JpaEntity.java.ejs +2 -2
  86. package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
  87. package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
  88. package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
  89. package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
  90. package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
  91. package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
  92. package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
  93. package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
  94. package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
  95. package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
  96. package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
  97. package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
  98. package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
  99. package/templates/base/root/AGENTS.md.ejs +1 -1
  100. package/templates/crud/DeleteCommandHandler.java.ejs +19 -1
  101. package/templates/crud/EndpointsController.java.ejs +1 -1
  102. package/templates/crud/ScaffoldCommand.java.ejs +5 -2
  103. package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
  104. package/templates/crud/ScaffoldQuery.java.ejs +5 -2
  105. package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
  106. package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
  107. package/templates/crud/UpdateCommandHandler.java.ejs +53 -2
  108. package/templates/evaluate/report.html.ejs +1447 -90
  109. package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
  110. package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
  111. package/templates/ports/PortAclMapper.java.ejs +35 -0
  112. package/templates/ports/PortFeignAdapter.java.ejs +7 -22
  113. package/templates/ports/PortFeignClient.java.ejs +4 -0
  114. package/templates/ports/PortResponseDto.java.ejs +1 -1
  115. package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
  116. package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
  117. package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
  118. package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
  119. package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
  120. package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
  121. package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
  122. package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
  123. package/templates/read-model/ReadModelDomain.java.ejs +46 -0
  124. package/templates/read-model/ReadModelJpa.java.ejs +58 -0
  125. package/templates/read-model/ReadModelJpaRepository.java.ejs +13 -0
  126. package/templates/read-model/ReadModelKafkaListener.java.ejs +64 -0
  127. package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
  128. package/templates/read-model/ReadModelRepository.java.ejs +42 -0
  129. package/templates/read-model/ReadModelRepositoryImpl.java.ejs +85 -0
  130. package/templates/read-model/ReadModelSyncHandler.java.ejs +54 -0
  131. package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
  132. package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
  133. package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
  134. package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
  135. package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
  136. package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
  137. package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
  138. package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
  139. package/templates/temporal-activity/NestedType.java.ejs +12 -0
  140. package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
  141. package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
  142. package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
  143. package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
  144. package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
  145. package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
  146. package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
  147. package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
  148. package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
  149. package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
  150. package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
  151. package/COMMAND_EVALUATION.md +0 -911
@@ -0,0 +1,595 @@
1
+ # Command `generate rabbitmq-listener` (alias: `g rabbitmq-listener`)
2
+
3
+ ## Description
4
+
5
+ Creates individual RabbitMQ listener classes in a module's infrastructure layer. Each listener class is dedicated to a single queue, following the **Open/Closed Principle** for better maintainability and scalability.
6
+
7
+ The command generates Spring AMQP `@RabbitListener` components that automatically consume events from configured queues, deserialize them into `EventEnvelope` objects, and provide integration with the `UseCaseMediator` for processing. Messages are manually acknowledged and failed messages are routed to a dead-letter queue (DLQ).
8
+
9
+ **Key Feature:** Each queue gets its own listener class (e.g., `UserUserCreatedListener.java`, `OrderOrderPlacedListener.java`), with module-prefixed names to avoid bean conflicts when multiple modules subscribe to the same event.
10
+
11
+ ---
12
+
13
+ ## Purpose
14
+
15
+ - **Create RabbitMQ consumers** to receive events from external systems
16
+ - **Process async events** using Spring AMQP listeners
17
+ - **Follow Open/Closed Principle** with individual listener classes per queue
18
+ - **Integrate with CQRS** architecture via UseCaseMediator
19
+ - **Handle errors with DLQ** — failed messages are nack'd to the dead-letter queue
20
+ - **Support multiple queues** by generating multiple listener classes
21
+ - **Enable event-driven communication** between microservices
22
+
23
+ ---
24
+
25
+ ## Syntax
26
+
27
+ ```bash
28
+ eva generate rabbitmq-listener <module>
29
+ eva g rabbitmq-listener <module> # Short alias
30
+ ```
31
+
32
+ ### Parameters
33
+
34
+ | Parameter | Required | Description |
35
+ |-----------|----------|-------------|
36
+ | `module` | Yes | Name of the module where listener will be created |
37
+
38
+ ### Interactive Prompts
39
+
40
+ After running the command, you'll be prompted to:
41
+
42
+ 1. **Select queues to listen to** (multiple selection with space bar)
43
+ - Queues are read from `rabbitmq.yaml` configuration
44
+ - Must select at least one queue
45
+ - Can select multiple queues at once
46
+
47
+ ---
48
+
49
+ ## Prerequisites
50
+
51
+ ### 1. RabbitMQ Client Must Be Installed
52
+
53
+ ```bash
54
+ eva add rabbitmq-client
55
+ ```
56
+
57
+ This configures:
58
+ - Spring AMQP dependencies
59
+ - RabbitMQ consumer configuration with manual acknowledgment
60
+ - Queue management in `rabbitmq.yaml`
61
+ - EventEnvelope infrastructure
62
+ - RabbitMQConfig.java with retry + DLQ support
63
+
64
+ ### 2. Queues Must Exist in rabbitmq.yaml
65
+
66
+ Queues are defined in: `src/main/resources/parameters/local/rabbitmq.yaml`
67
+
68
+ ```yaml
69
+ queues:
70
+ user-created: user.user-created
71
+ order-placed: order.order-placed
72
+ payment-processed: payment.payment-processed
73
+ ```
74
+
75
+ **Tip:** Generate queues using:
76
+ ```bash
77
+ eva generate rabbitmq-event <module> <event-name>
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Examples
83
+
84
+ ### Example 1: Basic Listener Creation
85
+
86
+ ```bash
87
+ # Create listeners in 'notification' module
88
+ eva g rabbitmq-listener notification
89
+
90
+ # Select queues from interactive menu:
91
+ # ✓ user-created (user.user-created)
92
+ # ✓ order-placed (order.order-placed)
93
+ ```
94
+
95
+ **Generated:**
96
+ ```
97
+ notification/infrastructure/rabbitListener/
98
+ ├── NotificationUserCreatedListener.java
99
+ └── NotificationOrderPlacedListener.java
100
+ ```
101
+
102
+ **Generated code for NotificationUserCreatedListener.java:**
103
+ ```java
104
+ package com.example.notification.infrastructure.rabbitListener;
105
+
106
+ import com.example.shared.infrastructure.configurations.useCaseConfig.UseCaseMediator;
107
+ import com.example.shared.infrastructure.eventEnvelope.EventEnvelope;
108
+
109
+ import com.fasterxml.jackson.core.type.TypeReference;
110
+ import com.fasterxml.jackson.databind.ObjectMapper;
111
+ import com.fasterxml.jackson.core.JsonProcessingException;
112
+ import com.rabbitmq.client.Channel;
113
+ import org.springframework.amqp.core.Message;
114
+ import org.springframework.amqp.rabbit.annotation.RabbitListener;
115
+ import org.springframework.stereotype.Component;
116
+ import org.slf4j.Logger;
117
+ import org.slf4j.LoggerFactory;
118
+
119
+ import java.io.IOException;
120
+ import java.util.Map;
121
+
122
+ /**
123
+ * RabbitMQ listener for queue user.user-created
124
+ */
125
+ @Component("notificationUserCreatedListener")
126
+ public class NotificationUserCreatedListener {
127
+
128
+ private static final Logger log = LoggerFactory.getLogger(NotificationUserCreatedListener.class);
129
+
130
+ private final UseCaseMediator useCaseMediator;
131
+ private final ObjectMapper objectMapper;
132
+
133
+ public NotificationUserCreatedListener(UseCaseMediator useCaseMediator, ObjectMapper objectMapper) {
134
+ this.useCaseMediator = useCaseMediator;
135
+ this.objectMapper = objectMapper;
136
+ }
137
+
138
+ @RabbitListener(queues = "${queues.user-created}")
139
+ public void handle(Message message, Channel channel) throws IOException {
140
+ long deliveryTag = message.getMessageProperties().getDeliveryTag();
141
+
142
+ EventEnvelope<Map<String, Object>> event;
143
+ try {
144
+ event = objectMapper.readValue(
145
+ message.getBody(),
146
+ new TypeReference<EventEnvelope<Map<String, Object>>>() {});
147
+ } catch (JsonProcessingException e) {
148
+ log.error("Fatal deserialization error — sending to DLQ: {}", e.getMessage());
149
+ channel.basicNack(deliveryTag, false, false);
150
+ return;
151
+ }
152
+
153
+ // TODO: Implement event processing logic
154
+ // Example: useCaseMediator.dispatch(new YourCommand(event.data()));
155
+
156
+ channel.basicAck(deliveryTag, false);
157
+ }
158
+
159
+ }
160
+ ```
161
+
162
+ ---
163
+
164
+ ### Example 2: Adding More Listeners
165
+
166
+ ```bash
167
+ # Run command again to add more listeners
168
+ eva g rabbitmq-listener order
169
+
170
+ # Select additional queues:
171
+ # ✓ payment-processed (payment.payment-processed)
172
+ ```
173
+
174
+ **Result:** New listener class is **created independently** — existing listeners are not modified:
175
+
176
+ ```
177
+ order/infrastructure/rabbitListener/
178
+ └── OrderPaymentProcessedListener.java
179
+ ```
180
+
181
+ ---
182
+
183
+ ### Example 3: Multiple Modules Subscribing to Same Event
184
+
185
+ **Scenario:** Both `notification` and `analytics` modules want to consume `user-created` events.
186
+
187
+ ```bash
188
+ # In notification module
189
+ eva g rabbitmq-listener notification
190
+ # Select: user-created → NotificationUserCreatedListener.java
191
+
192
+ # In analytics module
193
+ eva g rabbitmq-listener analytics
194
+ # Select: user-created → AnalyticsUserCreatedListener.java
195
+ ```
196
+
197
+ **Result:** No bean name conflicts — each module has its own listener with a module-prefixed class and bean name.
198
+
199
+ > **Note:** In RabbitMQ, each consumer queue is independent. If both modules need to receive **all** messages, each should have its own queue bound to the producer's exchange (configured via `rabbitmq.yaml`).
200
+
201
+ ---
202
+
203
+ ### Example 4: Processing Events with Use Cases
204
+
205
+ **Implement event processing in generated listener:**
206
+
207
+ ```java
208
+ @RabbitListener(queues = "${queues.user-created}")
209
+ public void handle(Message message, Channel channel) throws IOException {
210
+ long deliveryTag = message.getMessageProperties().getDeliveryTag();
211
+
212
+ EventEnvelope<Map<String, Object>> event;
213
+ try {
214
+ event = objectMapper.readValue(
215
+ message.getBody(),
216
+ new TypeReference<EventEnvelope<Map<String, Object>>>() {});
217
+ } catch (JsonProcessingException e) {
218
+ log.error("Fatal deserialization error — sending to DLQ: {}", e.getMessage());
219
+ channel.basicNack(deliveryTag, false, false);
220
+ return;
221
+ }
222
+
223
+ try {
224
+ String userId = (String) event.data().get("userId");
225
+ String email = (String) event.data().get("email");
226
+
227
+ useCaseMediator.dispatch(new SendWelcomeEmailCommand(userId, email));
228
+
229
+ channel.basicAck(deliveryTag, false);
230
+ } catch (Exception e) {
231
+ log.error("Processing failed — sending to DLQ: {}", e.getMessage());
232
+ channel.basicNack(deliveryTag, false, false);
233
+ }
234
+ }
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Generated Structure
240
+
241
+ ```
242
+ <module>/
243
+ └── infrastructure/
244
+ └── rabbitListener/
245
+ ├── <Module><Topic>Listener.java # Individual listener per queue
246
+ ├── NotificationUserCreatedListener.java # Notification module listening to user-created
247
+ ├── OrderOrderPlacedListener.java # Order module listening to order-placed
248
+ └── AnalyticsUserCreatedListener.java # Analytics module listening to user-created
249
+ ```
250
+
251
+ **Class Naming Pattern:** `{ModuleName}{QueueName}Listener`
252
+
253
+ | Module | Queue key | Class name |
254
+ |--------|-----------|------------|
255
+ | `notification` | `user-created` | `NotificationUserCreatedListener` |
256
+ | `order` | `order-placed` | `OrderOrderPlacedListener` |
257
+ | `analytics` | `user-created` | `AnalyticsUserCreatedListener` |
258
+
259
+ ---
260
+
261
+ ## Configuration
262
+
263
+ ### rabbitmq.yaml Structure
264
+
265
+ Location: `src/main/resources/parameters/local/rabbitmq.yaml`
266
+
267
+ ```yaml
268
+ spring:
269
+ rabbitmq:
270
+ host: localhost
271
+ port: 5672
272
+ username: guest
273
+ password: guest
274
+ virtual-host: /
275
+ listener:
276
+ simple:
277
+ acknowledge-mode: manual
278
+ concurrency: 3
279
+ retry:
280
+ enabled: true
281
+ max-attempts: 3
282
+ initial-interval: 1500
283
+
284
+ exchanges:
285
+ user: user.events
286
+ order: order.events
287
+
288
+ queues:
289
+ user-created: user.user-created
290
+ order-placed: order.order-placed
291
+
292
+ routing-keys:
293
+ user-created: user.created
294
+ order-placed: order.placed
295
+ ```
296
+
297
+ ### Environment-Specific Configuration
298
+
299
+ ```yaml
300
+ # parameters/local/rabbitmq.yaml
301
+ spring.rabbitmq.host: localhost
302
+
303
+ # parameters/production/rabbitmq.yaml
304
+ spring.rabbitmq.host: ${RABBITMQ_HOST}
305
+ ```
306
+
307
+ ---
308
+
309
+ ## How It Works
310
+
311
+ ### 1. Command Execution Flow
312
+
313
+ ```
314
+ User runs command
315
+
316
+ Validates eva project
317
+
318
+ Checks RabbitMQ client installed
319
+
320
+ Validates module exists
321
+
322
+ Reads available queues from rabbitmq.yaml
323
+
324
+ Prompts user to select queues (multi-select)
325
+
326
+ For each selected queue:
327
+ ├── Generate listener class name (e.g., NotificationUserCreated = Notification + UserCreated)
328
+ ├── Generate bean name (e.g., notificationUserCreatedListener)
329
+ ├── Check if listener class already exists
330
+ │ ├── YES → Skip (show warning)
331
+ │ └── NO → Create new listener class
332
+ └── Generate handle() method with @RabbitListener + manual ack
333
+ ```
334
+
335
+ ### 2. Runtime Event Processing Flow
336
+
337
+ ```
338
+ RabbitMQ queue receives message
339
+
340
+ Spring AMQP delivers raw Message to listener
341
+
342
+ ObjectMapper deserializes body to EventEnvelope<Map<String, Object>>
343
+ ├── Deserialization fails → basicNack (→ DLQ)
344
+ └── Success → continue
345
+
346
+ Extract data from event.data()
347
+
348
+ Create command/query object
349
+
350
+ Dispatch to UseCaseMediator
351
+
352
+ Use case processes event
353
+
354
+ Call channel.basicAck() to acknowledge
355
+ ↓ (on error)
356
+ Call channel.basicNack(deliveryTag, false, false) → message goes to DLQ
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Error Handling
362
+
363
+ ### Manual Acknowledgment
364
+
365
+ Unlike Kafka (which uses `Acknowledgment.acknowledge()`), RabbitMQ listeners use **channel-level acknowledgment**:
366
+
367
+ ```java
368
+ // Success — remove from queue
369
+ channel.basicAck(deliveryTag, false);
370
+
371
+ // Failure — send to DLQ (do NOT requeue)
372
+ channel.basicNack(deliveryTag, false, false);
373
+ ```
374
+
375
+ ### Deserialization Errors
376
+
377
+ Fatal deserialization errors (malformed JSON) are immediately nack'd to the DLQ — no retry:
378
+
379
+ ```java
380
+ } catch (JsonProcessingException e) {
381
+ log.error("Fatal deserialization error — sending to DLQ: {}", e.getMessage());
382
+ channel.basicNack(deliveryTag, false, false);
383
+ return;
384
+ }
385
+ ```
386
+
387
+ ### Dead-Letter Queue (DLQ)
388
+
389
+ Each queue has a companion `.dlq` queue. Messages that are nack'd arrive in the DLQ for inspection:
390
+
391
+ ```
392
+ [user.user-created] → nack → [user.user-created.dlq]
393
+ ```
394
+
395
+ Monitor DLQ in the RabbitMQ Management UI: `http://localhost:15672`
396
+
397
+ ### Retry Configuration
398
+
399
+ Retry is configured at the Spring AMQP level in `rabbitmq.yaml`:
400
+
401
+ ```yaml
402
+ spring.rabbitmq.listener.simple.retry:
403
+ enabled: true
404
+ max-attempts: 3
405
+ initial-interval: 1500
406
+ ```
407
+
408
+ ---
409
+
410
+ ## Best Practices
411
+
412
+ ### ✅ DO
413
+
414
+ 1. **Always acknowledge after successful processing**
415
+ ```java
416
+ channel.basicAck(deliveryTag, false);
417
+ ```
418
+
419
+ 2. **Nack to DLQ on unrecoverable errors**
420
+ ```java
421
+ channel.basicNack(deliveryTag, false, false); // requeue=false → DLQ
422
+ ```
423
+
424
+ 3. **Delegate to use cases via UseCaseMediator**
425
+ ```java
426
+ useCaseMediator.dispatch(new ProcessOrderCommand(data));
427
+ ```
428
+
429
+ 4. **Handle deserialization separately** from business logic errors
430
+
431
+ 5. **Use idempotent operations** — same message processed multiple times = same result
432
+
433
+ ### ❌ DON'T
434
+
435
+ 1. **Don't acknowledge before processing**
436
+ ```java
437
+ channel.basicAck(deliveryTag, false); // ❌ Don't do this first
438
+ processEvent(event); // If this fails, message is lost
439
+ ```
440
+
441
+ 2. **Don't requeue indefinitely**
442
+ ```java
443
+ channel.basicNack(deliveryTag, false, true); // ❌ requeue=true creates infinite loop
444
+ ```
445
+
446
+ 3. **Don't catch and swallow exceptions**
447
+ ```java
448
+ try {
449
+ processEvent(event);
450
+ } catch (Exception e) {
451
+ e.printStackTrace();
452
+ channel.basicAck(deliveryTag, false); // ❌ Message lost on error
453
+ }
454
+ ```
455
+
456
+ 4. **Each listener class handles one queue** — Single Responsibility Principle
457
+
458
+ 5. **Add new listeners without modifying existing code** — Open/Closed Principle
459
+
460
+ ---
461
+
462
+ ## Comparison with Kafka Listeners
463
+
464
+ | Aspect | Kafka | RabbitMQ |
465
+ |--------|-------|----------|
466
+ | Annotation | `@KafkaListener(topics = ...)` | `@RabbitListener(queues = ...)` |
467
+ | Acknowledgment | `Acknowledgment.acknowledge()` | `channel.basicAck(tag, false)` |
468
+ | Error routing | Retry + no action | `channel.basicNack()` → DLQ |
469
+ | Deserialization | `EventEnvelope<Map>` automatic | `Message` body + manual `ObjectMapper` |
470
+ | Config file | `kafka.yaml` (`topics:`) | `rabbitmq.yaml` (`queues:`) |
471
+ | Listener directory | `kafkaListener/` | `rabbitListener/` |
472
+ | Class naming | `{Module}{Topic}Listener` | `{Module}{Queue}Listener` |
473
+
474
+ ---
475
+
476
+ ## Common Use Cases
477
+
478
+ ### 1. Notification Service
479
+
480
+ ```bash
481
+ eva g rabbitmq-listener notification
482
+ # ✓ user-created (send welcome email)
483
+ # ✓ order-placed (send order confirmation)
484
+ # ✓ payment-processed (send payment receipt)
485
+ ```
486
+
487
+ ### 2. Analytics/Audit Service
488
+
489
+ ```bash
490
+ eva g rabbitmq-listener analytics
491
+ # ✓ user-created
492
+ # ✓ order-placed
493
+ # ✓ order-shipped
494
+ ```
495
+
496
+ ### 3. Saga Orchestration
497
+
498
+ ```bash
499
+ eva g rabbitmq-listener order
500
+ # ✓ payment-processed (complete order)
501
+ # ✓ payment-failed (cancel order)
502
+ ```
503
+
504
+ ---
505
+
506
+ ## Standalone vs domain.yaml Listeners
507
+
508
+ This standalone command generates a **generic stub** listener with a `TODO` comment. For **fully typed** listeners with IntegrationEvent records, typed Commands, and CommandHandlers, use the `listeners[]` section in `domain.yaml` and run `eva g entities <module>`.
509
+
510
+ | Feature | Standalone (`g rabbitmq-listener`) | domain.yaml (`listeners[]`) |
511
+ |---------|------------------------------------|-----------------------------|
512
+ | Template | `RabbitListenerSimple.java.ejs` | `RabbitListenerClass.java.ejs` |
513
+ | Typed fields | No — generic `Map<String, Object>` | Yes — explicit field extraction |
514
+ | IntegrationEvent | Not generated | Generated |
515
+ | Command + Handler | Not generated | Generated |
516
+ | Infrastructure beans | Not generated | Generated (exchange + queue + binding) |
517
+ | Use case | Manual implementation | Scaffold with CommandHandler |
518
+
519
+ ---
520
+
521
+ ## Troubleshooting
522
+
523
+ ### ❌ "RabbitMQ client is not installed"
524
+
525
+ ```bash
526
+ eva add rabbitmq-client
527
+ ```
528
+
529
+ ### ❌ "No queues found in rabbitmq.yaml"
530
+
531
+ Generate queues first:
532
+ ```bash
533
+ eva g rabbitmq-event user user-created
534
+ ```
535
+
536
+ Or manually add to `rabbitmq.yaml`:
537
+ ```yaml
538
+ queues:
539
+ user-created: user.user-created
540
+ ```
541
+
542
+ ### ❌ "Module not found"
543
+
544
+ ```bash
545
+ eva add module <module-name>
546
+ ```
547
+
548
+ ### ❌ Messages not being consumed
549
+
550
+ **Checklist:**
551
+ 1. RabbitMQ server is running (`docker-compose up -d`)
552
+ 2. Queue exists in RabbitMQ (check Management UI: `http://localhost:15672`)
553
+ 3. Queue name in `rabbitmq.yaml` matches actual RabbitMQ queue
554
+ 4. No deserialization errors in application logs
555
+ 5. Check DLQ for nack'd messages
556
+
557
+ ### ❌ Duplicate message processing
558
+
559
+ Ensure idempotent operations or use message deduplication:
560
+
561
+ ```java
562
+ @RabbitListener(queues = "${queues.user-created}")
563
+ public void handle(Message message, Channel channel) throws IOException {
564
+ // ...
565
+ String eventId = (String) event.data().get("eventId");
566
+ if (eventRepository.existsByEventId(eventId)) {
567
+ channel.basicAck(deliveryTag, false); // Skip duplicate
568
+ return;
569
+ }
570
+ processEvent(event);
571
+ eventRepository.save(new ProcessedEvent(eventId));
572
+ channel.basicAck(deliveryTag, false);
573
+ }
574
+ ```
575
+
576
+ ---
577
+
578
+ ## Next Steps After Generation
579
+
580
+ 1. **Implement processing logic** in generated `handle()` method
581
+ 2. **Create use cases** for event processing
582
+ 3. **Add error handling** with proper nack → DLQ routing
583
+ 4. **Monitor DLQ** via RabbitMQ Management UI
584
+ 5. **Write integration tests** with `@RabbitIntegrationTest` or Testcontainers
585
+ 6. **Configure concurrency** for scaling (`spring.rabbitmq.listener.simple.concurrency`)
586
+
587
+ ---
588
+
589
+ ## Related Commands
590
+
591
+ - [`generate rabbitmq-event`](./GENERATE_RABBITMQ_EVENT.md) — Create RabbitMQ event publisher
592
+ - [`generate kafka-listener`](./GENERATE_KAFKA_LISTENER.md) — Kafka equivalent of this command
593
+ - [`generate usecase`](./GENERATE_USECASE.md) — Create use cases to process events
594
+ - [`add module`](./ADD_MODULE.md) — Create new module
595
+ - [RabbitMQ Production Config](../RABBITMQ_PRODUCTION_CONFIG.md) — Production-ready configuration reference
@@ -36,7 +36,10 @@ eva g temporal-flow order
36
36
  - `application/usecases/ProcessOrderWorkFlow.java` — `@WorkflowInterface`
37
37
  - `application/usecases/ProcessOrderWorkFlowImpl.java` — implementation with Saga
38
38
  - `application/usecases/ProcessOrderWorkFlowService.java` — Spring service facade
39
- - Patches `shared/infrastructure/configurations/TemporalConfig.java` to register `ProcessOrderWorkFlowImpl`
39
+ - `domain/interfaces/OrderHeavyActivity.java` module-scoped marker interface
40
+ - `domain/interfaces/OrderLightActivity.java` — module-scoped marker interface
41
+ - `infrastructure/configurations/OrderTemporalWorkerConfig.java` — module worker registration
42
+ - Appends `ORDER` queue section to `temporal.yaml`
40
43
 
41
44
  ### Example 2: Payment workflow
42
45
 
@@ -49,7 +52,10 @@ eva g temporal-flow payment
49
52
  - `application/usecases/ProcessPaymentWorkFlow.java`
50
53
  - `application/usecases/ProcessPaymentWorkFlowImpl.java`
51
54
  - `application/usecases/ProcessPaymentWorkFlowService.java`
52
- - Patches `TemporalConfig.java`
55
+ - `domain/interfaces/PaymentHeavyActivity.java`
56
+ - `domain/interfaces/PaymentLightActivity.java`
57
+ - `infrastructure/configurations/PaymentTemporalWorkerConfig.java`
58
+ - Appends `PAYMENT` queue section to `temporal.yaml`
53
59
 
54
60
  ### Example 3: Multiple workflows in the same module
55
61
 
@@ -63,7 +69,7 @@ eva g temporal-flow order
63
69
  # refund-order → generates RefundOrder files
64
70
  ```
65
71
 
66
- Each run appends a new `registerWorkflowImplementationTypes(...)` entry to `TemporalConfig.java` without duplicating existing registrations.
72
+ Each run appends a new `registerWorkflowImplementationTypes(...)` entry to `OrderTemporalWorkerConfig.java` without duplicating existing registrations.
67
73
 
68
74
  ## 📦 Generated Code Structure
69
75
 
@@ -113,10 +119,10 @@ public class ProcessOrderWorkFlowImpl implements ProcessOrderWorkFlow {
113
119
 
114
120
  private Saga saga = new Saga(sagaOptions);
115
121
 
116
- // Light activities (<30 s) — routed to LIGHT_TASK_QUEUE
122
+ // Light activities (<30 s) — routed to ORDER_LIGHT_TASK_QUEUE
117
123
  private final ActivityOptions lightActivityOptions = ActivityOptions.newBuilder()
118
124
  .setStartToCloseTimeout(Duration.ofSeconds(30))
119
- .setTaskQueue("LIGHT_TASK_QUEUE")
125
+ .setTaskQueue("ORDER_LIGHT_TASK_QUEUE")
120
126
  .setRetryOptions(
121
127
  RetryOptions.newBuilder()
122
128
  .setMaximumAttempts(2)
@@ -126,10 +132,10 @@ public class ProcessOrderWorkFlowImpl implements ProcessOrderWorkFlow {
126
132
  .build()
127
133
  ).build();
128
134
 
129
- // Heavy activities (up to 2 min) — routed to HEAVY_TASK_QUEUE
135
+ // Heavy activities (up to 2 min) — routed to ORDER_HEAVY_TASK_QUEUE
130
136
  private final ActivityOptions heavyActivityOptions = ActivityOptions.newBuilder()
131
137
  .setStartToCloseTimeout(Duration.ofSeconds(120))
132
- .setTaskQueue("HEAVY_TASK_QUEUE")
138
+ .setTaskQueue("ORDER_HEAVY_TASK_QUEUE")
133
139
  .setRetryOptions(
134
140
  RetryOptions.newBuilder()
135
141
  .setMaximumAttempts(2)
@@ -179,7 +185,7 @@ public class ProcessOrderWorkFlowService {
179
185
 
180
186
  private final WorkflowClient workflowClient;
181
187
 
182
- @Value("${temporal.flow-queue}")
188
+ @Value("${temporal.modules.order.flow-queue}")
183
189
  private String flowQueue;
184
190
 
185
191
  // ... constructor injection
@@ -214,7 +220,7 @@ public class ProcessOrderWorkFlowService {
214
220
  }
215
221
  ```
216
222
 
217
- ### TemporalConfig.java — Auto-patched Entry
223
+ ### OrderTemporalWorkerConfig.java — Auto-generated
218
224
 
219
225
  ```java
220
226
  // registered automatically by eva g temporal-flow
@@ -223,11 +229,45 @@ workflowWorker.registerWorkflowImplementationTypes(ProcessOrderWorkFlowImpl.clas
223
229
 
224
230
  ## 🏗️ Queue Architecture
225
231
 
232
+ Queues are **module-scoped** — each module gets its own set of queues prefixed with the module name in SCREAMING_SNAKE_CASE:
233
+
226
234
  | Queue | Purpose |
227
235
  |-------|---------|
228
- | `FLOW_QUEUE` | Workflow orchestration (WorkFlowImpl runs here) |
229
- | `LIGHT_TASK_QUEUE` | Fast activities (< 30 s), injected via `lightActivityOptions` |
230
- | `HEAVY_TASK_QUEUE` | Long-running activities (up to 2 min), injected via `heavyActivityOptions` |
236
+ | `{MODULE}_WORKFLOW_QUEUE` | Workflow orchestration (WorkFlowImpl runs here) |
237
+ | `{MODULE}_LIGHT_TASK_QUEUE` | Fast activities (< 30 s), injected via `lightActivityOptions` |
238
+ | `{MODULE}_HEAVY_TASK_QUEUE` | Long-running activities (up to 2 min), injected via `heavyActivityOptions` |
239
+
240
+ For example, the `order` module generates:
241
+ - `ORDER_WORKFLOW_QUEUE`
242
+ - `ORDER_LIGHT_TASK_QUEUE`
243
+ - `ORDER_HEAVY_TASK_QUEUE`
244
+
245
+ The `payment` module generates:
246
+ - `PAYMENT_WORKFLOW_QUEUE`
247
+ - `PAYMENT_LIGHT_TASK_QUEUE`
248
+ - `PAYMENT_HEAVY_TASK_QUEUE`
249
+
250
+ This ensures each module's workers are isolated and can be scaled independently.
251
+
252
+ ### temporal.yaml (auto-updated)
253
+
254
+ ```yaml
255
+ temporal:
256
+ service-url: localhost:7233
257
+ namespace: default
258
+ number-flow-worker: 10
259
+ number-heavy-worker: 10
260
+ number-light-worker: 10
261
+ modules:
262
+ order:
263
+ flow-queue: ORDER_WORKFLOW_QUEUE
264
+ heavy-queue: ORDER_HEAVY_TASK_QUEUE
265
+ light-queue: ORDER_LIGHT_TASK_QUEUE
266
+ payment:
267
+ flow-queue: PAYMENT_WORKFLOW_QUEUE
268
+ heavy-queue: PAYMENT_HEAVY_TASK_QUEUE
269
+ light-queue: PAYMENT_LIGHT_TASK_QUEUE
270
+ ```
231
271
 
232
272
  Activity stubs created inside the workflow must pass the matching options object — see [GENERATE_TEMPORAL_ACTIVITY.md](./GENERATE_TEMPORAL_ACTIVITY.md).
233
273