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.
- package/AGENTS.md +220 -5
- package/DOMAIN_YAML_GUIDE.md +188 -3
- package/FUTURE_FEATURES.md +33 -52
- 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 +290 -10
- 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-events.yaml +26 -0
- package/examples/domain-read-models.yaml +113 -0
- 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/read-model-spec.md +664 -0
- 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 +34 -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 +560 -24
- package/src/commands/generate-http-exchange.js +3 -0
- package/src/commands/generate-kafka-event.js +3 -0
- package/src/commands/generate-kafka-listener.js +3 -0
- package/src/commands/generate-rabbitmq-event.js +665 -0
- package/src/commands/generate-rabbitmq-listener.js +205 -0
- package/src/commands/generate-record.js +2 -2
- package/src/commands/generate-resource.js +4 -1
- package/src/commands/generate-temporal-activity.js +970 -33
- package/src/commands/generate-temporal-flow.js +98 -38
- package/src/commands/generate-temporal-system.js +708 -0
- package/src/commands/generate-usecase.js +4 -1
- package/src/skills/build-system-yaml/SKILL.md +343 -2
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +253 -26
- package/src/skills/build-system-yaml/references/module-spec.md +90 -9
- package/src/skills/build-system-yaml/references/system-yaml-spec.md +36 -0
- 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/config-manager.js +4 -2
- package/src/utils/domain-validator.js +495 -17
- package/src/utils/naming.js +20 -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/validator.js +3 -1
- package/src/utils/yaml-to-entity.js +281 -9
- package/templates/aggregate/AggregateRepository.java.ejs +4 -0
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +8 -0
- package/templates/aggregate/AggregateRoot.java.ejs +38 -4
- package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
- package/templates/aggregate/JpaAggregateRoot.java.ejs +4 -4
- 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/DeleteCommandHandler.java.ejs +19 -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/crud/UpdateCommandHandler.java.ejs +53 -2
- 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/ReadModelDomain.java.ejs +46 -0
- package/templates/read-model/ReadModelJpa.java.ejs +58 -0
- package/templates/read-model/ReadModelJpaRepository.java.ejs +13 -0
- package/templates/read-model/ReadModelKafkaListener.java.ejs +64 -0
- package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
- package/templates/read-model/ReadModelRepository.java.ejs +42 -0
- package/templates/read-model/ReadModelRepositoryImpl.java.ejs +85 -0
- package/templates/read-model/ReadModelSyncHandler.java.ejs +54 -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
|
@@ -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
|
|
@@ -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
|
```
|
|
@@ -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
|
-
<%
|
|
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
|
|
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 {}
|
|
@@ -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
|
}
|