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,67 +3,161 @@ package <%= packageName %>.<%= moduleName %>.application.usecases;
3
3
  import <%= packageName %>.shared.domain.annotations.ApplicationComponent;
4
4
  import org.springframework.transaction.event.TransactionPhase;
5
5
  import org.springframework.transaction.event.TransactionalEventListener;
6
- <% domainEvents.forEach(event => { %>
6
+ <% domainEvents.forEach(event => { -%>
7
7
  import <%= packageName %>.<%= moduleName %>.domain.models.events.<%= event.name %>;
8
- <% }); %>
9
- <% if (broker) { %>
10
- <% domainEvents.forEach(event => { %>
8
+ <% }); -%>
9
+ <% if (broker) { -%>
10
+ <% domainEvents.forEach(event => { -%>
11
11
  import <%= packageName %>.<%= moduleName %>.application.events.<%= event.integrationEventClassName %>;
12
- <% }); %>
12
+ <% }); -%>
13
13
  import <%= packageName %>.<%= moduleName %>.application.ports.MessageBroker;
14
- <% } %>
14
+ <% } -%>
15
+ <%
16
+ // Collect unique workflow flowPascal names from events with notifies (Temporal bridge)
17
+ // flowPascal = workflow name with "Workflow" suffix stripped (e.g. PlaceOrderWorkflow → PlaceOrder)
18
+ // The generated service class is {flowPascal}WorkFlowService (e.g. PlaceOrderWorkFlowService)
19
+ const _flowPascalSet = new Set();
20
+ const _inputClassSet = new Set();
21
+ let _needsLocalDateTimeImport = false;
22
+ if (typeof temporal !== 'undefined' && temporal) {
23
+ domainEvents.forEach(event => {
24
+ const _evFieldNames = new Set((event.fields || []).map(f => f.name));
25
+ const _aggrIdWf = aggregateName.charAt(0).toLowerCase() + aggregateName.slice(1) + 'Id';
26
+ (event.notifies || []).forEach(n => {
27
+ if (n.workflow && n.flowPascal) _flowPascalSet.add(n.flowPascal);
28
+ if (n.inputFields && n.inputFields.length > 0 && n.flowPascal) {
29
+ _inputClassSet.add(n.flowPascal + 'Input');
30
+ // Check if any unresolvable field uses LocalDateTime.now()
31
+ n.inputFields.forEach(f => {
32
+ if (f.name !== _aggrIdWf && !_evFieldNames.has(f.name)
33
+ && f.name.endsWith('At') && f.javaType === 'LocalDateTime') {
34
+ _needsLocalDateTimeImport = true;
35
+ }
36
+ });
37
+ }
38
+ });
39
+ });
40
+ }
41
+ const _flowPascals = Array.from(_flowPascalSet);
42
+ const _hasTemporalBridge = _flowPascals.length > 0;
43
+ -%>
44
+ <% if (_hasTemporalBridge) { -%>
45
+ <% if (_needsLocalDateTimeImport) { -%>
46
+ import java.time.LocalDateTime;
47
+ <% } -%>
48
+ <% _flowPascals.forEach(fp => { -%>
49
+ import <%= packageName %>.<%= moduleName %>.application.usecases.<%= fp %>WorkFlowService;
50
+ <% }); -%>
51
+ <% _inputClassSet.forEach(cls => { -%>
52
+ import <%= packageName %>.<%= moduleName %>.application.usecases.<%= cls %>;
53
+ <% }); -%>
54
+ <% } -%>
15
55
 
16
56
  /**
17
57
  * <%= aggregateName %>DomainEventHandler — Domain Event Bridge
18
58
  *
19
- * Connects the internal Spring event bus (ApplicationEventPublisher) with the
20
- * external messaging port (MessageBroker).
59
+ * Connects the internal Spring event bus (ApplicationEventPublisher) with<% if (broker && _hasTemporalBridge) { %>
60
+ * the external messaging port (MessageBroker) and Temporal workflow orchestration.<% } else if (broker) { %>
61
+ * the external messaging port (MessageBroker).<% } else if (_hasTemporalBridge) { %>
62
+ * the Temporal workflow orchestration layer.<% } else { %>
63
+ * external side effects.<% } %>
21
64
  *
22
65
  * Architecture:
23
66
  * AggregateRepositoryImpl.save()
24
67
  * → eventPublisher.publishEvent(domainEvent) [internal Spring bus]
25
68
  * → @TransactionalEventListener(AFTER_COMMIT) [this class]
69
+ <% if (broker) { -%>
26
70
  * → messageBroker.publish*(integrationEvent) [port — broker-agnostic]
71
+ <% } -%>
72
+ <% if (_hasTemporalBridge) { -%>
73
+ * → workFlowService.startAsync(workflowId) [Temporal workflow launch]
74
+ <% } -%>
27
75
  *
28
76
  * AFTER_COMMIT guarantees that external events are published only when the
29
77
  * database transaction committed successfully, preventing ghost events from
30
78
  * rolled-back operations.
31
79
  *
32
80
  * Domain Events (domain/models/events/) — internal signals scoped to this bounded context.
81
+ <% if (broker) { -%>
33
82
  * Integration Events (application/events/) — broker-facing projections; changing broker
34
83
  * technology (Kafka → RabbitMQ → SNS) only requires changing the MessageBroker adapter.
35
84
  * Domain Events — and therefore this class — never need modification.
85
+ <% } -%>
36
86
  */
37
87
  @ApplicationComponent
38
88
  public class <%= aggregateName %>DomainEventHandler {
39
- <% if (broker) { %>
40
-
41
- private final MessageBroker messageBroker;
42
-
43
- public <%= aggregateName %>DomainEventHandler(MessageBroker messageBroker) {
44
- this.messageBroker = messageBroker;
89
+ <%
90
+ // Determine constructor dependencies
91
+ const _deps = [];
92
+ if (broker) _deps.push({ type: 'MessageBroker', name: 'messageBroker' });
93
+ _flowPascals.forEach(fp => {
94
+ const svcType = fp + 'WorkFlowService';
95
+ const svcName = fp.charAt(0).toLowerCase() + fp.slice(1) + 'WorkFlowService';
96
+ _deps.push({ type: svcType, name: svcName });
97
+ });
98
+ -%>
99
+ <% if (_deps.length > 0) { %>
100
+ <% _deps.forEach(dep => { -%>
101
+ private final <%= dep.type %> <%= dep.name %>;
102
+ <% }); %>
103
+ public <%= aggregateName %>DomainEventHandler(<%= _deps.map(d => d.type + ' ' + d.name).join(', ') %>) {
104
+ <% _deps.forEach(dep => { -%>
105
+ this.<%= dep.name %> = <%= dep.name %>;
106
+ <% }); -%>
45
107
  }
46
- <% } %>
47
- <% domainEvents.forEach(event => { %>
108
+ <% } -%>
109
+ <% domainEvents.forEach(event => {
110
+ const _eventNotifies = (typeof temporal !== 'undefined' && temporal)
111
+ ? (event.notifies || []).filter(n => n.workflow)
112
+ : [];
113
+ const _hasBrokerAction = !!broker;
114
+ const _hasWorkflowAction = _eventNotifies.length > 0;
115
+ -%>
48
116
 
49
117
  /**
50
- * Handles {@link <%= event.name %>} after the wrapping transaction commits.<% if (!broker) { %>
118
+ * Handles {@link <%= event.name %>} after the wrapping transaction commits.<% if (!_hasBrokerAction && !_hasWorkflowAction) { %>
51
119
  * <p>
52
120
  * TODO: Implement the side effect for this event (e.g., send notification,
53
121
  * update a read model, trigger a saga step, etc.).<% } %>
54
122
  */
55
123
  @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
56
124
  public void on<%= event.name %>(<%= event.name %> event) {
57
- <% if (broker) { %>
58
- <%
125
+ <% if (_hasBrokerAction) {
59
126
  const _aggrIdField = aggregateName.charAt(0).toLowerCase() + aggregateName.slice(1) + 'Id';
60
- %>
127
+ -%>
61
128
  messageBroker.publish<%= event.integrationEventClassName %>(new <%= event.integrationEventClassName %>(<% if (event.fields && event.fields.length > 0) { event.fields.forEach(function(field, idx) { %><%= field.name === _aggrIdField ? 'event.getAggregateId()' : 'event.get' + field.name.charAt(0).toUpperCase() + field.name.slice(1) + '()' %><%= idx < event.fields.length - 1 ? ', ' : '' %><% }); } %>));
62
- <% } else { %>
129
+ <% } -%>
130
+ <% if (_hasWorkflowAction) { _eventNotifies.forEach(n => {
131
+ const _svcName = n.flowPascal.charAt(0).toLowerCase() + n.flowPascal.slice(1) + 'WorkFlowService';
132
+ const _aggrIdFieldWf = aggregateName.charAt(0).toLowerCase() + aggregateName.slice(1) + 'Id';
133
+ const _eventFieldNames = new Set((event.fields || []).map(f => f.name));
134
+ const _hasTypedInput = n.inputFields && n.inputFields.length > 0;
135
+ -%>
136
+ <% if (_hasTypedInput) { -%>
137
+ <%= _svcName %>.startAsync(
138
+ event.getAggregateId(),
139
+ new <%= n.flowPascal %>Input(<% n.inputFields.forEach(function(inputF, idx) {
140
+ let _arg;
141
+ if (inputF.name === _aggrIdFieldWf) {
142
+ _arg = 'event.getAggregateId()';
143
+ } else if (_eventFieldNames.has(inputF.name)) {
144
+ _arg = 'event.get' + inputF.name.charAt(0).toUpperCase() + inputF.name.slice(1) + '()';
145
+ } else if (inputF.name.endsWith('At') && inputF.javaType === 'LocalDateTime') {
146
+ _arg = 'LocalDateTime.now()';
147
+ } else {
148
+ _arg = 'null /* TODO: provide ' + inputF.name + ' */';
149
+ }
150
+ %><%= idx > 0 ? ', ' : '' %><%= _arg %><% }); %>)
151
+ );
152
+ <% } else { -%>
153
+ <%= _svcName %>.startAsync(event.getAggregateId());
154
+ <% } -%>
155
+ <% }); } -%>
156
+ <% if (!_hasBrokerAction && !_hasWorkflowAction) { -%>
63
157
  // TODO: handle <%= event.name %> — add your side-effect logic here
64
158
  // e.g.: notificationService.notify(event);
65
159
  // readModelUpdater.on(event);
66
- <% } %>
160
+ <% } -%>
67
161
  }
68
162
  <% }); %>
69
163
  }
@@ -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
  ```
@@ -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 {}