eva4j 1.0.17 → 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 (134) hide show
  1. package/AGENTS.md +2 -0
  2. package/DOMAIN_YAML_GUIDE.md +3 -1
  3. package/QUICK_REFERENCE.md +8 -4
  4. package/bin/eva4j.js +70 -2
  5. package/config/defaults.json +1 -0
  6. package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
  7. package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
  8. package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
  9. package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
  10. package/docs/commands/EVALUATE_SYSTEM.md +272 -8
  11. package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
  12. package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
  13. package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
  14. package/docs/commands/INDEX.md +27 -3
  15. package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
  16. package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
  17. package/docs/prototype/system/RISKS.md +277 -0
  18. package/docs/prototype/system/customers.yaml +133 -0
  19. package/docs/prototype/system/inventory.yaml +109 -0
  20. package/docs/prototype/system/notifications.yaml +131 -0
  21. package/docs/prototype/system/orders.yaml +241 -0
  22. package/docs/prototype/system/payments.yaml +256 -0
  23. package/docs/prototype/system/products.yaml +168 -0
  24. package/docs/prototype/system/system.yaml +269 -0
  25. package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
  26. package/examples/domain-read-models.yaml +2 -2
  27. package/examples/system/customer.yaml +89 -0
  28. package/examples/system/orders.yaml +119 -0
  29. package/examples/system/product.yaml +27 -0
  30. package/examples/system/system.yaml +80 -0
  31. package/package.json +1 -1
  32. package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
  33. package/src/agents/design-gap-analyst.agent.md +383 -0
  34. package/src/agents/design-reviewer-temporal.agent.md +412 -0
  35. package/src/agents/design-reviewer.agent.md +31 -5
  36. package/src/agents/implement-use-cases.prompt.md +179 -0
  37. package/src/agents/ux-gap-analyst.agent.md +412 -0
  38. package/src/commands/add-rabbitmq-client.js +261 -0
  39. package/src/commands/add-temporal-client.js +22 -2
  40. package/src/commands/build.js +267 -11
  41. package/src/commands/evaluate-system.js +700 -13
  42. package/src/commands/generate-entities.js +308 -16
  43. package/src/commands/generate-rabbitmq-event.js +665 -0
  44. package/src/commands/generate-rabbitmq-listener.js +205 -0
  45. package/src/commands/generate-temporal-activity.js +968 -34
  46. package/src/commands/generate-temporal-flow.js +95 -38
  47. package/src/commands/generate-temporal-system.js +708 -0
  48. package/src/skills/build-system-yaml/SKILL.md +222 -2
  49. package/src/skills/build-system-yaml/references/domain-yaml-spec.md +50 -4
  50. package/src/skills/build-system-yaml/references/module-spec.md +57 -8
  51. package/src/skills/build-temporal-system/SKILL.md +752 -0
  52. package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
  53. package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
  54. package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
  55. package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
  56. package/src/skills/implement-use-case/SKILL.md +350 -0
  57. package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
  58. package/src/skills/requirements-elicitation/SKILL.md +228 -0
  59. package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
  60. package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
  61. package/src/utils/bounded-context-diagram.js +844 -0
  62. package/src/utils/domain-validator.js +266 -4
  63. package/src/utils/naming.js +10 -0
  64. package/src/utils/system-validator.js +169 -11
  65. package/src/utils/system-yaml-parser.js +318 -0
  66. package/src/utils/temporal-validator.js +497 -0
  67. package/src/utils/yaml-to-entity.js +10 -7
  68. package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
  69. package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -2
  70. package/templates/aggregate/JpaEntity.java.ejs +2 -2
  71. package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
  72. package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
  73. package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
  74. package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
  75. package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
  76. package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
  77. package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
  78. package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
  79. package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
  80. package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
  81. package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
  82. package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
  83. package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
  84. package/templates/base/root/AGENTS.md.ejs +1 -1
  85. package/templates/crud/EndpointsController.java.ejs +1 -1
  86. package/templates/crud/ScaffoldCommand.java.ejs +5 -2
  87. package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
  88. package/templates/crud/ScaffoldQuery.java.ejs +5 -2
  89. package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
  90. package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
  91. package/templates/evaluate/report.html.ejs +1447 -90
  92. package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
  93. package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
  94. package/templates/ports/PortAclMapper.java.ejs +35 -0
  95. package/templates/ports/PortFeignAdapter.java.ejs +7 -22
  96. package/templates/ports/PortFeignClient.java.ejs +4 -0
  97. package/templates/ports/PortResponseDto.java.ejs +1 -1
  98. package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
  99. package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
  100. package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
  101. package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
  102. package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
  103. package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
  104. package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
  105. package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
  106. package/templates/read-model/ReadModelJpa.java.ejs +1 -1
  107. package/templates/read-model/ReadModelJpaRepository.java.ejs +2 -0
  108. package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
  109. package/templates/read-model/ReadModelRepositoryImpl.java.ejs +9 -5
  110. package/templates/read-model/ReadModelSyncHandler.java.ejs +2 -0
  111. package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
  112. package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
  113. package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
  114. package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
  115. package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
  116. package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
  117. package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
  118. package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
  119. package/templates/temporal-activity/NestedType.java.ejs +12 -0
  120. package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
  121. package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
  122. package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
  123. package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
  124. package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
  125. package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
  126. package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
  127. package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
  128. package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
  129. package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
  130. package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
  131. package/COMMAND_EVALUATION.md +0 -911
  132. package/test-c2010.js +0 -49
  133. package/test-update-compat.js +0 -109
  134. package/test-update-lifecycle.js +0 -121
@@ -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
+ }
@@ -34,7 +34,7 @@ import lombok.Setter;
34
34
  * <p>
35
35
  * No audit fields. The @Id mirrors the source entity's ID (not auto-generated).
36
36
  */
37
- @Entity
37
+ @Entity(name = "<%= moduleName %>_<%= jpaEntityName %>")
38
38
  @Table(name = "<%= tableName %>")
39
39
  @Getter
40
40
  @Setter
@@ -2,10 +2,12 @@ package <%= packageName %>.<%= moduleName %>.infrastructure.database.repositorie
2
2
 
3
3
  import <%= packageName %>.<%= moduleName %>.infrastructure.database.entities.<%= jpaEntityName %>;
4
4
  import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.stereotype.Repository;
5
6
 
6
7
  /**
7
8
  * Spring Data JPA repository for the <%= domainClassName %> read model.
8
9
  * Table: <%= tableName %>.
9
10
  */
11
+ @Repository("<%= moduleName %>.<%= jpaRepositoryName %>")
10
12
  public interface <%= jpaRepositoryName %> extends JpaRepository<<%= jpaEntityName %>, String> {
11
13
  }
@@ -0,0 +1,71 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.rabbitListener;
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.core.type.TypeReference;
11
+ import com.fasterxml.jackson.databind.ObjectMapper;
12
+ import com.rabbitmq.client.Channel;
13
+ import org.springframework.amqp.core.Message;
14
+ import org.springframework.amqp.rabbit.annotation.RabbitListener;
15
+ import org.springframework.stereotype.Component;
16
+
17
+ import java.io.IOException;
18
+ import java.util.Map;
19
+ <% if (isUpsert) { %>
20
+ <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
21
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
22
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
23
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
24
+ <% if (needsBigDecimal) { %>import java.math.BigDecimal;
25
+ <% } %><% if (needsLocalDate) { %>import java.time.LocalDate;
26
+ import java.time.LocalDateTime;
27
+ import java.time.LocalTime;
28
+ <% } %><% if (needsInstant) { %>import java.time.Instant;
29
+ <% } %><% if (needsUUID) { %>import java.util.UUID;
30
+ <% } %>
31
+ <% } %>
32
+
33
+ /**
34
+ * RabbitMQ listener for read model sync — queue: <%= topicConstant %>.
35
+ * Delegates to <%= syncHandlerName %>.on<%= eventBaseName %>().
36
+ */
37
+ @Component("<%= moduleName %>.<%= listenerClassName %>")
38
+ public class <%= listenerClassName %> {
39
+
40
+ private final <%= syncHandlerName %> syncHandler;
41
+ private final ObjectMapper objectMapper;
42
+
43
+ public <%= listenerClassName %>(<%= syncHandlerName %> syncHandler, ObjectMapper objectMapper) {
44
+ this.syncHandler = syncHandler;
45
+ this.objectMapper = objectMapper;
46
+ }
47
+
48
+ @RabbitListener(queues = "<%= topicSpringProperty %>")
49
+ public void handle(Message message, Channel channel) throws IOException {
50
+ EventEnvelope<Map<String, Object>> event = objectMapper.readValue(
51
+ message.getBody(),
52
+ new TypeReference<EventEnvelope<Map<String, Object>>>() {});
53
+
54
+ <% if (isUpsert) { %>
55
+ <% (fields || []).forEach(f => { %>
56
+ <%- f.javaType %> <%= f.name %> = objectMapper.convertValue(event.data().get("<%= f.payloadKey || f.name %>"), <%- f.javaType %>.class);
57
+ <% }); %>
58
+
59
+ syncHandler.on<%= eventBaseName %>(new <%= integrationEventClassName %>(
60
+ <% (fields || []).forEach((f, i) => { %>
61
+ <%= f.name %><%= i < fields.length - 1 ? ',' : '' %>
62
+ <% }); %>
63
+ ));
64
+ <% } else { %>
65
+ String id = objectMapper.convertValue(event.data().get("<%= (fields[0] && fields[0].payloadKey) || 'id' %>"), String.class);
66
+ syncHandler.on<%= eventBaseName %>(id);
67
+ <% } %>
68
+
69
+ channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
70
+ }
71
+ }
@@ -27,7 +27,7 @@ import java.util.Optional;
27
27
  * Repository implementation for the <%= domainClassName %> read model.
28
28
  * Maps between domain read model and JPA entity.
29
29
  */
30
- @Repository
30
+ @Repository("<%= moduleName %>.<%= repositoryImplName %>")
31
31
  public class <%= repositoryImplName %> implements <%= repositoryName %> {
32
32
 
33
33
  private final <%= jpaRepositoryName %> jpaRepository;
@@ -38,11 +38,15 @@ public class <%= repositoryImplName %> implements <%= repositoryName %> {
38
38
 
39
39
  @Override
40
40
  public void upsert(<%= domainClassName %> readModel) {
41
- <%= jpaEntityName %> entity = <%= jpaEntityName %>.builder()
42
- <% fields.forEach(field => { %>
43
- .<%= field.name %>(readModel.get<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>())
41
+ <%= jpaEntityName %> entity = jpaRepository.findById(readModel.getId())
42
+ .orElseGet(() -> {
43
+ <%= jpaEntityName %> newEntity = new <%= jpaEntityName %>();
44
+ newEntity.setId(readModel.getId());
45
+ return newEntity;
46
+ });
47
+ <% fields.filter(f => f.name !== 'id').forEach(field => { %>
48
+ entity.set<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>(readModel.get<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>());
44
49
  <% }); %>
45
- .build();
46
50
  jpaRepository.save(entity);
47
51
  }
48
52
 
@@ -8,6 +8,7 @@ import <%= packageName %>.<%= moduleName %>.domain.models.readmodels.<%= domainC
8
8
  import <%= packageName %>.<%= moduleName %>.domain.repositories.<%= repositoryName %>;
9
9
  import <%= packageName %>.shared.domain.annotations.ApplicationComponent;
10
10
  import <%= packageName %>.shared.domain.annotations.LogExceptions;
11
+ import org.springframework.stereotype.Component;
11
12
 
12
13
  /**
13
14
  * Sync handler for the <%= domainClassName %> read model.
@@ -16,6 +17,7 @@ import <%= packageName %>.shared.domain.annotations.LogExceptions;
16
17
  * One method per syncedBy event — Kafka listeners delegate directly to these methods.
17
18
  */
18
19
  @ApplicationComponent
20
+ @Component("<%= moduleName %>.<%= syncHandlerName %>")
19
21
  public class <%= syncHandlerName %> {
20
22
 
21
23
  private final <%= repositoryName %> repository;
@@ -12,7 +12,7 @@ import org.springframework.kafka.core.KafkaTemplate;
12
12
  import org.springframework.kafka.listener.ContainerProperties;
13
13
  import org.springframework.kafka.listener.DeadLetterPublishingRecoverer;
14
14
  import org.springframework.kafka.listener.DefaultErrorHandler;
15
- import org.springframework.util.backoff.FixedBackOff;
15
+ import org.springframework.util.backoff.ExponentialBackOff;
16
16
 
17
17
 
18
18
  @Configuration
@@ -20,11 +20,23 @@ import org.springframework.util.backoff.FixedBackOff;
20
20
  public class KafkaConfig {
21
21
 
22
22
  @Value("${spring.kafka.listener.retry.max-attempts}")
23
- private long maxAttempts;
23
+ private int maxAttempts;
24
24
 
25
25
  @Value("${spring.kafka.listener.retry.backoff-delay}")
26
26
  private long backoffDelay;
27
27
 
28
+ @Value("${spring.kafka.listener.retry.backoff-multiplier:1.0}")
29
+ private double backoffMultiplier;
30
+
31
+ @Value("${spring.kafka.listener.retry.backoff-max-delay:30000}")
32
+ private long backoffMaxDelay;
33
+
34
+ @Value("${kafka.topic-defaults.partitions:3}")
35
+ private int defaultPartitions;
36
+
37
+ @Value("${kafka.topic-defaults.replicas:1}")
38
+ private short defaultReplicas;
39
+
28
40
  @Bean
29
41
  public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(
30
42
  ConsumerFactory<String, String> consumerFactory,
@@ -39,8 +51,10 @@ public class KafkaConfig {
39
51
  // Redirige al topic original + ".DLT"
40
52
  (record, ex) -> new TopicPartition(record.topic() + ".DLT", record.partition())
41
53
  );
42
- // Configura reintentos:
43
- FixedBackOff backOff = new FixedBackOff(backoffDelay, maxAttempts);
54
+ // Configura reintentos con backoff exponencial
55
+ ExponentialBackOff backOff = new ExponentialBackOff(backoffDelay, backoffMultiplier);
56
+ backOff.setMaxInterval(backoffMaxDelay);
57
+ backOff.setMaxAttempts(maxAttempts);
44
58
  // Manejador de errores
45
59
  DefaultErrorHandler errorHandler = new DefaultErrorHandler(recoverer, backOff);
46
60
  factory.setCommonErrorHandler(errorHandler);