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
@@ -68,9 +68,9 @@ if (audit && audit.trackUser) {
68
68
  private <%- field.javaTypeJpa %> <%= field.name %> = new ArrayList<>();
69
69
  <% } else { %>
70
70
  <% if (index === 0) { %>@Id
71
- <% if (field.javaType === 'String') { %>@GeneratedValue(strategy = GenerationType.UUID)
71
+ <% if (!hasCreateLifecycle) { %><% if (field.javaType === 'String') { %>@GeneratedValue(strategy = GenerationType.UUID)
72
72
  <% } else if (field.javaType === 'Long' || field.javaType === 'Integer') { %>@GeneratedValue(strategy = GenerationType.IDENTITY)
73
- <% } %><% } %><% if (field.annotations.length > 0) { %><% field.annotations.forEach(annotation => { %><%= annotation %>
73
+ <% } %><% } %><% } %><% if (field.annotations.length > 0) { %><% field.annotations.forEach(annotation => { %><%= annotation %>
74
74
  <% }); %><% } %><% if (field.isEmbedded || field.isValueObject) { %><%
75
75
  // Find the Value Object definition to get its fields
76
76
  const vo = valueObjects && valueObjects.find(v => v.name === field.javaType);
@@ -87,7 +87,7 @@ if (audit && audit.trackUser) {
87
87
  <% }); %>
88
88
  <% relationships.forEach(rel => { %>
89
89
  <% if (rel.type === 'OneToMany') { %>
90
- @OneToMany(mappedBy = "<%= rel.mappedBy %>", cascade = {<% rel.cascade.forEach((c, idx) => { %>CascadeType.<%= c %><%= idx < rel.cascade.length - 1 ? ', ' : '' %><% }); %>}, fetch = FetchType.<%= rel.fetch %>)
90
+ @OneToMany(mappedBy = "<%= rel.mappedBy %>", cascade = {<% rel.cascade.forEach((c, idx) => { %>CascadeType.<%= c %><%= idx < rel.cascade.length - 1 ? ', ' : '' %><% }); %>}, orphanRemoval = true, fetch = FetchType.<%= rel.fetch %>)
91
91
  @Builder.Default
92
92
  private <%- rel.javaTypeJpa %> <%= rel.fieldName %> = new ArrayList<>();
93
93
  <% } else if (rel.type === 'ManyToOne') { %>
@@ -96,7 +96,7 @@ if (audit && audit.trackUser) {
96
96
  private <%- rel.javaTypeJpa %> <%= rel.fieldName %>;
97
97
  <% } else if (rel.type === 'OneToOne') { %>
98
98
  <% if (rel.mappedBy) { %>
99
- @OneToOne(mappedBy = "<%= rel.mappedBy %>", cascade = {<% rel.cascade.forEach((c, idx) => { %>CascadeType.<%= c %><%= idx < rel.cascade.length - 1 ? ', ' : '' %><% }); %>}, fetch = FetchType.<%= rel.fetch %>)
99
+ @OneToOne(mappedBy = "<%= rel.mappedBy %>", cascade = {<% rel.cascade.forEach((c, idx) => { %>CascadeType.<%= c %><%= idx < rel.cascade.length - 1 ? ', ' : '' %><% }); %>}, orphanRemoval = true, fetch = FetchType.<%= rel.fetch %>)
100
100
  <% } else { %>
101
101
  @OneToOne(cascade = {<% rel.cascade.forEach((c, idx) => { %>CascadeType.<%= c %><%= idx < rel.cascade.length - 1 ? ', ' : '' %><% }); %>}, fetch = FetchType.<%= rel.fetch %>)
102
102
  @JoinColumn(name = "<%= rel.joinColumn %>")
@@ -91,7 +91,7 @@ if (audit && audit.trackUser) {
91
91
  <% }); %>
92
92
  <% relationships.forEach(rel => { %>
93
93
  <% if (rel.type === 'OneToMany') { %>
94
- @OneToMany(mappedBy = "<%= rel.mappedBy %>", cascade = {<% rel.cascade.forEach((c, idx) => { %>CascadeType.<%= c %><%= idx < rel.cascade.length - 1 ? ', ' : '' %><% }); %>}, fetch = FetchType.<%= rel.fetch %>)
94
+ @OneToMany(mappedBy = "<%= rel.mappedBy %>", cascade = {<% rel.cascade.forEach((c, idx) => { %>CascadeType.<%= c %><%= idx < rel.cascade.length - 1 ? ', ' : '' %><% }); %>}, orphanRemoval = true, fetch = FetchType.<%= rel.fetch %>)
95
95
  @Builder.Default
96
96
  private <%- rel.javaTypeJpa %> <%= rel.fieldName %> = new ArrayList<>();
97
97
  <% } else if (rel.type === 'ManyToOne') { %>
@@ -100,7 +100,7 @@ if (audit && audit.trackUser) {
100
100
  private <%- rel.javaTypeJpa %> <%= rel.fieldName %>;
101
101
  <% } else if (rel.type === 'OneToOne') { %>
102
102
  <% if (rel.mappedBy) { %>
103
- @OneToOne(mappedBy = "<%= rel.mappedBy %>", cascade = {<% rel.cascade.forEach((c, idx) => { %>CascadeType.<%= c %><%= idx < rel.cascade.length - 1 ? ', ' : '' %><% }); %>}, fetch = FetchType.<%= rel.fetch %>)
103
+ @OneToOne(mappedBy = "<%= rel.mappedBy %>", cascade = {<% rel.cascade.forEach((c, idx) => { %>CascadeType.<%= c %><%= idx < rel.cascade.length - 1 ? ', ' : '' %><% }); %>}, orphanRemoval = true, fetch = FetchType.<%= rel.fetch %>)
104
104
  <% } else { %>
105
105
  @OneToOne(cascade = {<% rel.cascade.forEach((c, idx) => { %>CascadeType.<%= c %><%= idx < rel.cascade.length - 1 ? ', ' : '' %><% }); %>}, fetch = FetchType.<%= rel.fetch %>)
106
106
  @JoinColumn(name = "<%= rel.joinColumn %>")
@@ -0,0 +1,12 @@
1
+ rabbitmq:
2
+ image: rabbitmq:<%= rabbitmqVersion %>
3
+ container_name: <%= artifactId %>-rabbitmq
4
+ ports:
5
+ - "5672:5672" # AMQP protocol
6
+ - "15672:15672" # Management UI
7
+ environment:
8
+ RABBITMQ_DEFAULT_USER: guest
9
+ RABBITMQ_DEFAULT_PASS: guest
10
+ RABBITMQ_DEFAULT_VHOST: /
11
+ networks:
12
+ - <%= artifactId %>-network
@@ -24,3 +24,8 @@ spring:
24
24
  retry:
25
25
  max-attempts: 2
26
26
  backoff-delay: 1500
27
+
28
+ kafka:
29
+ topic-defaults:
30
+ partitions: 3
31
+ replicas: 1
@@ -0,0 +1,15 @@
1
+ spring:
2
+ rabbitmq:
3
+ host: localhost
4
+ port: 5672
5
+ username: guest
6
+ password: guest
7
+ virtual-host: /
8
+ listener:
9
+ simple:
10
+ acknowledge-mode: manual
11
+ concurrency: 3
12
+ retry:
13
+ enabled: true
14
+ max-attempts: 3
15
+ initial-interval: 1500
@@ -1,9 +1,6 @@
1
1
  temporal:
2
2
  service-url: localhost:7233
3
3
  namespace: default
4
- flow-queue: WORKFLOW_QUEUE
5
4
  number-flow-worker: 10
6
- heavy-queue: HEAVY_TASK_QUEUE
7
5
  number-heavy-worker: 10
8
- light-queue: LIGHT_TASK_QUEUE
9
6
  number-light-worker: 10
@@ -24,3 +24,8 @@ spring:
24
24
  retry:
25
25
  max-attempts: 2
26
26
  backoff-delay: 1500
27
+
28
+ kafka:
29
+ topic-defaults:
30
+ partitions: 3
31
+ replicas: 1
@@ -0,0 +1,15 @@
1
+ spring:
2
+ rabbitmq:
3
+ host: localhost
4
+ port: 5672
5
+ username: guest
6
+ password: guest
7
+ virtual-host: /
8
+ listener:
9
+ simple:
10
+ acknowledge-mode: manual
11
+ concurrency: 3
12
+ retry:
13
+ enabled: true
14
+ max-attempts: 3
15
+ initial-interval: 1500
@@ -1,9 +1,6 @@
1
1
  temporal:
2
2
  service-url: localhost:7233
3
3
  namespace: default
4
- flow-queue: WORKFLOW_QUEUE
5
4
  number-flow-worker: 10
6
- heavy-queue: HEAVY_TASK_QUEUE
7
5
  number-heavy-worker: 10
8
- light-queue: LIGHT_TASK_QUEUE
9
6
  number-light-worker: 10
@@ -1,26 +1,57 @@
1
1
  spring:
2
2
  kafka:
3
- bootstrap-servers:
4
- - localhost:9092 # Lista de brokers Kafka
3
+ bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS}
5
4
  producer:
6
5
  properties:
7
6
  spring.json.add.type.headers: false
7
+ enable.idempotence: true
8
+ max.in.flight.requests.per.connection: 5
9
+ delivery.timeout.ms: 120000
10
+ request.timeout.ms: 30000
11
+ linger.ms: 5
12
+ batch.size: 32768
13
+ compression.type: snappy
8
14
  key-serializer: org.apache.kafka.common.serialization.StringSerializer
9
15
  value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
10
- retries: 3
16
+ acks: all
17
+ retries: 10
11
18
  consumer:
12
- group-id: <%= projectName %>-api-group
19
+ group-id: ${spring.application.name}-group
13
20
  auto-offset-reset: earliest
14
21
  enable-auto-commit: false
15
22
  key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
16
23
  value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
17
24
  properties:
18
- spring.json.trusted.packages: "*"
25
+ spring.json.trusted.packages: "<%= packageName %>.**"
19
26
  spring.json.use.type.headers: false
20
27
  spring.json.value.default.type: <%= packageName %>.shared.infrastructure.eventEnvelope.EventEnvelope
28
+ max.poll.records: 50
29
+ max.poll.interval.ms: 300000
30
+ session.timeout.ms: 30000
31
+ heartbeat.interval.ms: 10000
32
+ fetch-min-size: 1
33
+ fetch-max-wait: 500
21
34
  listener:
22
35
  ack-mode: manual
23
- concurrency: 3
36
+ concurrency: 5
24
37
  retry:
25
- max-attempts: 2
26
- backoff-delay: 1500
38
+ max-attempts: 5
39
+ backoff-delay: 2000
40
+ backoff-multiplier: 2.0
41
+ backoff-max-delay: 30000
42
+ # SSL/SASL — descomentar si el cluster lo requiere
43
+ # security:
44
+ # protocol: SASL_SSL
45
+ # properties:
46
+ # sasl.mechanism: PLAIN
47
+ # sasl.jaas.config: >-
48
+ # org.apache.kafka.common.security.plain.PlainLoginModule required
49
+ # username="${KAFKA_USERNAME}"
50
+ # password="${KAFKA_PASSWORD}";
51
+ # ssl.truststore.location: ${KAFKA_TRUSTSTORE_LOCATION}
52
+ # ssl.truststore.password: ${KAFKA_TRUSTSTORE_PASSWORD}
53
+
54
+ kafka:
55
+ topic-defaults:
56
+ partitions: 3
57
+ replicas: 3
@@ -0,0 +1,32 @@
1
+ spring:
2
+ rabbitmq:
3
+ host: ${RABBITMQ_HOST}
4
+ port: ${RABBITMQ_PORT:5672}
5
+ username: ${RABBITMQ_USERNAME}
6
+ password: ${RABBITMQ_PASSWORD}
7
+ virtual-host: ${RABBITMQ_VHOST:/}
8
+ connection-timeout: 5000
9
+ requested-heartbeat: 60
10
+ channel-rpc-timeout: 10000
11
+ publisher-confirm-type: correlated
12
+ publisher-returns: true
13
+ ssl:
14
+ enabled: ${RABBITMQ_SSL_ENABLED:false}
15
+ cache:
16
+ channel:
17
+ size: 25
18
+ template:
19
+ mandatory: true
20
+ listener:
21
+ simple:
22
+ acknowledge-mode: manual
23
+ concurrency: 5
24
+ max-concurrency: 20
25
+ prefetch: 10
26
+ retry:
27
+ enabled: true
28
+ max-attempts: 5
29
+ initial-interval: 2000
30
+ multiplier: 2.0
31
+ max-interval: 30000
32
+ stateless: true
@@ -1,9 +1,6 @@
1
1
  temporal:
2
2
  service-url: localhost:7233
3
3
  namespace: default
4
- flow-queue: WORKFLOW_QUEUE
5
4
  number-flow-worker: 10
6
- heavy-queue: HEAVY_TASK_QUEUE
7
5
  number-heavy-worker: 10
8
- light-queue: LIGHT_TASK_QUEUE
9
6
  number-light-worker: 10
@@ -1,15 +1,15 @@
1
1
  spring:
2
2
  kafka:
3
3
  bootstrap-servers:
4
- - localhost:9092 # Lista de brokers Kafka
4
+ - localhost:9092
5
5
  producer:
6
6
  properties:
7
7
  spring.json.add.type.headers: false
8
8
  key-serializer: org.apache.kafka.common.serialization.StringSerializer
9
9
  value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
10
- retries: 3
10
+ retries: 1
11
11
  consumer:
12
- group-id: <%= projectName %>-api-group
12
+ group-id: <%= projectName %>-test-group
13
13
  auto-offset-reset: earliest
14
14
  enable-auto-commit: false
15
15
  key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
@@ -18,9 +18,15 @@ spring:
18
18
  spring.json.trusted.packages: "*"
19
19
  spring.json.use.type.headers: false
20
20
  spring.json.value.default.type: <%= packageName %>.shared.infrastructure.eventEnvelope.EventEnvelope
21
+ max.poll.records: 10
21
22
  listener:
22
23
  ack-mode: manual
23
- concurrency: 3
24
+ concurrency: 1
24
25
  retry:
25
- max-attempts: 2
26
- backoff-delay: 1500
26
+ max-attempts: 1
27
+ backoff-delay: 100
28
+
29
+ kafka:
30
+ topic-defaults:
31
+ partitions: 1
32
+ replicas: 1
@@ -0,0 +1,15 @@
1
+ spring:
2
+ rabbitmq:
3
+ host: localhost
4
+ port: 5672
5
+ username: guest
6
+ password: guest
7
+ virtual-host: /
8
+ listener:
9
+ simple:
10
+ acknowledge-mode: manual
11
+ concurrency: 3
12
+ retry:
13
+ enabled: true
14
+ max-attempts: 3
15
+ initial-interval: 1500
@@ -1,9 +1,6 @@
1
1
  temporal:
2
2
  service-url: localhost:7233
3
3
  namespace: default
4
- flow-queue: WORKFLOW_QUEUE
5
4
  number-flow-worker: 10
6
- heavy-queue: HEAVY_TASK_QUEUE
7
5
  number-heavy-worker: 10
8
- light-queue: LIGHT_TASK_QUEUE
9
6
  number-light-worker: 10
@@ -722,7 +722,7 @@ public class OrderJpa {
722
722
 
723
723
  private LocalDateTime orderDate;
724
724
 
725
- @OneToMany(mappedBy = "order", cascade = {...}, fetch = FetchType.LAZY)
725
+ @OneToMany(mappedBy = "order", cascade = {...}, orphanRemoval = true, fetch = FetchType.LAZY)
726
726
  private List<OrderItemJpa> items;
727
727
  }
728
728
  ```
@@ -2,9 +2,20 @@ package <%= packageName %>.<%= moduleName %>.application.usecases;
2
2
 
3
3
  import <%= packageName %>.<%= moduleName %>.application.commands.Delete<%= aggregateName %>Command;
4
4
  import <%= packageName %>.<%= moduleName %>.domain.repositories.<%= aggregateName %>Repository;
5
- <% if (hasSoftDelete) { %>
5
+ <% const _deleteLifecycleEvents = (lifecycleEventsMap || {}).delete || []; %>
6
+ <% const _softDeleteLifecycleEvents2 = (lifecycleEventsMap || {}).softDelete || []; %>
7
+ <% if (hasSoftDelete || _deleteLifecycleEvents.length > 0) { %>
6
8
  import <%= packageName %>.<%= moduleName %>.domain.models.entities.<%= aggregateName %>;
7
9
  <% } %>
10
+ <% if (_deleteLifecycleEvents.length > 0) { %>
11
+ import <%= packageName %>.shared.domain.DomainEvent;
12
+ <% _deleteLifecycleEvents.forEach(function(evt) { %>
13
+ import <%= packageName %>.<%= moduleName %>.domain.models.events.<%= evt.name %>;
14
+ <% }); %>
15
+ <% if (_deleteLifecycleEvents.some(function(evt) { return evt.needsLocalDateTime; })) { %>
16
+ import java.time.LocalDateTime;
17
+ <% } %>
18
+ <% } %>
8
19
  import <%= packageName %>.shared.domain.annotations.ApplicationComponent;
9
20
  import <%= packageName %>.shared.domain.annotations.LogExceptions;
10
21
  import <%= packageName %>.shared.domain.customExceptions.NotFoundException;
@@ -33,6 +44,13 @@ public class Delete<%= aggregateName %>CommandHandler implements CommandHandler<
33
44
  .orElseThrow(() -> new NotFoundException("<%= aggregateName %> not found with id: " + command.id()));
34
45
  entity.softDelete();
35
46
  repository.save(entity);
47
+ <% } else if (_deleteLifecycleEvents.length > 0) { %>
48
+ <%= aggregateName %> entity = repository.findById(command.id())
49
+ .orElseThrow(() -> new NotFoundException("<%= aggregateName %> not found with id: " + command.id()));
50
+ <% _deleteLifecycleEvents.forEach(function(evt) { %>
51
+ entity.raise(new <%= evt.name %>(<%= evt.resolvedArgs.join(', ') %>));
52
+ <% }); %>
53
+ repository.delete(entity);
36
54
  <% } else { %>
37
55
  if (!repository.existsById(command.id())) {
38
56
  throw new NotFoundException("<%= aggregateName %> not found with id: " + command.id());
@@ -147,7 +147,7 @@ public class <%= controllerName %> {
147
147
  useCaseMediator.dispatch(new <%= op.useCase %>Command(id<% if (op.classification && op.classification.entityFields && op.classification.entityFields.length > 0) { %>, <% op.classification.entityFields.forEach(function(f, idx) { %>command.<%= f.name %>()<% if (idx < op.classification.entityFields.length - 1) { %>, <% } %><% }); %><% } %>));
148
148
  }
149
149
  <% } else if (op.classifiedType === 'subEntityRemove') { %>
150
- public void <%= op.methodName %>(@PathVariable <%- op.idType %> id, @PathVariable String itemId) {
150
+ public void <%= op.methodName %>(@PathVariable <%- op.idType %> id, @PathVariable <%- op.classification.itemIdType %> itemId) {
151
151
  log.info("Handling <%= op.useCase %> for <%= aggregateName %> id: {}, itemId: {}", id, itemId);
152
152
  useCaseMediator.dispatch(new <%= op.useCase %>Command(id, itemId));
153
153
  }
@@ -7,6 +7,9 @@ import <%= packageName %>.shared.domain.interfaces.Command;
7
7
  * TODO: Add the fields required by this use case and remove the placeholder.
8
8
  */
9
9
  public record <%= useCaseName %>Command(
10
- // TODO: Replace with actual command fields
11
- String id
10
+ <% if (hasPathVar) { -%>
11
+ <%- idType %> <%= pathVarName %>
12
+ <% } else { -%>
13
+ // TODO: Add command fields if needed
14
+ <% } -%>
12
15
  ) implements Command {}
@@ -30,11 +30,13 @@ public class <%= useCaseName %>CommandHandler implements CommandHandler<<%= useC
30
30
  @LogExceptions
31
31
  public void handle(<%= useCaseName %>Command command) {
32
32
  // TODO: Implement business logic
33
+ <% if (hasPathVar) { -%>
33
34
  // Example:
34
- // <%= aggregateName %> entity = repository.findById(command.id())
35
+ // <%= aggregateName %> entity = repository.findById(command.<%= pathVarName %>())
35
36
  // .orElseThrow(() -> new EntityNotFoundException("<%= aggregateName %> not found"));
36
37
  // entity.someBusinessMethod();
37
38
  // repository.save(entity);
39
+ <% } -%>
38
40
 
39
41
  throw new UnsupportedOperationException("<%= useCaseName %> not yet implemented");
40
42
  }
@@ -8,6 +8,9 @@ import <%= packageName %>.shared.domain.interfaces.Query;
8
8
  * TODO: Add the parameters required by this query and adapt the return type if needed.
9
9
  */
10
10
  public record <%= useCaseName %>Query(
11
- // TODO: Replace with actual query parameters
12
- String id
11
+ <% if (hasPathVar) { -%>
12
+ <%- idType %> <%= pathVarName %>
13
+ <% } else { -%>
14
+ // TODO: Add query parameters if needed
15
+ <% } -%>
13
16
  ) implements Query<<%= aggregateName %>ResponseDto> {}
@@ -29,10 +29,12 @@ public class <%= useCaseName %>QueryHandler implements QueryHandler<<%= useCaseN
29
29
  @LogExceptions
30
30
  public <%= aggregateName %>ResponseDto handle(<%= useCaseName %>Query query) {
31
31
  // TODO: Implement query logic
32
+ <% if (hasPathVar) { -%>
32
33
  // Example:
33
- // return repository.findById(query.id())
34
+ // return repository.findById(query.<%= pathVarName %>())
34
35
  // .map(mapper::toDto)
35
36
  // .orElseThrow(() -> new EntityNotFoundException("<%= aggregateName %> not found"));
37
+ <% } -%>
36
38
 
37
39
  throw new UnsupportedOperationException("<%= useCaseName %> not yet implemented");
38
40
  }
@@ -6,4 +6,4 @@ import <%= packageName %>.shared.domain.interfaces.Command;
6
6
  * <%= useCaseName %>Command
7
7
  * Removes the <%= entityName %> identified by itemId from the <%= aggregateName %> aggregate.
8
8
  */
9
- public record <%= useCaseName %>Command(<%- idType %> id, String itemId) implements Command {}
9
+ public record <%= useCaseName %>Command(<%- idType %> id, <%- itemIdType %> itemId) implements Command {}
@@ -6,6 +6,7 @@ import <%= packageName %>.<%= moduleName %>.domain.repositories.<%= aggregateNam
6
6
  <% if ((commandFields || []).some(f => f.originalVoType)) { %>
7
7
  import <%= packageName %>.<%= moduleName %>.application.mappers.<%= aggregateName %>ApplicationMapper;
8
8
  <% } %>
9
+ <% const _updateLifecycleEvents = (lifecycleEventsMap || {}).update || []; %>
9
10
  import <%= packageName %>.shared.domain.annotations.ApplicationComponent;
10
11
  import <%= packageName %>.shared.domain.annotations.LogExceptions;
11
12
  import <%= packageName %>.shared.domain.customExceptions.NotFoundException;
@@ -15,8 +16,13 @@ import org.springframework.transaction.annotation.Transactional;
15
16
  /**
16
17
  * Update<%= aggregateName %>CommandHandler — PATCH
17
18
  *
19
+ <% if (_updateLifecycleEvents.length > 0) { %>
20
+ * Delegates field updates to the aggregate root's update() method,
21
+ * which raises domain events internally.
22
+ <% } else { %>
18
23
  * Reconstructs the <%= aggregateName %> aggregate using the full constructor,
19
24
  * merging each command field with the existing entity value.
25
+ <% } %>
20
26
  * Non-null command fields override the current value; null fields are preserved
21
27
  * from the loaded entity. No setters are required on the domain entity.
22
28
  */
@@ -52,12 +58,56 @@ public class Update<%= aggregateName %>CommandHandler implements CommandHandler<
52
58
  const cmdFieldMap = {};
53
59
  (commandFields || []).forEach(f => { cmdFieldMap[f.name] = f; });
54
60
 
61
+ function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1); }
62
+
63
+ // Updatable fields: same filter as creationFields (exclude id, audit, deletedAt, readOnly, autoInit)
64
+ const updatableFields = (rootEntity.fields || []).filter(f =>
65
+ f.name !== 'id' && f.name !== 'createdAt' && f.name !== 'updatedAt' &&
66
+ f.name !== 'createdBy' && f.name !== 'updatedBy' && f.name !== 'deletedAt' && !f.readOnly && !f.autoInit
67
+ );
68
+ %>
69
+ <% if (_updateLifecycleEvents.length > 0) { %>
70
+ // Merge: use command value when non-null, otherwise preserve existing value.
71
+ // The aggregate root's update() method raises domain events internally.
72
+ existing.update(
73
+ <% updatableFields.forEach((field, idx) => { %>
74
+ <% const cmdField = cmdFieldMap[field.name]; %>
75
+ <% const getter = `existing.get${capitalize(field.name)}()`; %>
76
+ <% const comma = idx < updatableFields.length - 1 ? ',' : ''; %>
77
+ <% if (cmdField && cmdField.originalVoType) { %>
78
+ command.<%= field.name %>() != null
79
+ ? mapper.to<%= cmdField.originalVoType %>(command.<%= field.name %>())
80
+ : <%= getter %><%= comma %>
81
+ <% } else if (cmdField) { %>
82
+ command.<%= field.name %>() != null ? command.<%= field.name %>() : <%= getter %><%= comma %>
83
+ <% } else { %>
84
+ <%= getter %><%= comma %> // preserved: <%= field.name %>
85
+ <% } %>
86
+ <% }); %>
87
+ );
88
+ <% if ((oneToManyRelationships || []).length > 0) { %>
89
+
90
+ // TODO: Collection relationships require an explicit replacement strategy.
91
+ // Choose the right approach for your domain and uncomment:
92
+ //
93
+ // Option A — Clear-and-replace (simple, loses existing entity ids):
94
+ // existing.get<collection>().clear();
95
+ // command.<collection>().forEach(dto -> entity.add<Item>(...));
96
+ //
97
+ // Option B — Merge by id (preserves audit trail, more complex):
98
+ // reconcile existing items against command items by id
99
+ <% (oneToManyRelationships || []).forEach(rel => { %>
100
+ // if (command.<%= rel.fieldName %>() != null) { /* replace <%= rel.fieldName %> */ }
101
+ <% }); %>
102
+ <% } %>
103
+
104
+ repository.save(existing);
105
+ <% } else { %>
106
+ <%
55
107
  // oneToOneRelationships are non-collection non-inverse rels → appear in the full constructor
56
108
  const ctorRels = oneToOneRelationships || [];
57
109
  const totalArgs = (rootEntity.fields || []).length + ctorRels.length;
58
110
  let argIdx = 0;
59
-
60
- function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1); }
61
111
  %>
62
112
  // Merge: use command value when non-null, otherwise preserve existing value.
63
113
  // The full constructor is called so no setters are needed on the domain entity.
@@ -98,5 +148,6 @@ public class Update<%= aggregateName %>CommandHandler implements CommandHandler<
98
148
  <% } %>
99
149
 
100
150
  repository.save(updated);
151
+ <% } %>
101
152
  }
102
153
  }