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
@@ -3,5 +3,5 @@
3
3
 
4
4
  @Bean
5
5
  public NewTopic <%= beanMethodName %>() {
6
- return new NewTopic(<%= valueFieldName %>, <%= partitions %>, (short) <%= replicas %>);
6
+ return new NewTopic(<%= valueFieldName %>, defaultPartitions, defaultReplicas);
7
7
  }
@@ -12,7 +12,7 @@ import org.slf4j.MDC;
12
12
  public class <%= modulePascalCase %>KafkaMessageBroker implements MessageBroker {
13
13
 
14
14
  @Value("<%= topicSpringProperty %>")
15
- private String <%= topicNameKebab.replace(/-/g, '') %>Topic;
15
+ private String <%= topicNameCamel %>Topic;
16
16
 
17
17
  private final KafkaTemplate<String, Object> kafkaTemplate;
18
18
 
@@ -23,10 +23,10 @@ public class <%= modulePascalCase %>KafkaMessageBroker implements MessageBroker
23
23
  @Override
24
24
  public void publish<%= eventClassName %>(<%= eventClassName %> event) {
25
25
  EventEnvelope<<%= eventClassName %>> envelope = EventEnvelope.of(
26
- <%= topicNameKebab.replace(/-/g, '') %>Topic,
26
+ <%= topicNameCamel %>Topic,
27
27
  event,
28
28
  MDC.get("correlationId")
29
29
  );
30
- kafkaTemplate.send(<%= topicNameKebab.replace(/-/g, '') %>Topic, envelope);
30
+ kafkaTemplate.send(<%= topicNameCamel %>Topic, envelope);
31
31
  }
32
32
  }
@@ -0,0 +1,35 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.adapters.<%= adapterPackage %>;
2
+
3
+ import org.springframework.stereotype.Component;
4
+ <% const allMethods = methods || []; %>
5
+ <% const responseMethods = allMethods.filter(m => m.hasResponse); %>
6
+ <% /* ACL: import domain models */ %>
7
+ <% const uniqueDomainTypes = [...new Set(responseMethods.map(m => m.domainType))]; %>
8
+ <% uniqueDomainTypes.forEach(dt => { %>import <%= packageName %>.<%= moduleName %>.domain.models.<%= adapterPackage %>.<%= dt %>;
9
+ <% }); %>
10
+ <% /* Import infra DTOs from dtos sub-package */ %>
11
+ <% const uniqueInfraDtos = [...new Set(responseMethods.map(m => m.infraDtoName))]; %>
12
+ <% uniqueInfraDtos.forEach(dto => { %>import <%= packageName %>.<%= moduleName %>.infrastructure.adapters.<%= adapterPackage %>.dtos.<%= dto %>;
13
+ <% }); %>
14
+
15
+ /**
16
+ * ACL (Anti-Corruption Layer) mapper for {@link <%= serviceName %>}.
17
+ *
18
+ * <p>Maps infrastructure DTOs (shaped by the external service) to domain models.
19
+ * If the external API changes, only these methods need to be updated;
20
+ * domain logic using the domain models remains untouched.
21
+ */
22
+ @Component("<%= moduleName %>.<%= aclMapperClassName %>")
23
+ public class <%= aclMapperClassName %> {
24
+ <% responseMethods.forEach(m => { %>
25
+
26
+ public <%- m.domainType %> to<%= m.domainType %>(<%= m.infraDtoName %> dto) {
27
+ if (dto == null) return null;
28
+ return new <%= m.domainType %>(
29
+ <% m.fields.forEach((f, i) => { %>
30
+ dto.<%= f.name %>()<%= i < m.fields.length - 1 ? ',' : '' %>
31
+ <% }); %>
32
+ );
33
+ }
34
+ <% }); %>
35
+ }
@@ -20,9 +20,11 @@ import java.util.stream.Collectors;
20
20
  public class <%= feignAdapterClassName %> implements <%= serviceName %> {
21
21
 
22
22
  private final <%= feignClientClassName %> feignClient;
23
+ private final <%= aclMapperClassName %> aclMapper;
23
24
 
24
- public <%= feignAdapterClassName %>(<%= feignClientClassName %> feignClient) {
25
+ public <%= feignAdapterClassName %>(<%= feignClientClassName %> feignClient, <%= aclMapperClassName %> aclMapper) {
25
26
  this.feignClient = feignClient;
27
+ this.aclMapper = aclMapper;
26
28
  }
27
29
  <% allMethods.forEach(m => { %>
28
30
  <% /* Public override returns domain model; FeignClient returns infra DTO */ %>
@@ -39,29 +41,12 @@ public class <%= feignAdapterClassName %> implements <%= serviceName %> {
39
41
  <% if (!m.hasResponse) { %>
40
42
  feignClient.<%= m.name %>(<%= args.join(', ') %>);
41
43
  <% } else if (m.returnList) { %>
42
- List<<%= m.infraDtoName %>> dtos = feignClient.<%= m.name %>(<%= args.join(', ') %>);
43
- return dtos.stream().map(dto -> to<%= m.domainType %>(dto)).collect(Collectors.toList());
44
+ return feignClient.<%= m.name %>(<%= args.join(', ') %>).stream()
45
+ .map(aclMapper::to<%= m.domainType %>)
46
+ .collect(Collectors.toList());
44
47
  <% } else { %>
45
- return to<%= m.domainType %>(feignClient.<%= m.name %>(<%= args.join(', ') %>));
48
+ return aclMapper.to<%= m.domainType %>(feignClient.<%= m.name %>(<%= args.join(', ') %>));
46
49
  <% } %>
47
50
  }
48
51
  <% }); %>
49
-
50
- // ── ACL Mappers ─────────────────────────────────────────────────────────
51
- // Maps infra DTOs (shaped by the external service) to domain models.
52
- // If the external API changes, only these methods need to be updated.
53
- <%
54
- // Build one mapper per method that has a response
55
- const responseMethods = allMethods.filter(m => m.hasResponse);
56
- %>
57
- <% responseMethods.forEach(m => { %>
58
- private <%- m.domainType %> to<%= m.domainType %>(<%= m.infraDtoName %> dto) {
59
- if (dto == null) return null;
60
- return new <%= m.domainType %>(
61
- <% m.fields.forEach((f, i) => { %>
62
- dto.<%= f.name %>()<%= i < m.fields.length - 1 ? ',' : '' %>
63
- <% }); %>
64
- );
65
- }
66
- <% }); %>
67
52
  }
@@ -23,6 +23,10 @@ import java.time.LocalTime;
23
23
  <% }); %>
24
24
  <% (nestedTypes || []).forEach(nt => { %>import <%= packageName %>.<%= moduleName %>.application.dtos.<%= nt.name %>;
25
25
  <% }); %>
26
+ <% /* Infra response DTOs live in infrastructure.adapters.{service}.dtos */ %>
27
+ <% const uniqueInfraDtos = [...new Set(allMethods.filter(m => m.hasResponse).map(m => m.infraDtoName))]; %>
28
+ <% uniqueInfraDtos.forEach(dto => { %>import <%= packageName %>.<%= moduleName %>.infrastructure.adapters.<%= adapterPackage %>.dtos.<%= dto %>;
29
+ <% }); %>
26
30
  @FeignClient(
27
31
  name = "<%= feignClientName %>",
28
32
  url = "${<%= baseUrlProperty %>}",
@@ -1,4 +1,4 @@
1
- package <%= packageName %>.<%= moduleName %>.infrastructure.adapters.<%= adapterPackage %>;
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.adapters.<%= adapterPackage %>.dtos;
2
2
  <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
3
3
  <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
4
4
  <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
@@ -0,0 +1,33 @@
1
+ @Value("${queues.<%= topicNameKebab %>}")
2
+ private String <%= valueFieldName %>QueueName;
3
+
4
+ @Value("${routing-keys.<%= topicNameKebab %>}")
5
+ private String <%= valueFieldName %>RoutingKeyValue;
6
+
7
+ @Bean
8
+ public Queue <%= beanMethodName %>Queue() {
9
+ return QueueBuilder.durable(<%= valueFieldName %>QueueName)
10
+ .withArgument("x-dead-letter-exchange", <%= moduleName %>ExchangeName + ".dlx")
11
+ .build();
12
+ }
13
+
14
+ @Bean
15
+ public Binding <%= beanMethodName %>Binding() {
16
+ return BindingBuilder
17
+ .bind(<%= beanMethodName %>Queue())
18
+ .to(<%= moduleName %>Exchange())
19
+ .with(<%= valueFieldName %>RoutingKeyValue);
20
+ }
21
+
22
+ @Bean
23
+ public Queue <%= beanMethodName %>Dlq() {
24
+ return QueueBuilder.durable(<%= valueFieldName %>QueueName + ".dlq").build();
25
+ }
26
+
27
+ @Bean
28
+ public Binding <%= beanMethodName %>DlqBinding() {
29
+ return BindingBuilder
30
+ .bind(<%= beanMethodName %>Dlq())
31
+ .to(<%= moduleName %>DlxExchange())
32
+ .with(<%= valueFieldName %>RoutingKeyValue);
33
+ }
@@ -0,0 +1,12 @@
1
+ @Value("${exchanges.<%= moduleName %>}")
2
+ private String <%= moduleName %>ExchangeName;
3
+
4
+ @Bean
5
+ public TopicExchange <%= moduleName %>Exchange() {
6
+ return new TopicExchange(<%= moduleName %>ExchangeName, true, false);
7
+ }
8
+
9
+ @Bean
10
+ public TopicExchange <%= moduleName %>DlxExchange() {
11
+ return new TopicExchange(<%= moduleName %>ExchangeName + ".dlx", true, false);
12
+ }
@@ -0,0 +1,35 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.adapters.rabbitmqMessageBroker;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.events.<%= eventClassName %>;
4
+ import <%= packageName %>.<%= moduleName %>.application.ports.MessageBroker;
5
+ import <%= packageName %>.shared.infrastructure.eventEnvelope.EventEnvelope;
6
+ import org.springframework.amqp.rabbit.core.RabbitTemplate;
7
+ import org.springframework.beans.factory.annotation.Value;
8
+ import org.springframework.stereotype.Component;
9
+ import org.slf4j.MDC;
10
+
11
+ @Component("<%= moduleCamelCase %>RabbitMessageBroker")
12
+ public class <%= modulePascalCase %>RabbitMessageBroker implements MessageBroker {
13
+
14
+ @Value("${exchanges.<%= moduleName %>}")
15
+ private String exchange;
16
+
17
+ @Value("${routing-keys.<%= topicNameKebab %>}")
18
+ private String <%= topicNameCamel %>RoutingKey;
19
+
20
+ private final RabbitTemplate rabbitTemplate;
21
+
22
+ public <%= modulePascalCase %>RabbitMessageBroker(RabbitTemplate rabbitTemplate) {
23
+ this.rabbitTemplate = rabbitTemplate;
24
+ }
25
+
26
+ @Override
27
+ public void publish<%= eventClassName %>(<%= eventClassName %> event) {
28
+ EventEnvelope<<%= eventClassName %>> envelope = EventEnvelope.of(
29
+ <%= topicNameCamel %>RoutingKey,
30
+ event,
31
+ MDC.get("correlationId")
32
+ );
33
+ rabbitTemplate.convertAndSend(exchange, <%= topicNameCamel %>RoutingKey, envelope);
34
+ }
35
+ }
@@ -0,0 +1,9 @@
1
+ @Override
2
+ public void publish<%= eventClassName %>(<%= eventClassName %> event) {
3
+ EventEnvelope<<%= eventClassName %>> envelope = EventEnvelope.of(
4
+ <%= valueFieldName %>,
5
+ event,
6
+ MDC.get("correlationId")
7
+ );
8
+ rabbitTemplate.convertAndSend(exchange, <%= valueFieldName %>, envelope);
9
+ }
@@ -0,0 +1,33 @@
1
+ @Value("${queues.<%= topicKey %>}")
2
+ private String <%= valueFieldName %>QueueName;
3
+
4
+ @Value("${routing-keys.<%= topicKey %>}")
5
+ private String <%= valueFieldName %>RoutingKeyValue;
6
+
7
+ @Bean
8
+ public Queue <%= beanMethodName %>Queue() {
9
+ return QueueBuilder.durable(<%= valueFieldName %>QueueName)
10
+ .withArgument("x-dead-letter-exchange", <%= producerModule %>ExchangeName + ".dlx")
11
+ .build();
12
+ }
13
+
14
+ @Bean
15
+ public Binding <%= beanMethodName %>Binding() {
16
+ return BindingBuilder
17
+ .bind(<%= beanMethodName %>Queue())
18
+ .to(<%= producerModule %>Exchange())
19
+ .with(<%= valueFieldName %>RoutingKeyValue);
20
+ }
21
+
22
+ @Bean
23
+ public Queue <%= beanMethodName %>Dlq() {
24
+ return QueueBuilder.durable(<%= valueFieldName %>QueueName + ".dlq").build();
25
+ }
26
+
27
+ @Bean
28
+ public Binding <%= beanMethodName %>DlqBinding() {
29
+ return BindingBuilder
30
+ .bind(<%= beanMethodName %>Dlq())
31
+ .to(<%= producerModule %>DlxExchange())
32
+ .with(<%= valueFieldName %>RoutingKeyValue);
33
+ }
@@ -0,0 +1,12 @@
1
+ @Value("${exchanges.<%= producerModule %>}")
2
+ private String <%= producerModule %>ExchangeName;
3
+
4
+ @Bean
5
+ public TopicExchange <%= producerModule %>Exchange() {
6
+ return new TopicExchange(<%= producerModule %>ExchangeName, true, false);
7
+ }
8
+
9
+ @Bean
10
+ public TopicExchange <%= producerModule %>DlxExchange() {
11
+ return new TopicExchange(<%= producerModule %>ExchangeName + ".dlx", true, false);
12
+ }
@@ -0,0 +1,82 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.rabbitListener;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= commandClassName %>;
4
+ import <%= packageName %>.shared.infrastructure.configurations.useCaseConfig.UseCaseMediator;
5
+ import <%= packageName %>.shared.infrastructure.eventEnvelope.EventEnvelope;
6
+
7
+ import com.fasterxml.jackson.core.type.TypeReference;
8
+ import com.fasterxml.jackson.databind.ObjectMapper;
9
+ import com.fasterxml.jackson.core.JsonProcessingException;
10
+ import com.rabbitmq.client.Channel;
11
+ import org.springframework.amqp.core.Message;
12
+ import org.springframework.amqp.rabbit.annotation.RabbitListener;
13
+ import org.springframework.stereotype.Component;
14
+ import org.slf4j.Logger;
15
+ import org.slf4j.LoggerFactory;
16
+
17
+ import java.io.IOException;
18
+ import java.util.Map;
19
+ <% const hasLists = fields && fields.some(f => f.javaType && f.javaType.startsWith('List')); %>
20
+ <% if (hasLists) { %>import java.util.List;
21
+ <% } %><% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
22
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
23
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
24
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
25
+ <% if (needsBigDecimal) { %>import java.math.BigDecimal;
26
+ <% } %><% if (needsLocalDate) { %>import java.time.LocalDate;
27
+ import java.time.LocalDateTime;
28
+ import java.time.LocalTime;
29
+ <% } %><% if (needsInstant) { %>import java.time.Instant;
30
+ <% } %><% if (needsUUID) { %>import java.util.UUID;
31
+ <% } %><% (nestedTypes || []).forEach(nt => { %>import <%= packageName %>.<%= moduleName %>.application.events.<%= nt.name %>;
32
+ <% }); %>
33
+ /**
34
+ * RabbitMQ listener for queue <%= topicConstant %>.
35
+ * Consumes events produced by: <%= producer %>.
36
+ * Dispatches to use case: <%= useCase %>.
37
+ */
38
+ @Component("<%= moduleName %>.<%= listenerClassName %>")
39
+ public class <%= listenerClassName %> {
40
+
41
+ private static final Logger log = LoggerFactory.getLogger(<%= listenerClassName %>.class);
42
+
43
+ private final UseCaseMediator useCaseMediator;
44
+ private final ObjectMapper objectMapper;
45
+
46
+ public <%= listenerClassName %>(UseCaseMediator useCaseMediator, ObjectMapper objectMapper) {
47
+ this.useCaseMediator = useCaseMediator;
48
+ this.objectMapper = objectMapper;
49
+ }
50
+
51
+ @RabbitListener(queues = "<%= topicSpringProperty %>")
52
+ public void handle(Message message, Channel channel) throws IOException {
53
+ long deliveryTag = message.getMessageProperties().getDeliveryTag();
54
+
55
+ EventEnvelope<Map<String, Object>> event;
56
+ try {
57
+ event = objectMapper.readValue(
58
+ message.getBody(),
59
+ new TypeReference<EventEnvelope<Map<String, Object>>>() {});
60
+ } catch (JsonProcessingException e) {
61
+ log.error("Fatal deserialization error — sending to DLQ: {}", e.getMessage());
62
+ channel.basicNack(deliveryTag, false, false);
63
+ return;
64
+ }
65
+
66
+ <% (fields || []).forEach(f => { %><%
67
+ const listMatch = f.javaType.match(/^List<(.+)>$/);
68
+ if (listMatch) { %>
69
+ <%- f.javaType %> <%= f.name %> = objectMapper.convertValue(
70
+ event.data().get("<%= f.name %>"),
71
+ objectMapper.getTypeFactory().constructCollectionType(List.class, <%= listMatch[1] %>.class));
72
+ <% } else { %>
73
+ <%- f.javaType %> <%= f.name %> = objectMapper.convertValue(event.data().get("<%= f.name %>"), <%- f.javaType %>.class);
74
+ <% } %><% }); %>
75
+ useCaseMediator.dispatch(new <%= commandClassName %>(
76
+ <% (fields || []).forEach((f, i) => { %>
77
+ <%= f.name %><%= i < fields.length - 1 ? ',' : '' %>
78
+ <% }); %>
79
+ ));
80
+ channel.basicAck(deliveryTag, false);
81
+ }
82
+ }
@@ -0,0 +1,56 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.rabbitListener;
2
+
3
+ import <%= packageName %>.shared.infrastructure.configurations.useCaseConfig.UseCaseMediator;
4
+ import <%= packageName %>.shared.infrastructure.eventEnvelope.EventEnvelope;
5
+
6
+ import com.fasterxml.jackson.core.type.TypeReference;
7
+ import com.fasterxml.jackson.databind.ObjectMapper;
8
+ import com.fasterxml.jackson.core.JsonProcessingException;
9
+ import com.rabbitmq.client.Channel;
10
+ import org.springframework.amqp.core.Message;
11
+ import org.springframework.amqp.rabbit.annotation.RabbitListener;
12
+ import org.springframework.stereotype.Component;
13
+ import org.slf4j.Logger;
14
+ import org.slf4j.LoggerFactory;
15
+
16
+ import java.io.IOException;
17
+ import java.util.Map;
18
+
19
+ /**
20
+ * RabbitMQ listener for queue <%= topicValue %>
21
+ */
22
+ @Component("<%= listenerBeanName %>")
23
+ public class <%= listenerClassName %> {
24
+
25
+ private static final Logger log = LoggerFactory.getLogger(<%= listenerClassName %>.class);
26
+
27
+ private final UseCaseMediator useCaseMediator;
28
+ private final ObjectMapper objectMapper;
29
+
30
+ public <%= listenerClassName %>(UseCaseMediator useCaseMediator, ObjectMapper objectMapper) {
31
+ this.useCaseMediator = useCaseMediator;
32
+ this.objectMapper = objectMapper;
33
+ }
34
+
35
+ @RabbitListener(queues = "<%= topicSpringProperty %>")
36
+ public void handle(Message message, Channel channel) throws IOException {
37
+ long deliveryTag = message.getMessageProperties().getDeliveryTag();
38
+
39
+ EventEnvelope<Map<String, Object>> event;
40
+ try {
41
+ event = objectMapper.readValue(
42
+ message.getBody(),
43
+ new TypeReference<EventEnvelope<Map<String, Object>>>() {});
44
+ } catch (JsonProcessingException e) {
45
+ log.error("Fatal deserialization error — sending to DLQ: {}", e.getMessage());
46
+ channel.basicNack(deliveryTag, false, false);
47
+ return;
48
+ }
49
+
50
+ // TODO: Implement event processing logic
51
+ // Example: useCaseMediator.dispatch(new YourCommand(event.data()));
52
+
53
+ channel.basicAck(deliveryTag, false);
54
+ }
55
+
56
+ }
@@ -0,0 +1,46 @@
1
+ package <%= packageName %>.<%= moduleName %>.domain.models.readmodels;
2
+ <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
3
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
4
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
5
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
6
+ <% if (needsBigDecimal) { %>
7
+ import java.math.BigDecimal;
8
+ <% } %>
9
+ <% if (needsLocalDate) { %>
10
+ import java.time.LocalDate;
11
+ import java.time.LocalDateTime;
12
+ import java.time.LocalTime;
13
+ <% } %>
14
+ <% if (needsInstant) { %>
15
+ import java.time.Instant;
16
+ <% } %>
17
+ <% if (needsUUID) { %>
18
+ import java.util.UUID;
19
+ <% } %>
20
+
21
+ /**
22
+ * Local Read Model — projection of data from <%= sourceModule %>/<%= sourceName %>.
23
+ * <p>
24
+ * This class is read-only from this module's perspective.
25
+ * The source module owns mutations; this projection is maintained
26
+ * via domain events (see syncedBy configuration in domain.yaml).
27
+ * <p>
28
+ * No business logic, no setters, no empty constructor.
29
+ */
30
+ public class <%= domainClassName %> {
31
+ <% fields.forEach(field => { %>
32
+ private final <%- field.javaType %> <%= field.name %>;
33
+ <% }); %>
34
+
35
+ public <%= domainClassName %>(<%= fields.map(f => f.javaType + ' ' + f.name).join(', ') %>) {
36
+ <% fields.forEach(field => { %>
37
+ this.<%= field.name %> = <%= field.name %>;
38
+ <% }); %>
39
+ }
40
+ <% fields.forEach(field => { %>
41
+
42
+ public <%- field.javaType %> get<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>() {
43
+ return this.<%= field.name %>;
44
+ }
45
+ <% }); %>
46
+ }
@@ -0,0 +1,58 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.database.entities;
2
+ <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
3
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
4
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
5
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
6
+ <% if (needsBigDecimal) { %>
7
+ import java.math.BigDecimal;
8
+ <% } %>
9
+ <% if (needsLocalDate || hasSoftDelete) { %>
10
+ import java.time.LocalDate;
11
+ import java.time.LocalDateTime;
12
+ import java.time.LocalTime;
13
+ <% } %>
14
+ <% if (needsInstant) { %>
15
+ import java.time.Instant;
16
+ <% } %>
17
+ <% if (needsUUID) { %>
18
+ import java.util.UUID;
19
+ <% } %>
20
+
21
+ import jakarta.persistence.Column;
22
+ import jakarta.persistence.Entity;
23
+ import jakarta.persistence.Id;
24
+ import jakarta.persistence.Table;
25
+ import lombok.AllArgsConstructor;
26
+ import lombok.Builder;
27
+ import lombok.Getter;
28
+ import lombok.NoArgsConstructor;
29
+ import lombok.Setter;
30
+
31
+ /**
32
+ * JPA entity for the local read model — table: <%= tableName %>.
33
+ * Projection of data from <%= sourceModule %>/<%= sourceName %>.
34
+ * <p>
35
+ * No audit fields. The @Id mirrors the source entity's ID (not auto-generated).
36
+ */
37
+ @Entity(name = "<%= moduleName %>_<%= jpaEntityName %>")
38
+ @Table(name = "<%= tableName %>")
39
+ @Getter
40
+ @Setter
41
+ @NoArgsConstructor
42
+ @AllArgsConstructor
43
+ @Builder
44
+ public class <%= jpaEntityName %> {
45
+
46
+ @Id
47
+ private String id;
48
+ <% fields.filter(f => f.name !== 'id').forEach(field => { %>
49
+
50
+ @Column(name = "<%= field.name.replace(/([A-Z])/g, '_$1').toLowerCase() %>")
51
+ private <%- field.javaType %> <%= field.name %>;
52
+ <% }); %>
53
+ <% if (hasSoftDelete) { %>
54
+
55
+ @Column(name = "deleted_at")
56
+ private LocalDateTime deletedAt;
57
+ <% } %>
58
+ }
@@ -0,0 +1,13 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.database.repositories;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.infrastructure.database.entities.<%= jpaEntityName %>;
4
+ import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.stereotype.Repository;
6
+
7
+ /**
8
+ * Spring Data JPA repository for the <%= domainClassName %> read model.
9
+ * Table: <%= tableName %>.
10
+ */
11
+ @Repository("<%= moduleName %>.<%= jpaRepositoryName %>")
12
+ public interface <%= jpaRepositoryName %> extends JpaRepository<<%= jpaEntityName %>, String> {
13
+ }
@@ -0,0 +1,64 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.kafkaListener;
2
+ <% const isUpsert = action === 'UPSERT'; %>
3
+ <% if (isUpsert) { %>
4
+
5
+ import <%= packageName %>.<%= moduleName %>.application.events.<%= integrationEventClassName %>;
6
+ <% } %>
7
+ import <%= packageName %>.<%= moduleName %>.application.usecases.<%= syncHandlerName %>;
8
+ import <%= packageName %>.shared.infrastructure.eventEnvelope.EventEnvelope;
9
+
10
+ import com.fasterxml.jackson.databind.ObjectMapper;
11
+ import org.springframework.kafka.annotation.KafkaListener;
12
+ import org.springframework.kafka.support.Acknowledgment;
13
+ import org.springframework.stereotype.Component;
14
+
15
+ import java.util.Map;
16
+ <% if (isUpsert) { %>
17
+ <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
18
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
19
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
20
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
21
+ <% if (needsBigDecimal) { %>import java.math.BigDecimal;
22
+ <% } %><% if (needsLocalDate) { %>import java.time.LocalDate;
23
+ import java.time.LocalDateTime;
24
+ import java.time.LocalTime;
25
+ <% } %><% if (needsInstant) { %>import java.time.Instant;
26
+ <% } %><% if (needsUUID) { %>import java.util.UUID;
27
+ <% } %>
28
+ <% } %>
29
+
30
+ /**
31
+ * Kafka listener for read model sync — topic: <%= topicConstant %>.
32
+ * Delegates to <%= syncHandlerName %>.on<%= eventBaseName %>().
33
+ */
34
+ @Component("<%= moduleName %>.<%= listenerClassName %>")
35
+ public class <%= listenerClassName %> {
36
+
37
+ private final <%= syncHandlerName %> syncHandler;
38
+ private final ObjectMapper objectMapper;
39
+
40
+ public <%= listenerClassName %>(<%= syncHandlerName %> syncHandler, ObjectMapper objectMapper) {
41
+ this.syncHandler = syncHandler;
42
+ this.objectMapper = objectMapper;
43
+ }
44
+
45
+ @KafkaListener(topics = "<%= topicSpringProperty %>", groupId = "${spring.application.name}-<%= moduleName %>-group")
46
+ public void handle(EventEnvelope<Map<String, Object>> event, Acknowledgment ack) {
47
+ <% if (isUpsert) { %>
48
+ <% (fields || []).forEach(f => { %>
49
+ <%- f.javaType %> <%= f.name %> = objectMapper.convertValue(event.data().get("<%= f.payloadKey || f.name %>"), <%- f.javaType %>.class);
50
+ <% }); %>
51
+
52
+ syncHandler.on<%= eventBaseName %>(new <%= integrationEventClassName %>(
53
+ <% (fields || []).forEach((f, i) => { %>
54
+ <%= f.name %><%= i < fields.length - 1 ? ',' : '' %>
55
+ <% }); %>
56
+ ));
57
+ <% } else { %>
58
+ String id = objectMapper.convertValue(event.data().get("<%= (fields[0] && fields[0].payloadKey) || 'id' %>"), String.class);
59
+ syncHandler.on<%= eventBaseName %>(id);
60
+ <% } %>
61
+
62
+ ack.acknowledge();
63
+ }
64
+ }