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.
- package/AGENTS.md +2 -0
- package/DOMAIN_YAML_GUIDE.md +3 -1
- package/QUICK_REFERENCE.md +8 -4
- package/bin/eva4j.js +70 -2
- package/config/defaults.json +1 -0
- package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
- package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
- package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
- package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
- package/docs/commands/EVALUATE_SYSTEM.md +272 -8
- package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
- package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
- package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
- package/docs/commands/INDEX.md +27 -3
- package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
- package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
- package/docs/prototype/system/RISKS.md +277 -0
- package/docs/prototype/system/customers.yaml +133 -0
- package/docs/prototype/system/inventory.yaml +109 -0
- package/docs/prototype/system/notifications.yaml +131 -0
- package/docs/prototype/system/orders.yaml +241 -0
- package/docs/prototype/system/payments.yaml +256 -0
- package/docs/prototype/system/products.yaml +168 -0
- package/docs/prototype/system/system.yaml +269 -0
- package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
- package/examples/domain-read-models.yaml +2 -2
- package/examples/system/customer.yaml +89 -0
- package/examples/system/orders.yaml +119 -0
- package/examples/system/product.yaml +27 -0
- package/examples/system/system.yaml +80 -0
- package/package.json +1 -1
- package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
- package/src/agents/design-gap-analyst.agent.md +383 -0
- package/src/agents/design-reviewer-temporal.agent.md +412 -0
- package/src/agents/design-reviewer.agent.md +31 -5
- package/src/agents/implement-use-cases.prompt.md +179 -0
- package/src/agents/ux-gap-analyst.agent.md +412 -0
- package/src/commands/add-rabbitmq-client.js +261 -0
- package/src/commands/add-temporal-client.js +22 -2
- package/src/commands/build.js +267 -11
- package/src/commands/evaluate-system.js +700 -13
- package/src/commands/generate-entities.js +308 -16
- package/src/commands/generate-rabbitmq-event.js +665 -0
- package/src/commands/generate-rabbitmq-listener.js +205 -0
- package/src/commands/generate-temporal-activity.js +968 -34
- package/src/commands/generate-temporal-flow.js +95 -38
- package/src/commands/generate-temporal-system.js +708 -0
- package/src/skills/build-system-yaml/SKILL.md +222 -2
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +50 -4
- package/src/skills/build-system-yaml/references/module-spec.md +57 -8
- package/src/skills/build-temporal-system/SKILL.md +752 -0
- package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
- package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
- package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
- package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
- package/src/skills/implement-use-case/SKILL.md +350 -0
- package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
- package/src/skills/requirements-elicitation/SKILL.md +228 -0
- package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
- package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
- package/src/utils/bounded-context-diagram.js +844 -0
- package/src/utils/domain-validator.js +266 -4
- package/src/utils/naming.js +10 -0
- package/src/utils/system-validator.js +169 -11
- package/src/utils/system-yaml-parser.js +318 -0
- package/src/utils/temporal-validator.js +497 -0
- package/src/utils/yaml-to-entity.js +10 -7
- package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
- package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -2
- package/templates/aggregate/JpaEntity.java.ejs +2 -2
- package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
- package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
- package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
- package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
- package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
- package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
- package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
- package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
- package/templates/base/root/AGENTS.md.ejs +1 -1
- package/templates/crud/EndpointsController.java.ejs +1 -1
- package/templates/crud/ScaffoldCommand.java.ejs +5 -2
- package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
- package/templates/crud/ScaffoldQuery.java.ejs +5 -2
- package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
- package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
- package/templates/evaluate/report.html.ejs +1447 -90
- package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
- package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
- package/templates/ports/PortAclMapper.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +7 -22
- package/templates/ports/PortFeignClient.java.ejs +4 -0
- package/templates/ports/PortResponseDto.java.ejs +1 -1
- package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
- package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
- package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
- package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
- package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
- package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
- package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
- package/templates/read-model/ReadModelJpa.java.ejs +1 -1
- package/templates/read-model/ReadModelJpaRepository.java.ejs +2 -0
- package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
- package/templates/read-model/ReadModelRepositoryImpl.java.ejs +9 -5
- package/templates/read-model/ReadModelSyncHandler.java.ejs +2 -0
- package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
- package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
- package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
- package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
- package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
- package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
- package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/NestedType.java.ejs +12 -0
- package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
- package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
- package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
- package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
- package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
- package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
- package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
- package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
- package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
- package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
- package/COMMAND_EVALUATION.md +0 -911
- package/test-c2010.js +0 -49
- package/test-update-compat.js +0 -109
- 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
|
|
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
|
-
<%
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
16
|
+
acks: all
|
|
17
|
+
retries: 10
|
|
11
18
|
consumer:
|
|
12
|
-
group-id:
|
|
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:
|
|
36
|
+
concurrency: 5
|
|
24
37
|
retry:
|
|
25
|
-
max-attempts:
|
|
26
|
-
backoff-delay:
|
|
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,15 +1,15 @@
|
|
|
1
1
|
spring:
|
|
2
2
|
kafka:
|
|
3
3
|
bootstrap-servers:
|
|
4
|
-
- localhost:9092
|
|
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:
|
|
10
|
+
retries: 1
|
|
11
11
|
consumer:
|
|
12
|
-
group-id: <%= projectName %>-
|
|
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:
|
|
24
|
+
concurrency: 1
|
|
24
25
|
retry:
|
|
25
|
-
max-attempts:
|
|
26
|
-
backoff-delay:
|
|
26
|
+
max-attempts: 1
|
|
27
|
+
backoff-delay: 100
|
|
28
|
+
|
|
29
|
+
kafka:
|
|
30
|
+
topic-defaults:
|
|
31
|
+
partitions: 1
|
|
32
|
+
replicas: 1
|
|
@@ -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
|
|
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
|
-
|
|
11
|
-
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
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
|
|
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,
|
|
9
|
+
public record <%= useCaseName %>Command(<%- idType %> id, <%- itemIdType %> itemId) implements Command {}
|