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,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
+ }
@@ -0,0 +1,42 @@
1
+ package <%= packageName %>.<%= moduleName %>.domain.repositories;
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
+ import <%= packageName %>.<%= moduleName %>.domain.models.readmodels.<%= domainClassName %>;
22
+
23
+ import java.util.Optional;
24
+
25
+ /**
26
+ * Repository interface for the <%= domainClassName %> read model.
27
+ * Provides read-only access and upsert/delete for event-driven sync.
28
+ */
29
+ public interface <%= repositoryName %> {
30
+
31
+ void upsert(<%= domainClassName %> readModel);
32
+
33
+ void deleteById(String id);
34
+ <% if (hasSoftDelete) { %>
35
+
36
+ void softDeleteById(String id);
37
+ <% } %>
38
+
39
+ Optional<<%= domainClassName %>> findById(String id);
40
+
41
+ boolean existsById(String id);
42
+ }
@@ -0,0 +1,85 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.database.repositories;
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.LocalDateTime;
11
+ <% } %>
12
+ <% if (needsInstant) { %>
13
+ import java.time.Instant;
14
+ <% } %>
15
+ <% if (needsUUID) { %>
16
+ import java.util.UUID;
17
+ <% } %>
18
+
19
+ import <%= packageName %>.<%= moduleName %>.domain.models.readmodels.<%= domainClassName %>;
20
+ import <%= packageName %>.<%= moduleName %>.domain.repositories.<%= repositoryName %>;
21
+ import <%= packageName %>.<%= moduleName %>.infrastructure.database.entities.<%= jpaEntityName %>;
22
+ import org.springframework.stereotype.Repository;
23
+
24
+ import java.util.Optional;
25
+
26
+ /**
27
+ * Repository implementation for the <%= domainClassName %> read model.
28
+ * Maps between domain read model and JPA entity.
29
+ */
30
+ @Repository("<%= moduleName %>.<%= repositoryImplName %>")
31
+ public class <%= repositoryImplName %> implements <%= repositoryName %> {
32
+
33
+ private final <%= jpaRepositoryName %> jpaRepository;
34
+
35
+ public <%= repositoryImplName %>(<%= jpaRepositoryName %> jpaRepository) {
36
+ this.jpaRepository = jpaRepository;
37
+ }
38
+
39
+ @Override
40
+ public void upsert(<%= domainClassName %> readModel) {
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) %>());
49
+ <% }); %>
50
+ jpaRepository.save(entity);
51
+ }
52
+
53
+ @Override
54
+ public void deleteById(String id) {
55
+ jpaRepository.deleteById(id);
56
+ }
57
+
58
+ @Override
59
+ public Optional<<%= domainClassName %>> findById(String id) {
60
+ return jpaRepository.findById(id).map(this::toDomain);
61
+ }
62
+
63
+ @Override
64
+ public boolean existsById(String id) {
65
+ return jpaRepository.existsById(id);
66
+ }
67
+ <% if (hasSoftDelete) { %>
68
+
69
+ @Override
70
+ public void softDeleteById(String id) {
71
+ jpaRepository.findById(id).ifPresent(entity -> {
72
+ entity.setDeletedAt(LocalDateTime.now());
73
+ jpaRepository.save(entity);
74
+ });
75
+ }
76
+ <% } %>
77
+
78
+ private <%= domainClassName %> toDomain(<%= jpaEntityName %> entity) {
79
+ return new <%= domainClassName %>(
80
+ <% fields.forEach((field, idx) => { %>
81
+ entity.get<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>()<%= idx < fields.length - 1 ? ',' : '' %>
82
+ <% }); %>
83
+ );
84
+ }
85
+ }
@@ -0,0 +1,54 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.usecases;
2
+ <% const upsertEntries = syncedBy.filter(s => s.action === 'UPSERT'); %>
3
+ <% const upsertImports = [...new Set(upsertEntries.map(s => s.integrationEventClassName))]; %>
4
+ <% upsertImports.forEach(cls => { %>
5
+ import <%= packageName %>.<%= moduleName %>.application.events.<%= cls %>;
6
+ <% }); %>
7
+ import <%= packageName %>.<%= moduleName %>.domain.models.readmodels.<%= domainClassName %>;
8
+ import <%= packageName %>.<%= moduleName %>.domain.repositories.<%= repositoryName %>;
9
+ import <%= packageName %>.shared.domain.annotations.ApplicationComponent;
10
+ import <%= packageName %>.shared.domain.annotations.LogExceptions;
11
+ import org.springframework.stereotype.Component;
12
+
13
+ /**
14
+ * Sync handler for the <%= domainClassName %> read model.
15
+ * Maintains the local projection by processing events from <%= sourceModule %>/<%= sourceName %>.
16
+ * <p>
17
+ * One method per syncedBy event — Kafka listeners delegate directly to these methods.
18
+ */
19
+ @ApplicationComponent
20
+ @Component("<%= moduleName %>.<%= syncHandlerName %>")
21
+ public class <%= syncHandlerName %> {
22
+
23
+ private final <%= repositoryName %> repository;
24
+
25
+ public <%= syncHandlerName %>(<%= repositoryName %> repository) {
26
+ this.repository = repository;
27
+ }
28
+ <% syncedBy.forEach(sync => { %>
29
+ <% if (sync.action === 'UPSERT') { %>
30
+
31
+ @LogExceptions
32
+ public void on<%= sync.eventBaseName %>(<%= sync.integrationEventClassName %> event) {
33
+ <%= domainClassName %> model = new <%= domainClassName %>(
34
+ <% fields.forEach((field, idx) => { %>
35
+ event.<%= field.name %>()<%= idx < fields.length - 1 ? ',' : '' %>
36
+ <% }); %>
37
+ );
38
+ repository.upsert(model);
39
+ }
40
+ <% } else if (sync.action === 'DELETE') { %>
41
+
42
+ @LogExceptions
43
+ public void on<%= sync.eventBaseName %>(String id) {
44
+ repository.deleteById(id);
45
+ }
46
+ <% } else if (sync.action === 'SOFT_DELETE') { %>
47
+
48
+ @LogExceptions
49
+ public void on<%= sync.eventBaseName %>(String id) {
50
+ repository.softDeleteById(id);
51
+ }
52
+ <% } %>
53
+ <% }); %>
54
+ }
@@ -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);
@@ -0,0 +1,100 @@
1
+ package <%= packageName %>.shared.infrastructure.configurations.rabbitmqConfig;
2
+
3
+ import org.springframework.amqp.core.*;
4
+ import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
5
+ import org.springframework.amqp.rabbit.connection.ConnectionFactory;
6
+ import org.springframework.amqp.rabbit.core.RabbitAdmin;
7
+ import org.springframework.amqp.rabbit.core.RabbitTemplate;
8
+ import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
9
+ import org.springframework.amqp.support.converter.MessageConverter;
10
+ import org.springframework.beans.factory.annotation.Value;
11
+ import org.springframework.boot.ApplicationRunner;
12
+ import org.springframework.context.annotation.Bean;
13
+ import org.springframework.context.annotation.Configuration;
14
+ import org.springframework.amqp.rabbit.annotation.EnableRabbit;
15
+ import org.springframework.retry.backoff.ExponentialBackOffPolicy;
16
+ import org.springframework.retry.policy.SimpleRetryPolicy;
17
+ import org.springframework.retry.support.RetryTemplate;
18
+ import org.slf4j.Logger;
19
+ import org.slf4j.LoggerFactory;
20
+
21
+
22
+ @Configuration
23
+ @EnableRabbit
24
+ public class RabbitMQConfig {
25
+
26
+ private static final Logger log = LoggerFactory.getLogger(RabbitMQConfig.class);
27
+
28
+ @Value("${spring.rabbitmq.listener.simple.retry.max-attempts}")
29
+ private int maxAttempts;
30
+
31
+ @Value("${spring.rabbitmq.listener.simple.retry.initial-interval}")
32
+ private long initialInterval;
33
+
34
+ @Value("${spring.rabbitmq.listener.simple.retry.multiplier:1.0}")
35
+ private double multiplier;
36
+
37
+ @Value("${spring.rabbitmq.listener.simple.retry.max-interval:10000}")
38
+ private long maxInterval;
39
+
40
+ @Bean
41
+ public MessageConverter jackson2JsonMessageConverter() {
42
+ return new Jackson2JsonMessageConverter();
43
+ }
44
+
45
+ @Bean
46
+ public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
47
+ return new RabbitAdmin(connectionFactory);
48
+ }
49
+
50
+ @Bean
51
+ public ApplicationRunner rabbitInitializer(RabbitAdmin rabbitAdmin) {
52
+ return args -> rabbitAdmin.initialize();
53
+ }
54
+
55
+ @Bean
56
+ public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory,
57
+ MessageConverter jackson2JsonMessageConverter) {
58
+ RabbitTemplate template = new RabbitTemplate(connectionFactory);
59
+ template.setMessageConverter(jackson2JsonMessageConverter);
60
+ template.setMandatory(true);
61
+ template.setConfirmCallback((correlationData, ack, cause) -> {
62
+ if (!ack) {
63
+ log.error("Message not confirmed by broker: {}", cause);
64
+ }
65
+ });
66
+ template.setReturnsCallback(returned -> {
67
+ log.error("Message returned — unroutable: exchange={}, routingKey={}, replyText={}",
68
+ returned.getExchange(), returned.getRoutingKey(), returned.getReplyText());
69
+ });
70
+ return template;
71
+ }
72
+
73
+ @Bean
74
+ public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
75
+ ConnectionFactory connectionFactory,
76
+ MessageConverter jackson2JsonMessageConverter) {
77
+
78
+ SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
79
+ factory.setConnectionFactory(connectionFactory);
80
+ factory.setMessageConverter(jackson2JsonMessageConverter);
81
+ factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
82
+ factory.setDefaultRequeueRejected(false);
83
+
84
+ // Retry template with exponential backoff
85
+ RetryTemplate retryTemplate = new RetryTemplate();
86
+ SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
87
+ retryPolicy.setMaxAttempts(maxAttempts);
88
+ retryTemplate.setRetryPolicy(retryPolicy);
89
+
90
+ ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
91
+ backOffPolicy.setInitialInterval(initialInterval);
92
+ backOffPolicy.setMultiplier(multiplier);
93
+ backOffPolicy.setMaxInterval(maxInterval);
94
+ retryTemplate.setBackOffPolicy(backOffPolicy);
95
+
96
+ factory.setRetryTemplate(retryTemplate);
97
+
98
+ return factory;
99
+ }
100
+ }
@@ -1,19 +1,12 @@
1
1
  package <%= packageName %>.shared.infrastructure.configurations.temporalConfig;
2
2
 
3
- import <%= packageName %>.shared.domain.interfaces.HeavyActivity;
4
- import <%= packageName %>.shared.domain.interfaces.LightActivity;
5
3
  import io.temporal.client.WorkflowClient;
6
4
  import io.temporal.serviceclient.WorkflowServiceStubs;
7
- import io.temporal.worker.Worker;
8
5
  import io.temporal.worker.WorkerFactory;
9
- import io.temporal.worker.WorkerOptions;
10
- import jakarta.annotation.PreDestroy;
11
6
  import org.springframework.beans.factory.annotation.Value;
12
7
  import org.springframework.context.annotation.Bean;
13
8
  import org.springframework.context.annotation.Configuration;
14
9
 
15
- import java.util.List;
16
-
17
10
  @Configuration
18
11
  public class TemporalConfig {
19
12
 
@@ -23,26 +16,6 @@ public class TemporalConfig {
23
16
  @Value("${temporal.namespace}")
24
17
  private String namespace;
25
18
 
26
- @Value("${temporal.flow-queue}")
27
- private String flowQueue;
28
-
29
- @Value("${temporal.number-flow-worker}")
30
- private Integer numberFlowWorker;
31
-
32
- @Value("${temporal.heavy-queue}")
33
- private String heavyQueue;
34
-
35
- @Value("${temporal.number-heavy-worker}")
36
- private Integer numberHeavyWorker;
37
-
38
- @Value("${temporal.light-queue}")
39
- private String lightQueue;
40
-
41
- @Value("${temporal.number-light-worker}")
42
- private Integer numberLightWorker;
43
-
44
- private WorkerFactory workerFactory;
45
-
46
19
  @Bean
47
20
  public WorkflowServiceStubs workflowServiceStubs() {
48
21
  return WorkflowServiceStubs.newServiceStubs(
@@ -63,42 +36,7 @@ public class TemporalConfig {
63
36
  }
64
37
 
65
38
  @Bean
66
- public WorkerFactory workerFactory(WorkflowClient client,
67
- List<HeavyActivity> heavyActivities,
68
- List<LightActivity> lightActivities) {
69
-
70
- workerFactory = WorkerFactory.newInstance(client);
71
-
72
- // 1. WORKER DE FLUJO (Orquestador)
73
- WorkerOptions workflowOptions = WorkerOptions.newBuilder()
74
- .setMaxConcurrentWorkflowTaskExecutionSize(numberFlowWorker)
75
- .build();
76
- Worker workflowWorker = workerFactory.newWorker(flowQueue, workflowOptions);
77
- // TODO: register your workflow implementation types here
78
- // workflowWorker.registerWorkflowImplementationTypes(MyWorkflowImpl.class);
79
-
80
- // 2. WORKER PESADO (tareas complejas)
81
- WorkerOptions heavyOptions = WorkerOptions.newBuilder()
82
- .setMaxConcurrentActivityExecutionSize(numberHeavyWorker)
83
- .build();
84
- Worker heavyWorker = workerFactory.newWorker(heavyQueue, heavyOptions);
85
- heavyWorker.registerActivitiesImplementations(heavyActivities.toArray());
86
-
87
- // 3. WORKER LIGERO (tareas rápidas)
88
- WorkerOptions lightOptions = WorkerOptions.newBuilder()
89
- .setMaxConcurrentActivityExecutionSize(numberLightWorker)
90
- .build();
91
- Worker lightWorker = workerFactory.newWorker(lightQueue, lightOptions);
92
- lightWorker.registerActivitiesImplementations(lightActivities.toArray());
93
-
94
- workerFactory.start();
95
- return workerFactory;
96
- }
97
-
98
- @PreDestroy
99
- public void destroy() {
100
- if (workerFactory != null) {
101
- workerFactory.shutdown();
102
- }
39
+ public WorkerFactory workerFactory(WorkflowClient client) {
40
+ return WorkerFactory.newInstance(client);
103
41
  }
104
42
  }
@@ -0,0 +1,41 @@
1
+ package <%= packageName %>.shared.infrastructure.configurations.temporalConfig;
2
+
3
+ import io.temporal.worker.WorkerFactory;
4
+ import jakarta.annotation.PreDestroy;
5
+ import org.springframework.context.SmartLifecycle;
6
+ import org.springframework.stereotype.Component;
7
+
8
+ @Component
9
+ public class TemporalWorkerFactoryLifecycle implements SmartLifecycle {
10
+
11
+ private final WorkerFactory workerFactory;
12
+ private volatile boolean running = false;
13
+
14
+ public TemporalWorkerFactoryLifecycle(WorkerFactory workerFactory) {
15
+ this.workerFactory = workerFactory;
16
+ }
17
+
18
+ @Override
19
+ public void start() {
20
+ workerFactory.start();
21
+ running = true;
22
+ }
23
+
24
+ @Override
25
+ public void stop() {
26
+ if (workerFactory != null) {
27
+ workerFactory.shutdown();
28
+ }
29
+ running = false;
30
+ }
31
+
32
+ @Override
33
+ public boolean isRunning() {
34
+ return running;
35
+ }
36
+
37
+ @Override
38
+ public int getPhase() {
39
+ return Integer.MAX_VALUE;
40
+ }
41
+ }
@@ -1,14 +1,80 @@
1
1
  package <%= packageName %>.<%= moduleName %>.infrastructure.adapters.activities;
2
2
 
3
+ <% if (shared) { %>
4
+ import <%= packageName %>.shared.domain.contracts.<%= moduleName %>.<%= activityPascalCase %>Activity;
5
+ <% if (hasInput) { %>
6
+ import <%= packageName %>.shared.domain.contracts.<%= moduleName %>.<%= activityPascalCase %>Input;
7
+ <% } %>
8
+ <% if (hasOutput) { %>
9
+ import <%= packageName %>.shared.domain.contracts.<%= moduleName %>.<%= activityPascalCase %>Output;
10
+ <% } %>
11
+ <% } else { %>
3
12
  import <%= packageName %>.<%= moduleName %>.application.ports.<%= activityPascalCase %>Activity;
4
- import <%= packageName %>.shared.domain.interfaces.<%= activityCategory %>;
13
+ <% if (hasInput) { %>
14
+ import <%= packageName %>.<%= moduleName %>.application.dtos.temporal.<%= activityPascalCase %>Input;
15
+ <% } %>
16
+ <% if (hasOutput) { %>
17
+ import <%= packageName %>.<%= moduleName %>.application.dtos.temporal.<%= activityPascalCase %>Output;
18
+ <% } %>
19
+ <% } %>
20
+ import <%= packageName %>.<%= moduleName %>.domain.interfaces.<%= activityCategory %>;
21
+ <% if (isQueryActivity && queryMeta) { %>
22
+ import <%= packageName %>.<%= moduleName %>.domain.models.entities.<%= queryMeta.aggregateName %>;
23
+ import <%= packageName %>.<%= moduleName %>.domain.repositories.<%= queryMeta.aggregateName %>Repository;
24
+ import <%= packageName %>.shared.domain.customExceptions.NotFoundException;
25
+ <% if (queryMeta.voMappingImports && queryMeta.voMappingImports.length > 0) { -%>
26
+ <% queryMeta.voMappingImports.forEach(imp => { -%>
27
+ <%- imp %>
28
+ <% }); -%>
29
+ <% } -%>
30
+ <% } %>
5
31
  import org.springframework.stereotype.Component;
32
+ import org.springframework.transaction.annotation.Transactional;
6
33
 
7
34
  @Component
8
35
  public class <%= activityPascalCase %>ActivityImpl implements <%= activityPascalCase %>Activity, <%= activityCategory %> {
36
+ <% if (isQueryActivity && queryMeta) { %>
37
+
38
+ private final <%= queryMeta.aggregateName %>Repository repository;
39
+
40
+ public <%= activityPascalCase %>ActivityImpl(<%= queryMeta.aggregateName %>Repository repository) {
41
+ this.repository = repository;
42
+ }
43
+
44
+ @Override
45
+ @Transactional(readOnly = true)
46
+ public <%= activityPascalCase %>Output execute(<%= activityPascalCase %>Input input) {
47
+ <% const findMethod = (queryMeta.needsEagerLoad && queryMeta.eagerLoadField)
48
+ ? 'findByIdWith' + queryMeta.eagerLoadField.fieldPascal
49
+ : 'findById'; -%>
50
+ <%= queryMeta.aggregateName %> entity = repository.<%= findMethod %>(<%= queryMeta.idFieldAccessor %>)
51
+ .orElseThrow(() -> new NotFoundException("<%= queryMeta.aggregateName %> not found with id: " + <%= queryMeta.idFieldAccessor %>));
52
+ <% if (queryMeta.outputMappings.some(m => m.preDeclaration)) { -%>
53
+ <% queryMeta.outputMappings.filter(m => m.preDeclaration).forEach(m => { %>
54
+ <%- m.preDeclaration.varType %> <%- m.preDeclaration.varName %> = <%- m.preDeclaration.getter %> != null
55
+ ? new <%- m.preDeclaration.varType %>(
56
+ <% m.preDeclaration.ntArgs.forEach((arg, ai) => { -%>
57
+ <%- arg %><%= ai < m.preDeclaration.ntArgs.length - 1 ? ',' : '' %>
58
+ <% }); -%>
59
+ )
60
+ : null;
61
+ <% }); -%>
62
+ <% } -%>
63
+ return new <%= activityPascalCase %>Output(
64
+ <% queryMeta.outputMappings.forEach((m, i) => { -%>
65
+ <%- m.accessor %><%= i < queryMeta.outputMappings.length - 1 ? ',' : '' %>
66
+ <% }); -%>
67
+ );
68
+ }
69
+ <% } else { %>
9
70
 
10
71
  @Override
11
- public void execute(String flowId) {
72
+ @Transactional
73
+ public <%= hasOutput ? activityPascalCase + 'Output' : 'void' %> execute(<%= hasInput ? activityPascalCase + 'Input input' : '' %>) {
12
74
  //todo: implement the logic
75
+ <% if (hasOutput) { %>
76
+ throw new UnsupportedOperationException("Not implemented yet");
77
+ <% } %>
13
78
  }
79
+ <% } %>
14
80
  }
@@ -0,0 +1,14 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.dtos.temporal;
2
+ <%
3
+ const _allImports = [...(imports || []), ...((typeof nestedTypeImportLines !== 'undefined' ? nestedTypeImportLines : []) || [])];
4
+ if (_allImports.length > 0) { %>
5
+
6
+ <%= _allImports.join('\n') %>
7
+ <% } %>
8
+
9
+ public record <%= activityPascalCase %>Input(
10
+ <% inputFields.forEach((field, index) => { %>
11
+ <%- field.javaType %> <%= field.name %><%= index < inputFields.length - 1 ? ',' : '' %>
12
+ <% }); %>
13
+ ) {
14
+ }
@@ -2,10 +2,16 @@ package <%= packageName %>.<%= moduleName %>.application.ports;
2
2
 
3
3
  import io.temporal.activity.ActivityInterface;
4
4
  import io.temporal.activity.ActivityMethod;
5
+ <% if (hasInput) { %>
6
+ import <%= packageName %>.<%= moduleName %>.application.dtos.temporal.<%= activityPascalCase %>Input;
7
+ <% } %>
8
+ <% if (hasOutput) { %>
9
+ import <%= packageName %>.<%= moduleName %>.application.dtos.temporal.<%= activityPascalCase %>Output;
10
+ <% } %>
5
11
 
6
12
  @ActivityInterface
7
13
  public interface <%= activityPascalCase %>Activity {
8
14
 
9
15
  @ActivityMethod(name = "<%= activityPascalCase %>")
10
- void execute(String flowId);
16
+ <%= hasOutput ? activityPascalCase + 'Output' : 'void' %> execute(<%= hasInput ? activityPascalCase + 'Input input' : '' %>);
11
17
  }
@@ -0,0 +1,14 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.dtos.temporal;
2
+ <%
3
+ const _allImports = [...(imports || []), ...((typeof nestedTypeImportLines !== 'undefined' ? nestedTypeImportLines : []) || [])];
4
+ if (_allImports.length > 0) { %>
5
+
6
+ <%= _allImports.join('\n') %>
7
+ <% } %>
8
+
9
+ public record <%= activityPascalCase %>Output(
10
+ <% outputFields.forEach((field, index) => { %>
11
+ <%- field.javaType %> <%= field.name %><%= index < outputFields.length - 1 ? ',' : '' %>
12
+ <% }); %>
13
+ ) {
14
+ }