eva4j 1.0.13 → 1.0.15

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 (106) hide show
  1. package/AGENTS.md +314 -10
  2. package/COMMAND_EVALUATION.md +15 -16
  3. package/DOMAIN_YAML_GUIDE.md +576 -10
  4. package/FUTURE_FEATURES.md +1627 -1168
  5. package/README.md +318 -13
  6. package/bin/eva4j.js +34 -0
  7. package/config/defaults.json +1 -0
  8. package/design-system.md +797 -0
  9. package/docs/commands/EVALUATE_SYSTEM.md +994 -0
  10. package/docs/commands/GENERATE_ENTITIES.md +795 -6
  11. package/docs/commands/INDEX.md +10 -1
  12. package/examples/domain-endpoints-relations.yaml +353 -0
  13. package/examples/domain-endpoints-versioned.yaml +144 -0
  14. package/examples/domain-endpoints.yaml +135 -0
  15. package/examples/domain-events.yaml +166 -20
  16. package/examples/domain-listeners.yaml +212 -0
  17. package/examples/domain-one-to-many.yaml +1 -0
  18. package/examples/domain-one-to-one.yaml +1 -0
  19. package/examples/domain-ports.yaml +414 -0
  20. package/examples/domain-soft-delete.yaml +47 -44
  21. package/examples/system/notification.yaml +147 -0
  22. package/examples/system/product.yaml +185 -0
  23. package/examples/system/system.yaml +112 -0
  24. package/examples/system-report.html +971 -0
  25. package/examples/system.yaml +332 -0
  26. package/package.json +2 -1
  27. package/src/commands/build.js +714 -0
  28. package/src/commands/create.js +7 -3
  29. package/src/commands/detach.js +1 -0
  30. package/src/commands/evaluate-system.js +610 -0
  31. package/src/commands/generate-entities.js +1331 -49
  32. package/src/commands/generate-http-exchange.js +2 -0
  33. package/src/commands/generate-kafka-event.js +98 -11
  34. package/src/generators/base-generator.js +8 -1
  35. package/src/generators/postman-generator.js +188 -0
  36. package/src/generators/shared-generator.js +10 -0
  37. package/src/utils/config-manager.js +54 -0
  38. package/src/utils/context-builder.js +1 -0
  39. package/src/utils/domain-diagram.js +192 -0
  40. package/src/utils/domain-validator.js +970 -0
  41. package/src/utils/fake-data.js +376 -0
  42. package/src/utils/naming.js +3 -2
  43. package/src/utils/system-validator.js +434 -0
  44. package/src/utils/yaml-to-entity.js +302 -8
  45. package/templates/aggregate/AggregateMapper.java.ejs +3 -2
  46. package/templates/aggregate/AggregateRepository.java.ejs +8 -2
  47. package/templates/aggregate/AggregateRepositoryImpl.java.ejs +13 -3
  48. package/templates/aggregate/AggregateRoot.java.ejs +60 -2
  49. package/templates/aggregate/DomainEventHandler.java.ejs +27 -20
  50. package/templates/aggregate/DomainEventRecord.java.ejs +24 -8
  51. package/templates/aggregate/DomainEventSnapshot.java.ejs +46 -0
  52. package/templates/aggregate/JpaAggregateRoot.java.ejs +6 -0
  53. package/templates/aggregate/JpaRepository.java.ejs +5 -0
  54. package/templates/base/gradle/build.gradle.ejs +3 -2
  55. package/templates/base/root/AGENTS.md.ejs +306 -45
  56. package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1663 -0
  57. package/templates/base/root/skill-build-system-yaml.ejs +1446 -0
  58. package/templates/base/root/system.yaml.ejs +97 -0
  59. package/templates/crud/ApplicationMapper.java.ejs +4 -0
  60. package/templates/crud/Controller.java.ejs +4 -4
  61. package/templates/crud/CreateCommand.java.ejs +4 -0
  62. package/templates/crud/CreateItemDto.java.ejs +4 -0
  63. package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
  64. package/templates/crud/DeleteCommandHandler.java.ejs +10 -2
  65. package/templates/crud/EndpointsController.java.ejs +178 -0
  66. package/templates/crud/FindByQuery.java.ejs +17 -0
  67. package/templates/crud/FindByQueryHandler.java.ejs +57 -0
  68. package/templates/crud/ListQuery.java.ejs +1 -1
  69. package/templates/crud/ListQueryHandler.java.ejs +8 -8
  70. package/templates/crud/ScaffoldCommand.java.ejs +12 -0
  71. package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
  72. package/templates/crud/ScaffoldQuery.java.ejs +13 -0
  73. package/templates/crud/ScaffoldQueryHandler.java.ejs +41 -0
  74. package/templates/crud/SubEntityAddCommand.java.ejs +21 -0
  75. package/templates/crud/SubEntityAddCommandHandler.java.ejs +43 -0
  76. package/templates/crud/SubEntityRemoveCommand.java.ejs +9 -0
  77. package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +42 -0
  78. package/templates/crud/TransitionCommand.java.ejs +9 -0
  79. package/templates/crud/TransitionCommandHandler.java.ejs +39 -0
  80. package/templates/crud/UpdateCommand.java.ejs +4 -0
  81. package/templates/evaluate/report.html.ejs +1363 -0
  82. package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
  83. package/templates/kafka-event/Event.java.ejs +16 -0
  84. package/templates/kafka-listener/KafkaController.java.ejs +1 -1
  85. package/templates/kafka-listener/KafkaListenerClass.java.ejs +1 -1
  86. package/templates/kafka-listener/ListenerClass.java.ejs +65 -0
  87. package/templates/kafka-listener/ListenerCommand.java.ejs +31 -0
  88. package/templates/kafka-listener/ListenerCommandHandler.java.ejs +23 -0
  89. package/templates/kafka-listener/ListenerIntegrationEvent.java.ejs +37 -0
  90. package/templates/kafka-listener/ListenerMethod.java.ejs +1 -1
  91. package/templates/kafka-listener/ListenerNestedType.java.ejs +28 -0
  92. package/templates/mock/MockEvent.java.ejs +10 -0
  93. package/templates/mock/MockMessageBrokerImpl.java.ejs +35 -0
  94. package/templates/mock/MockMessageBrokerImplMethod.java.ejs +6 -0
  95. package/templates/mock/SpringEventListener.java.ejs +61 -0
  96. package/templates/ports/PortDomainModel.java.ejs +35 -0
  97. package/templates/ports/PortFeignAdapter.java.ejs +67 -0
  98. package/templates/ports/PortFeignClient.java.ejs +45 -0
  99. package/templates/ports/PortFeignConfig.java.ejs +24 -0
  100. package/templates/ports/PortInterface.java.ejs +45 -0
  101. package/templates/ports/PortNestedType.java.ejs +28 -0
  102. package/templates/ports/PortRequestDto.java.ejs +30 -0
  103. package/templates/ports/PortResponseDto.java.ejs +28 -0
  104. package/templates/postman/Collection.json.ejs +1 -1
  105. package/templates/postman/UnifiedCollection.json.ejs +185 -0
  106. package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
@@ -1 +1,3 @@
1
- messageBroker.publish<%= eventClassName %>(new <%= eventClassName %>(<% if (domainEventFields && domainEventFields.length > 0) { domainEventFields.forEach(function(field, idx) { %>event.get<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>()<%= idx < domainEventFields.length - 1 ? ', ' : '' %><% }); } %>));
1
+ <%
2
+ const _aggrIdField = aggregateName ? aggregateName.charAt(0).toLowerCase() + aggregateName.slice(1) + 'Id' : null;
3
+ %>messageBroker.publish<%= eventClassName %>(new <%= eventClassName %>(<% if (domainEventFields && domainEventFields.length > 0) { domainEventFields.forEach(function(field, idx) { %><%= (_aggrIdField && field.name === _aggrIdField) ? 'event.getAggregateId()' : 'event.get' + field.name.charAt(0).toUpperCase() + field.name.slice(1) + '()' %><%= idx < domainEventFields.length - 1 ? ', ' : '' %><% }); } %>));
@@ -3,6 +3,12 @@ package <%= packageName %>.<%= moduleName %>.application.events;
3
3
  <% const needsLocalDate = eventFields && eventFields.some(f => f.javaType === 'LocalDate' || f.javaType === 'LocalDateTime' || f.javaType === 'LocalTime'); %>
4
4
  <% const needsUUID = eventFields && eventFields.some(f => f.javaType === 'UUID'); %>
5
5
  <% const needsList = eventFields && eventFields.some(f => f.isCollection); %>
6
+ <%
7
+ const _STANDARD_TYPES = new Set(['String','Integer','Long','Double','Float','Boolean','BigDecimal','LocalDate','LocalDateTime','LocalTime','Instant','UUID']);
8
+ const _customElementTypes = eventFields
9
+ ? [...new Set(eventFields.filter(f => f.isCollection && f.collectionElementType && !_STANDARD_TYPES.has(f.collectionElementType)).map(f => f.collectionElementType))]
10
+ : [];
11
+ %>
6
12
  <% if (needsBigDecimal) { %>
7
13
  import java.math.BigDecimal;
8
14
  <% } %>
@@ -16,7 +22,17 @@ import java.util.UUID;
16
22
  <% if (needsList) { %>
17
23
  import java.util.List;
18
24
  <% } %>
25
+ <% _customElementTypes.forEach(typeName => { %>
26
+ import <%= packageName %>.<%= moduleName %>.domain.models.events.<%- typeName %>;
27
+ <% }); %>
19
28
 
29
+ /**
30
+ * Integration Event — broker-side projection of the corresponding domain event.
31
+ * <p>
32
+ * This record is the data carrier published to the message broker. It is intentionally
33
+ * decoupled from the domain event so that changes in broker technology or serialization
34
+ * format never affect the domain model.
35
+ */
20
36
  public record <%= eventClassName %>(
21
37
  <% if (eventFields && eventFields.length > 0) { %>
22
38
  <% eventFields.forEach((field, idx) => { %>
@@ -23,7 +23,7 @@ public class KafkaController {
23
23
  this.useCaseMediator = useCaseMediator;
24
24
  }
25
25
 
26
- @KafkaListener(topics = "<%= topicSpringProperty %>")
26
+ @KafkaListener(topics = "<%= topicSpringProperty %>", groupId = "${spring.application.name}-<%= moduleName %>-group")
27
27
  void <%= methodName %>(EventEnvelope<Map<String, Object>> event, Acknowledgment ack) {
28
28
  // TODO: Implement event processing logic
29
29
  // Example: useCaseMediator.dispatch(new YourCommand(event.data()));
@@ -26,7 +26,7 @@ public class <%= listenerClassName %> {
26
26
  this.useCaseMediator = useCaseMediator;
27
27
  }
28
28
 
29
- @KafkaListener(topics = "<%= topicSpringProperty %>")
29
+ @KafkaListener(topics = "<%= topicSpringProperty %>", groupId = "${spring.application.name}-<%= moduleName %>-group")
30
30
  public void handle(EventEnvelope<Map<String, Object>> event, Acknowledgment ack) {
31
31
  // TODO: Implement event processing logic
32
32
  // Example: useCaseMediator.dispatch(new YourCommand(event.data()));
@@ -0,0 +1,65 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.kafkaListener;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= commandClassName %>;
4
+ import <%= packageName %>.shared.infrastructure.configurations.useCaseConfig.UseCaseMediator;
5
+ import <%= packageName %>.shared.infrastructure.eventEnvelope.EventEnvelope;
6
+
7
+ import com.fasterxml.jackson.databind.ObjectMapper;
8
+ import org.springframework.beans.factory.annotation.Value;
9
+ import org.springframework.kafka.annotation.KafkaListener;
10
+ import org.springframework.kafka.support.Acknowledgment;
11
+ import org.springframework.stereotype.Component;
12
+
13
+ import java.util.Map;
14
+ <% const hasLists = fields && fields.some(f => f.javaType && f.javaType.startsWith('List')); %>
15
+ <% if (hasLists) { %>import java.util.List;
16
+ <% } %><% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
17
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
18
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
19
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
20
+ <% if (needsBigDecimal) { %>import java.math.BigDecimal;
21
+ <% } %><% if (needsLocalDate) { %>import java.time.LocalDate;
22
+ import java.time.LocalDateTime;
23
+ import java.time.LocalTime;
24
+ <% } %><% if (needsInstant) { %>import java.time.Instant;
25
+ <% } %><% if (needsUUID) { %>import java.util.UUID;
26
+ <% } %><% (nestedTypes || []).forEach(nt => { %>import <%= packageName %>.<%= moduleName %>.application.events.<%= nt.name %>;
27
+ <% }); %>
28
+ /**
29
+ * Kafka listener for topic <%= topicConstant %>.
30
+ * Consumes events produced by: <%= producer %>.
31
+ * Dispatches to use case: <%= useCase %>.
32
+ */
33
+ @Component("<%= moduleName %>.<%= listenerClassName %>")
34
+ public class <%= listenerClassName %> {
35
+
36
+ private final UseCaseMediator useCaseMediator;
37
+ private final ObjectMapper objectMapper;
38
+
39
+ @Value("<%= topicSpringProperty %>")
40
+ private String <%= topicVariableName %>Topic;
41
+
42
+ public <%= listenerClassName %>(UseCaseMediator useCaseMediator, ObjectMapper objectMapper) {
43
+ this.useCaseMediator = useCaseMediator;
44
+ this.objectMapper = objectMapper;
45
+ }
46
+
47
+ @KafkaListener(topics = "<%= topicSpringProperty %>", groupId = "${spring.application.name}-<%= moduleName %>-group")
48
+ public void handle(EventEnvelope<Map<String, Object>> event, Acknowledgment ack) {
49
+ <% (fields || []).forEach(f => { %><%
50
+ const listMatch = f.javaType.match(/^List<(.+)>$/);
51
+ if (listMatch) { %>
52
+ <%- f.javaType %> <%= f.name %> = objectMapper.convertValue(
53
+ event.data().get("<%= f.name %>"),
54
+ objectMapper.getTypeFactory().constructCollectionType(List.class, <%= listMatch[1] %>.class));
55
+ <% } else { %>
56
+ <%- f.javaType %> <%= f.name %> = objectMapper.convertValue(event.data().get("<%= f.name %>"), <%- f.javaType %>.class);
57
+ <% } %><% }); %>
58
+ useCaseMediator.dispatch(new <%= commandClassName %>(
59
+ <% (fields || []).forEach((f, i) => { %>
60
+ <%= f.name %><%= i < fields.length - 1 ? ',' : '' %>
61
+ <% }); %>
62
+ ));
63
+ ack.acknowledge();
64
+ }
65
+ }
@@ -0,0 +1,31 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.commands;
2
+
3
+ import <%= packageName %>.shared.domain.interfaces.Command;
4
+ <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
5
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
6
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
7
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
8
+ <% const needsList = fields && fields.some(f => f.javaType && f.javaType.startsWith('List')); %>
9
+ <% if (needsBigDecimal) { %>import java.math.BigDecimal;
10
+ <% } %><% if (needsLocalDate) { %>import java.time.LocalDate;
11
+ import java.time.LocalDateTime;
12
+ import java.time.LocalTime;
13
+ <% } %><% if (needsInstant) { %>import java.time.Instant;
14
+ <% } %><% if (needsUUID) { %>import java.util.UUID;
15
+ <% } %><% if (needsList) { %>import java.util.List;
16
+ <% } %><% (nestedTypes || []).forEach(nt => { %>import <%= packageName %>.<%= moduleName %>.application.events.<%= nt.name %>;
17
+ <% }); %>
18
+ /**
19
+ * Command dispatched when <%= event %> is received from topic <%= topicConstant %>.
20
+ * Produced by: <%= producer %>.
21
+ * Handled by use case: <%= useCase %>.
22
+ */
23
+ public record <%= commandClassName %>(
24
+ <% if (fields && fields.length > 0) { %>
25
+ <% fields.forEach((f, i) => { %>
26
+ <%- f.javaType %> <%= f.name %><%= i < fields.length - 1 ? ',' : '' %>
27
+ <% }); %>
28
+ <% } else { %>
29
+ // TODO: Add command fields
30
+ <% } %>
31
+ ) implements Command {}
@@ -0,0 +1,23 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.usecases;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= commandClassName %>;
4
+ import <%= packageName %>.shared.domain.annotations.ApplicationComponent;
5
+ import <%= packageName %>.shared.domain.interfaces.CommandHandler;
6
+
7
+ /**
8
+ * Handles <%= commandClassName %> dispatched when <%= event %> is received.
9
+ * Produced by: <%= producer %>.
10
+ */
11
+ @ApplicationComponent
12
+ public class <%= useCase %>CommandHandler implements CommandHandler<<%= commandClassName %>> {
13
+
14
+ public <%= useCase %>CommandHandler() {
15
+
16
+ }
17
+
18
+ @Override
19
+ public void handle(<%= commandClassName %> command) {
20
+ // TODO: implement <%= useCase %> logic
21
+ }
22
+
23
+ }
@@ -0,0 +1,37 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.events;
2
+ <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
3
+ <% const needsLocalDate = fields && fields.some(f => f.javaType === 'LocalDate' || f.javaType === 'LocalDateTime' || f.javaType === 'LocalTime'); %>
4
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
5
+ <% const needsList = fields && fields.some(f => f.javaType && f.javaType.startsWith('List')); %>
6
+ <% if (needsBigDecimal) { %>
7
+ import java.math.BigDecimal;
8
+ <% } %>
9
+ <% if (needsLocalDate) { %>
10
+ import java.time.LocalDate;
11
+ import java.time.LocalDateTime;
12
+ import java.time.LocalTime;
13
+ <% } %>
14
+ <% if (needsUUID) { %>
15
+ import java.util.UUID;
16
+ <% } %>
17
+ <% if (needsList) { %>
18
+ import java.util.List;
19
+ <% } %>
20
+
21
+ /**
22
+ * Integration Event consumed from topic <%= topicConstant %>.
23
+ * Produced by: <%= producer %>.
24
+ * <p>
25
+ * This record is the typed payload received from the message broker.
26
+ * It is intentionally decoupled from the producer's domain model so that
27
+ * changes in the producer never force changes here beyond the agreed contract.
28
+ */
29
+ public record <%= integrationEventClassName %>(
30
+ <% if (fields && fields.length > 0) { %>
31
+ <% fields.forEach((field, idx) => { %>
32
+ <%- field.javaType %> <%= field.name %><%= idx < fields.length - 1 ? ',' : '' %>
33
+ <% }); %>
34
+ <% } else { %>
35
+ // TODO: Add payload fields matching the producer's Integration Event
36
+ <% } %>
37
+ ) {}
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- @KafkaListener(topics = "<%= topicSpringProperty %>")
3
+ @KafkaListener(topics = "<%= topicSpringProperty %>", groupId = "${spring.application.name}-<%= moduleName %>-group")
4
4
  void <%= methodName %>(EventEnvelope<Map<String, Object>> event, Acknowledgment ack) {
5
5
  // TODO: Implement event processing logic
6
6
  // Example: useCaseMediator.dispatch(new YourCommand(event.data()));
@@ -0,0 +1,28 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.events;
2
+ <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
3
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
4
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
5
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
6
+ <% const needsList = fields && fields.some(f => f.javaType && f.javaType.startsWith('List')); %>
7
+ <% if (needsBigDecimal) { %>
8
+ import java.math.BigDecimal;
9
+ <% } %><% if (needsLocalDate) { %>
10
+ import java.time.LocalDate;
11
+ import java.time.LocalDateTime;
12
+ import java.time.LocalTime;
13
+ <% } %><% if (needsInstant) { %>
14
+ import java.time.Instant;
15
+ <% } %><% if (needsUUID) { %>
16
+ import java.util.UUID;
17
+ <% } %><% if (needsList) { %>
18
+ import java.util.List;
19
+ <% } %>
20
+ public record <%= name %>(
21
+ <% if (fields && fields.length > 0) { %>
22
+ <% fields.forEach((field, idx) => { %>
23
+ <%- field.javaType %> <%= field.name %><%= idx < fields.length - 1 ? ',' : '' %>
24
+ <% }); %>
25
+ <% } else { %>
26
+ // TODO: Add payload fields
27
+ <% } %>
28
+ ) {}
@@ -0,0 +1,10 @@
1
+ package <%= packageName %>.shared.infrastructure.mockEvent;
2
+
3
+ import java.util.Map;
4
+
5
+ /**
6
+ * In-memory event envelope for mock mode.
7
+ * Routes events between modules via Spring's ApplicationEventPublisher
8
+ * instead of Kafka. Generated by eva build --mock.
9
+ */
10
+ public record MockEvent(String topic, Map<String, Object> data) {}
@@ -0,0 +1,35 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.adapters.kafkaMessageBroker;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.events.<%= eventClassName %>;
4
+ import <%= packageName %>.<%= moduleName %>.application.ports.MessageBroker;
5
+ import <%= packageName %>.shared.infrastructure.mockEvent.MockEvent;
6
+ import com.fasterxml.jackson.databind.ObjectMapper;
7
+ import org.springframework.context.ApplicationEventPublisher;
8
+ import org.springframework.stereotype.Component;
9
+
10
+ import java.util.Map;
11
+
12
+ /**
13
+ * Mock MessageBroker implementation — routes events via Spring's
14
+ * ApplicationEventPublisher instead of Kafka.
15
+ * Generated by eva build --mock. Restored automatically on eva build.
16
+ */
17
+ @Component("<%= moduleCamelCase %>KafkaMessageBroker")
18
+ public class <%= modulePascalCase %>KafkaMessageBroker implements MessageBroker {
19
+
20
+ private final ApplicationEventPublisher eventPublisher;
21
+ private final ObjectMapper objectMapper;
22
+
23
+ public <%= modulePascalCase %>KafkaMessageBroker(ApplicationEventPublisher eventPublisher,
24
+ ObjectMapper objectMapper) {
25
+ this.eventPublisher = eventPublisher;
26
+ this.objectMapper = objectMapper;
27
+ }
28
+
29
+ @Override
30
+ public void publish<%= eventClassName %>(<%= eventClassName %> event) {
31
+ @SuppressWarnings("unchecked")
32
+ Map<String, Object> payload = objectMapper.convertValue(event, Map.class);
33
+ eventPublisher.publishEvent(new MockEvent("<%= topicName %>", payload));
34
+ }
35
+ }
@@ -0,0 +1,6 @@
1
+ @Override
2
+ public void publish<%= eventClassName %>(<%= eventClassName %> event) {
3
+ @SuppressWarnings("unchecked")
4
+ Map<String, Object> payload = objectMapper.convertValue(event, Map.class);
5
+ eventPublisher.publishEvent(new MockEvent("<%= topicName %>", payload));
6
+ }
@@ -0,0 +1,61 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.kafkaListener;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= commandClassName %>;
4
+ import <%= packageName %>.shared.infrastructure.configurations.useCaseConfig.UseCaseMediator;
5
+ import <%= packageName %>.shared.infrastructure.mockEvent.MockEvent;
6
+
7
+ import com.fasterxml.jackson.databind.ObjectMapper;
8
+ import org.springframework.context.event.EventListener;
9
+ import org.springframework.stereotype.Component;
10
+
11
+ import java.util.Map;
12
+ <% const hasLists = fields && fields.some(f => f.javaType && f.javaType.startsWith('List')); %>
13
+ <% if (hasLists) { %>import java.util.List;
14
+ <% } %><% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
15
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
16
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
17
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
18
+ <% if (needsBigDecimal) { %>import java.math.BigDecimal;
19
+ <% } %><% if (needsLocalDate) { %>import java.time.LocalDate;
20
+ import java.time.LocalDateTime;
21
+ import java.time.LocalTime;
22
+ <% } %><% if (needsInstant) { %>import java.time.Instant;
23
+ <% } %><% if (needsUUID) { %>import java.util.UUID;
24
+ <% } %><% (nestedTypes || []).forEach(nt => { %>import <%= packageName %>.<%= moduleName %>.application.events.<%= nt.name %>;
25
+ <% }); %>
26
+ /**
27
+ * Spring in-memory listener for topic <%= topicConstant %>.
28
+ * Consumes MockEvent published by: <%= producer %>.
29
+ * Dispatches to use case: <%= useCase %>.
30
+ * Generated by eva build --mock. Restored automatically on eva build.
31
+ */
32
+ @Component("<%= moduleName %>.<%= listenerClassName %>")
33
+ public class <%= listenerClassName %> {
34
+
35
+ private final UseCaseMediator useCaseMediator;
36
+ private final ObjectMapper objectMapper;
37
+
38
+ public <%= listenerClassName %>(UseCaseMediator useCaseMediator, ObjectMapper objectMapper) {
39
+ this.useCaseMediator = useCaseMediator;
40
+ this.objectMapper = objectMapper;
41
+ }
42
+
43
+ @EventListener(condition = "#event.topic() == '<%= topicConstant %>'")
44
+ public void handle(MockEvent event) {
45
+ Map<String, Object> data = event.data();
46
+ <% (fields || []).forEach(f => { %><%
47
+ const listMatch = f.javaType.match(/^List<(.+)>$/);
48
+ if (listMatch) { %>
49
+ <%- f.javaType %> <%= f.name %> = objectMapper.convertValue(
50
+ data.get("<%= f.name %>"),
51
+ objectMapper.getTypeFactory().constructCollectionType(List.class, <%= listMatch[1] %>.class));
52
+ <% } else { %>
53
+ <%- f.javaType %> <%= f.name %> = objectMapper.convertValue(data.get("<%= f.name %>"), <%- f.javaType %>.class);
54
+ <% } %><% }); %>
55
+ useCaseMediator.dispatch(new <%= commandClassName %>(
56
+ <% (fields || []).forEach((f, i) => { %>
57
+ <%= f.name %><%= i < fields.length - 1 ? ',' : '' %>
58
+ <% }); %>
59
+ ));
60
+ }
61
+ }
@@ -0,0 +1,35 @@
1
+ package <%= packageName %>.<%= moduleName %>.domain.models.<%= adapterPackage %>;
2
+ <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
3
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
4
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
5
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
6
+ <% const needsList = fields && fields.some(f => f.javaType && f.javaType.startsWith('List')); %>
7
+ <% if (needsBigDecimal) { %>
8
+ import java.math.BigDecimal;
9
+ <% } %><% if (needsLocalDate) { %>
10
+ import java.time.LocalDate;
11
+ import java.time.LocalDateTime;
12
+ import java.time.LocalTime;
13
+ <% } %><% if (needsInstant) { %>
14
+ import java.time.Instant;
15
+ <% } %><% if (needsUUID) { %>
16
+ import java.util.UUID;
17
+ <% } %><% if (needsList) { %>
18
+ import java.util.List;
19
+ <% } %>
20
+ /**
21
+ * Domain model for the <%- name %> concept consumed from <%= target || serviceName %>.
22
+ *
23
+ * <p>ACL (Anti-Corruption Layer): this record is the domain-side abstraction.
24
+ * The corresponding infra DTO lives in
25
+ * {@code infrastructure.adapters.<%= adapterPackage %>} and is mapped
26
+ * by the Feign adapter — if the external API changes, only the adapter
27
+ * mapper needs to be updated; domain logic using this type remains untouched.
28
+ */
29
+ public record <%= name %>(
30
+ <% if (fields && fields.length > 0) { %>
31
+ <% fields.forEach((f, i) => { %>
32
+ <%- f.javaType %> <%= f.name %><%= i < fields.length - 1 ? ',' : '' %>
33
+ <% }); %>
34
+ <% } %>
35
+ ) {}
@@ -0,0 +1,67 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.adapters.<%= adapterPackage %>;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.domain.repositories.<%= serviceName %>;
4
+ import org.springframework.stereotype.Component;
5
+ <% const allMethods = methods || []; %>
6
+ <% const hasAnyList = allMethods.some(m => m.returnList); %>
7
+ <% if (hasAnyList) { %>import java.util.List;
8
+ import java.util.stream.Collectors;
9
+ <% } %>
10
+ <% /* ACL: import domain models — the types the port interface returns */ %>
11
+ <% const uniqueDomainTypes = [...new Set(allMethods.filter(m => m.domainType).map(m => m.domainType))]; %>
12
+ <% uniqueDomainTypes.forEach(dt => { %>import <%= packageName %>.<%= moduleName %>.domain.models.<%= adapterPackage %>.<%= dt %>;
13
+ <% }); %>
14
+ <% /* Request DTOs and nested types stay in application.dtos */ %>
15
+ <% allMethods.filter(m => m.hasBody).forEach(m => { %>import <%= packageName %>.<%= moduleName %>.application.dtos.<%= m.requestDtoName %>;
16
+ <% }); %>
17
+ <% (nestedTypes || []).forEach(nt => { %>import <%= packageName %>.<%= moduleName %>.application.dtos.<%= nt.name %>;
18
+ <% }); %>
19
+ @Component("<%= moduleName %>.<%= feignAdapterClassName %>")
20
+ public class <%= feignAdapterClassName %> implements <%= serviceName %> {
21
+
22
+ private final <%= feignClientClassName %> feignClient;
23
+
24
+ public <%= feignAdapterClassName %>(<%= feignClientClassName %> feignClient) {
25
+ this.feignClient = feignClient;
26
+ }
27
+ <% allMethods.forEach(m => { %>
28
+ <% /* Public override returns domain model; FeignClient returns infra DTO */ %>
29
+ <% const domainReturn = !m.hasResponse ? 'void'
30
+ : m.returnList ? `List<${m.domainType}>`
31
+ : m.domainType; %>
32
+ <% const params = []; %>
33
+ <% const args = []; %>
34
+ <% m.pathVariables.forEach(pv => { params.push(`String ${pv}`); args.push(pv); }); %>
35
+ <% if (m.hasBody) { params.push(`${m.requestDtoName} body`); args.push('body'); } %>
36
+
37
+ @Override
38
+ public <%- domainReturn %> <%= m.name %>(<%= params.join(', ') %>) {
39
+ <% if (!m.hasResponse) { %>
40
+ feignClient.<%= m.name %>(<%= args.join(', ') %>);
41
+ <% } else if (m.returnList) { %>
42
+ List<<%= m.infraDtoName %>> dtos = feignClient.<%= m.name %>(<%= args.join(', ') %>);
43
+ return dtos.stream().map(dto -> to<%= m.domainType %>(dto)).collect(Collectors.toList());
44
+ <% } else { %>
45
+ return to<%= m.domainType %>(feignClient.<%= m.name %>(<%= args.join(', ') %>));
46
+ <% } %>
47
+ }
48
+ <% }); %>
49
+
50
+ // ── ACL Mappers ─────────────────────────────────────────────────────────
51
+ // Maps infra DTOs (shaped by the external service) to domain models.
52
+ // If the external API changes, only these methods need to be updated.
53
+ <%
54
+ // Build one mapper per method that has a response
55
+ const responseMethods = allMethods.filter(m => m.hasResponse);
56
+ %>
57
+ <% responseMethods.forEach(m => { %>
58
+ private <%- m.domainType %> to<%= m.domainType %>(<%= m.infraDtoName %> dto) {
59
+ if (dto == null) return null;
60
+ return new <%= m.domainType %>(
61
+ <% m.fields.forEach((f, i) => { %>
62
+ dto.<%= f.name %>()<%= i < m.fields.length - 1 ? ',' : '' %>
63
+ <% }); %>
64
+ );
65
+ }
66
+ <% }); %>
67
+ }
@@ -0,0 +1,45 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.adapters.<%= adapterPackage %>;
2
+
3
+ import org.springframework.cloud.openfeign.FeignClient;
4
+ import org.springframework.web.bind.annotation.*;
5
+ <% const allMethods = methods || []; %>
6
+ <% const bodyFields = allMethods.flatMap(m => m.bodyFields || []); %>
7
+ <% const hasAnyList = allMethods.some(m => m.returnList); %>
8
+ <% const needsBigDecimal = bodyFields.some(f => f.javaType === 'BigDecimal'); %>
9
+ <% const needsLocalDate = bodyFields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
10
+ <% const needsInstant = bodyFields.some(f => f.javaType === 'Instant'); %>
11
+ <% const needsUUID = bodyFields.some(f => f.javaType === 'UUID'); %>
12
+ <% const needsListField = bodyFields.some(f => f.javaType && f.javaType.startsWith('List')); %>
13
+ <% if (hasAnyList || needsListField) { %>import java.util.List;
14
+ <% } %><% if (needsBigDecimal) { %>import java.math.BigDecimal;
15
+ <% } %><% if (needsLocalDate) { %>import java.time.LocalDate;
16
+ import java.time.LocalDateTime;
17
+ import java.time.LocalTime;
18
+ <% } %><% if (needsInstant) { %>import java.time.Instant;
19
+ <% } %><% if (needsUUID) { %>import java.util.UUID;
20
+ <% } %>
21
+ <% /* Request DTOs and nested types live in application.dtos */ %>
22
+ <% allMethods.filter(m => m.hasBody).forEach(m => { %>import <%= packageName %>.<%= moduleName %>.application.dtos.<%= m.requestDtoName %>;
23
+ <% }); %>
24
+ <% (nestedTypes || []).forEach(nt => { %>import <%= packageName %>.<%= moduleName %>.application.dtos.<%= nt.name %>;
25
+ <% }); %>
26
+ @FeignClient(
27
+ name = "<%= feignClientName %>",
28
+ url = "${<%= baseUrlProperty %>}",
29
+ configuration = <%= feignConfigClassName %>.class
30
+ )
31
+ public interface <%= feignClientClassName %> {
32
+ <% allMethods.forEach(m => { %>
33
+ <% /* FeignClient returns the infra DTO (or void), not the domain model */ %>
34
+ <% const returnType = !m.hasResponse ? 'void'
35
+ : m.returnList ? `List<${m.infraDtoName}>`
36
+ : m.infraDtoName; %>
37
+ <% const httpVerbLower = m.httpVerb.charAt(0) + m.httpVerb.slice(1).toLowerCase(); %>
38
+ <% const mappingAnnotation = `@${httpVerbLower.charAt(0).toUpperCase()}${httpVerbLower.slice(1)}Mapping("${m.httpPath}")`; %>
39
+ <% const paramList = []; %>
40
+ <% m.pathVariables.forEach(pv => { paramList.push(`@PathVariable("${pv}") String ${pv}`); }); %>
41
+ <% if (m.hasBody) { paramList.push(`@RequestBody ${m.requestDtoName} body`); } %>
42
+ <%- mappingAnnotation %>
43
+ <%- returnType %> <%= m.name %>(<%- paramList.join(', ') %>);
44
+ <% }); %>
45
+ }
@@ -0,0 +1,24 @@
1
+ package <%= packageName %>.<%= moduleName %>.infrastructure.adapters.<%= adapterPackage %>;
2
+
3
+ import feign.Logger;
4
+ import feign.Request;
5
+ import org.springframework.context.annotation.Bean;
6
+
7
+ import java.util.concurrent.TimeUnit;
8
+
9
+ public class <%= feignConfigClassName %> {
10
+
11
+ @Bean
12
+ public Logger.Level feignLoggerLevel() {
13
+ return Logger.Level.BASIC;
14
+ }
15
+
16
+ @Bean
17
+ public Request.Options feignOptions() {
18
+ return new Request.Options(
19
+ 15, TimeUnit.SECONDS, // connect timeout
20
+ 15, TimeUnit.SECONDS, // read timeout
21
+ true
22
+ );
23
+ }
24
+ }
@@ -0,0 +1,45 @@
1
+ package <%= packageName %>.<%= moduleName %>.domain.repositories;
2
+ <% /* ── Compute all imports needed ── */ %>
3
+ <% const allMethods = methods || []; %>
4
+ <% const hasAnyList = allMethods.some(m => m.returnList); %>
5
+ <% const bodyFields = allMethods.flatMap(m => m.bodyFields || []); %>
6
+ <% const needsBigDecimal = bodyFields.some(f => f.javaType === 'BigDecimal'); %>
7
+ <% const needsLocalDate = bodyFields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
8
+ <% const needsInstant = bodyFields.some(f => f.javaType === 'Instant'); %>
9
+ <% const needsUUID = bodyFields.some(f => f.javaType === 'UUID'); %>
10
+ <% const needsListField = bodyFields.some(f => f.javaType && f.javaType.startsWith('List')); %>
11
+ <% if (hasAnyList || needsListField) { %>import java.util.List;
12
+ <% } %><% if (needsBigDecimal) { %>import java.math.BigDecimal;
13
+ <% } %><% if (needsLocalDate) { %>import java.time.LocalDate;
14
+ import java.time.LocalDateTime;
15
+ import java.time.LocalTime;
16
+ <% } %><% if (needsInstant) { %>import java.time.Instant;
17
+ <% } %><% if (needsUUID) { %>import java.util.UUID;
18
+ <% } %>
19
+ <% /* ACL: port interface returns domain models, not infra DTOs */ %>
20
+ <% const uniqueDomainTypes = [...new Set(allMethods.filter(m => m.domainType).map(m => m.domainType))]; %>
21
+ <% uniqueDomainTypes.forEach(dt => { %>import <%= packageName %>.<%= moduleName %>.domain.models.<%= adapterPackage %>.<%= dt %>;
22
+ <% }); %>
23
+ <% /* Import request DTOs */ %>
24
+ <% allMethods.filter(m => m.hasBody).forEach(m => { %>import <%= packageName %>.<%= moduleName %>.application.dtos.<%= m.requestDtoName %>;
25
+ <% }); %>
26
+ <% /* Import nested types */ %>
27
+ <% (nestedTypes || []).forEach(nt => { %>import <%= packageName %>.<%= moduleName %>.application.dtos.<%= nt.name %>;
28
+ <% }); %>
29
+ /**
30
+ * Secondary port — HTTP client to <%= target || serviceName %>.
31
+ * The domain depends on this interface; the infrastructure provides the Feign implementation.
32
+ * All return types are domain models (ACL pattern).
33
+ */
34
+ public interface <%= serviceName %> {
35
+ <% allMethods.forEach(m => { %>
36
+ <% /* Domain models are the return type — void when no response */ %>
37
+ <% const returnType = !m.hasResponse ? 'void'
38
+ : m.returnList ? `List<${m.domainType}>`
39
+ : m.domainType; %>
40
+ <% const params = []; %>
41
+ <% m.pathVariables.forEach(pv => { params.push(`String ${pv}`); }); %>
42
+ <% if (m.hasBody) { params.push(`${m.requestDtoName} body`); } %>
43
+ <%- returnType %> <%= m.name %>(<%= params.join(', ') %>);
44
+ <% }); %>
45
+ }
@@ -0,0 +1,28 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.dtos;
2
+ <% const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal'); %>
3
+ <% const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType)); %>
4
+ <% const needsInstant = fields && fields.some(f => f.javaType === 'Instant'); %>
5
+ <% const needsUUID = fields && fields.some(f => f.javaType === 'UUID'); %>
6
+ <% const needsList = fields && fields.some(f => f.javaType && f.javaType.startsWith('List')); %>
7
+ <% if (needsBigDecimal) { %>
8
+ import java.math.BigDecimal;
9
+ <% } %><% if (needsLocalDate) { %>
10
+ import java.time.LocalDate;
11
+ import java.time.LocalDateTime;
12
+ import java.time.LocalTime;
13
+ <% } %><% if (needsInstant) { %>
14
+ import java.time.Instant;
15
+ <% } %><% if (needsUUID) { %>
16
+ import java.util.UUID;
17
+ <% } %><% if (needsList) { %>
18
+ import java.util.List;
19
+ <% } %>
20
+ public record <%= name %>(
21
+ <% if (fields && fields.length > 0) { %>
22
+ <% fields.forEach((field, idx) => { %>
23
+ <%- field.javaType %> <%= field.name %><%= idx < fields.length - 1 ? ',' : '' %>
24
+ <% }); %>
25
+ <% } else { %>
26
+ // TODO: Add fields
27
+ <% } %>
28
+ ) {}