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
@@ -0,0 +1,97 @@
1
+ # system.yaml
2
+ # ─────────────────────────────────────────────────────────────────────────────
3
+ # Archivo de arquitectura del sistema — System-First Development con eva4j
4
+ #
5
+ # Describe qué módulos existen, qué exponen y cómo se comunican entre sí.
6
+ # Los campos de dominio (aggregates, events, ports) se declaran en domain.yaml.
7
+ #
8
+ # Comandos disponibles:
9
+ # eva system validate → valida coherencia del grafo (referencias, ciclos)
10
+ # eva generate system → genera módulos + domain.yaml con endpoints: pre-generado
11
+ # eva system diagram → genera diagrama Mermaid del sistema
12
+ # ─────────────────────────────────────────────────────────────────────────────
13
+
14
+ system:
15
+ name: <%= projectName %>
16
+ groupId: <%= groupId %>
17
+ javaVersion: <%= javaVersion %>
18
+ springBootVersion: <%= springBootVersion %>
19
+ database: <%= databaseType %>
20
+
21
+ # messaging: # Descomentar si el sistema usa mensajería asíncrona
22
+ # enabled: true
23
+ # broker: kafka # kafka | rabbitmq | sns-sqs
24
+ # kafka:
25
+ # bootstrapServers: localhost:9092
26
+ # defaultGroupId: <%= projectName %>
27
+ # topicPrefix: <%= projectName %> # prefija todos los topics: <%= projectName %>.ORDER_PLACED
28
+
29
+ modules:
30
+ # Declara cada módulo del sistema (nombre en plural, kebab-case).
31
+ # eva generate system genera por cada módulo: carpeta + domain.yaml con endpoints: pre-generado.
32
+ #
33
+ # - name: orders
34
+ # description: "Gestión del ciclo de vida de pedidos"
35
+ # exposes:
36
+ # - method: GET
37
+ # path: /orders/{id}
38
+ # useCase: GetOrder # PascalCase — alimenta endpoints: en domain.yaml
39
+ # description: "Obtener pedido por ID"
40
+ # - method: GET
41
+ # path: /orders
42
+ # useCase: FindAllOrders
43
+ # description: "Listar pedidos con filtros y paginación"
44
+ # - method: POST
45
+ # path: /orders
46
+ # useCase: CreateOrder
47
+ # description: "Crear nuevo pedido"
48
+ # - method: PUT
49
+ # path: /orders/{id}/confirm
50
+ # useCase: ConfirmOrder
51
+ # description: "Confirmar pedido pendiente"
52
+ # - method: PUT
53
+ # path: /orders/{id}/cancel
54
+ # useCase: CancelOrder
55
+ # description: "Cancelar pedido (PENDING o CONFIRMED)"
56
+ #
57
+ # - name: customers
58
+ # description: "Registro y gestión de clientes"
59
+ # exposes:
60
+ # - method: GET
61
+ # path: /customers/{id}
62
+ # useCase: GetCustomer
63
+ # description: "Obtener cliente por ID"
64
+ # - method: POST
65
+ # path: /customers
66
+ # useCase: CreateCustomer
67
+ # description: "Registrar nuevo cliente"
68
+ #
69
+ # - name: notifications
70
+ # description: "Envío de notificaciones"
71
+ # # Sin endpoints REST — solo consume eventos
72
+
73
+ # integrations:
74
+ # async:
75
+ # # Eventos asíncronos entre módulos.
76
+ # # Los campos del evento se declaran en domain.yaml → events[].fields del productor.
77
+ # - event: OrderPlacedEvent
78
+ # producer: orders
79
+ # topic: ORDER_PLACED
80
+ # consumers:
81
+ # - module: payments
82
+ # - module: notifications
83
+ #
84
+ # - event: PaymentProcessedEvent
85
+ # producer: payments
86
+ # topic: PAYMENT_PROCESSED
87
+ # consumers:
88
+ # - module: orders
89
+ #
90
+ # sync:
91
+ # # Llamadas HTTP síncronas entre módulos.
92
+ # # El shape del DTO de respuesta se declara en domain.yaml → ports[] del caller.
93
+ # - caller: orders
94
+ # calls: customers
95
+ # port: CustomerService
96
+ # using:
97
+ # - GET /customers/{id}
@@ -1,6 +1,8 @@
1
1
  package <%= packageName %>.<%= moduleName %>.application.mappers;
2
2
 
3
+ <% if (hasCreateOperation) { %>
3
4
  import <%= packageName %>.<%= moduleName %>.application.commands.Create<%= aggregateName %>Command;
5
+ <% } %>
4
6
  import <%= packageName %>.<%= moduleName %>.application.dtos.<%= aggregateName %>ResponseDto;
5
7
  <% secondaryEntities.forEach(entity => { %>
6
8
  import <%= packageName %>.<%= moduleName %>.application.dtos.Create<%= entity.name %>Dto;
@@ -31,6 +33,7 @@ public class <%= aggregateName %>ApplicationMapper {
31
33
 
32
34
  // ========== Command to Domain Mapping ==========
33
35
 
36
+ <% if (hasCreateOperation) { %>
34
37
  /**
35
38
  * Convert Create<%= aggregateName %>Command to <%= aggregateName %> domain entity
36
39
  */
@@ -98,6 +101,7 @@ public class <%= aggregateName %>ApplicationMapper {
98
101
  <% } %>
99
102
  return entity;
100
103
  }
104
+ <% } %>
101
105
  <% if (oneToManyRelationships && oneToManyRelationships.length > 0) { %>
102
106
  <% oneToManyRelationships.forEach(rel => { %>
103
107
 
@@ -4,7 +4,7 @@ import <%= packageName %>.<%= moduleName %>.application.commands.Create<%= aggre
4
4
  import <%= packageName %>.<%= moduleName %>.application.commands.Delete<%= aggregateName %>Command;
5
5
  import <%= packageName %>.<%= moduleName %>.application.commands.Update<%= aggregateName %>Command;
6
6
  import <%= packageName %>.<%= moduleName %>.application.queries.Get<%= aggregateName %>Query;
7
- import <%= packageName %>.<%= moduleName %>.application.queries.FindAll<%= aggregateName %>sQuery;
7
+ import <%= packageName %>.<%= moduleName %>.application.queries.FindAll<%= aggregateNamePlural %>Query;
8
8
  import <%= packageName %>.<%= moduleName %>.application.dtos.<%= aggregateName %>ResponseDto;
9
9
  import <%= packageName %>.shared.application.dtos.PagedResponse;
10
10
  import <%= packageName %>.shared.infrastructure.configurations.useCaseConfig.UseCaseMediator;
@@ -49,15 +49,15 @@ public class <%= aggregateName %>Controller {
49
49
 
50
50
  @GetMapping
51
51
  @ResponseStatus(HttpStatus.OK)
52
- @Operation(summary = "Get all <%= aggregateName %>s (paginated)")
52
+ @Operation(summary = "Get all <%= aggregateNamePlural %> (paginated)")
53
53
  public PagedResponse<<%= aggregateName %>ResponseDto> findAll(
54
54
  @RequestParam(defaultValue = "0") int page,
55
55
  @RequestParam(defaultValue = "20") int size,
56
56
  @RequestParam(defaultValue = "id") String sortBy,
57
57
  @RequestParam(defaultValue = "ASC") String sortDirection) {
58
- log.info("Finding all <%= aggregateName %>s — page={}, size={}, sortBy={}, sortDirection={}",
58
+ log.info("Finding all <%= aggregateNamePlural %> — page={}, size={}, sortBy={}, sortDirection={}",
59
59
  page, size, sortBy, sortDirection);
60
- return useCaseMediator.dispatch(new FindAll<%= aggregateName %>sQuery(page, size, sortBy, sortDirection));
60
+ return useCaseMediator.dispatch(new FindAll<%= aggregateNamePlural %>Query(page, size, sortBy, sortDirection));
61
61
  }
62
62
 
63
63
  @DeleteMapping("/{id}")
@@ -1,6 +1,7 @@
1
1
  package <%= packageName %>.<%= moduleName %>.application.commands;
2
2
 
3
3
  import <%= packageName %>.shared.domain.interfaces.Command;
4
+ import io.swagger.v3.oas.annotations.media.Schema;
4
5
  <% if (hasValueObjects) { %>
5
6
  import <%= packageName %>.<%= moduleName %>.domain.models.valueObjects.*;
6
7
  <% } %>
@@ -29,6 +30,9 @@ public record Create<%= aggregateName %>Command(
29
30
  <% (field.validationAnnotations || []).forEach(annotation => { %>
30
31
  <%- annotation %>
31
32
  <% }); %>
33
+ <% if (field.schemaExample) { %>
34
+ @Schema(example = "<%= field.schemaExample %>")
35
+ <% } %>
32
36
  <%- field.javaType %> <%= field.name %><% if (idx < commandFields.length - 1 || (oneToManyRelationships && oneToManyRelationships.length > 0) || (oneToOneRelationships && oneToOneRelationships.length > 0)) { %>,<% } %>
33
37
  <% }); %>
34
38
  <% if (oneToManyRelationships && oneToManyRelationships.length > 0) { %>
@@ -9,6 +9,7 @@ import <%= packageName %>.<%= moduleName %>.domain.models.enums.*;
9
9
  <% if (hasNestedRelationships || fields.some(f => f.isCollection)) { %>
10
10
  import java.util.List;
11
11
  <% } %>
12
+ import io.swagger.v3.oas.annotations.media.Schema;
12
13
  <% imports.forEach(imp => { %>
13
14
  <%- imp %>
14
15
  <% }); %>
@@ -18,6 +19,9 @@ public record Create<%= entityName %>Dto(
18
19
  <% (field.validationAnnotations || []).forEach(annotation => { %>
19
20
  <%- annotation %>
20
21
  <% }); %>
22
+ <% if (field.schemaExample) { %>
23
+ @Schema(example = "<%= field.schemaExample %>")
24
+ <% } %>
21
25
  <%- field.javaType %> <%= field.name %><% if (idx < fields.length - 1 || hasNestedRelationships || (forwardOneToOneRels && forwardOneToOneRels.length > 0)) { %>,<% } %>
22
26
  <% }); %>
23
27
  <% if (hasNestedRelationships) { %>
@@ -3,6 +3,7 @@ package <%= packageName %>.<%= moduleName %>.application.dtos;
3
3
  <% if (hasEnums) { %>
4
4
  import <%= packageName %>.<%= moduleName %>.domain.models.enums.*;
5
5
  <% } %>
6
+ import io.swagger.v3.oas.annotations.media.Schema;
6
7
  <% imports.forEach(imp => { %>
7
8
  <%- imp %>
8
9
  <% }); %>
@@ -12,6 +13,9 @@ public record Create<%= voName %>Dto(
12
13
  <% (field.validationAnnotations || []).forEach(annotation => { %>
13
14
  <%- annotation %>
14
15
  <% }); %>
16
+ <% if (field.schemaExample) { %>
17
+ @Schema(example = "<%= field.schemaExample %>")
18
+ <% } %>
15
19
  <%- field.javaType %> <%= field.name %><% if (idx < fields.length - 1) { %>,<% } %>
16
20
  <% }); %>
17
21
  ) {
@@ -2,6 +2,9 @@ package <%= packageName %>.<%= moduleName %>.application.usecases;
2
2
 
3
3
  import <%= packageName %>.<%= moduleName %>.application.commands.Delete<%= aggregateName %>Command;
4
4
  import <%= packageName %>.<%= moduleName %>.domain.repositories.<%= aggregateName %>Repository;
5
+ <% if (hasSoftDelete) { %>
6
+ import <%= packageName %>.<%= moduleName %>.domain.models.entities.<%= aggregateName %>;
7
+ <% } %>
5
8
  import <%= packageName %>.shared.domain.annotations.ApplicationComponent;
6
9
  import <%= packageName %>.shared.domain.customExceptions.NotFoundException;
7
10
  import <%= packageName %>.shared.domain.interfaces.CommandHandler;
@@ -26,12 +29,17 @@ public class Delete<%= aggregateName %>CommandHandler implements CommandHandler<
26
29
  @Transactional
27
30
  public void handle(Delete<%= aggregateName %>Command command) {
28
31
  log.info("Handling Delete<%= aggregateName %>Command for id: {}", command.id());
29
-
32
+ <% if (hasSoftDelete) { %>
33
+ <%= aggregateName %> entity = repository.findById(command.id())
34
+ .orElseThrow(() -> new NotFoundException("<%= aggregateName %> not found with id: " + command.id()));
35
+ entity.softDelete();
36
+ repository.save(entity);
37
+ <% } else { %>
30
38
  if (!repository.existsById(command.id())) {
31
39
  throw new NotFoundException("<%= aggregateName %> not found with id: " + command.id());
32
40
  }
33
-
34
41
  repository.deleteById(command.id());
42
+ <% } %>
35
43
  log.info("<%= aggregateName %> deleted successfully with id: {}", command.id());
36
44
  }
37
45
  }
@@ -0,0 +1,178 @@
1
+ <%
2
+ // ── Classify operations for selective imports ────────────────────────────
3
+ const uniqueOpsMap = new Map();
4
+ operations.forEach(op => { if (!uniqueOpsMap.has(op.useCase)) uniqueOpsMap.set(op.useCase, op); });
5
+ const uniqueOps = Array.from(uniqueOpsMap.values());
6
+
7
+ const hasCreate = uniqueOps.some(op => op.isStandard && op.standardType === 'create');
8
+ const hasUpdate = uniqueOps.some(op => op.isStandard && op.standardType === 'update');
9
+ const hasDelete = uniqueOps.some(op => op.isStandard && op.standardType === 'delete');
10
+ const hasGetById = uniqueOps.some(op => op.isStandard && op.standardType === 'getById');
11
+ const hasFindAll = uniqueOps.some(op => op.isStandard && op.standardType === 'findAll');
12
+ const hasTransition = uniqueOps.some(op => op.classifiedType === 'transition');
13
+ const hasSubAdd = uniqueOps.some(op => op.classifiedType === 'subEntityAdd');
14
+ const hasSubRemove = uniqueOps.some(op => op.classifiedType === 'subEntityRemove');
15
+ const hasFindBy = uniqueOps.some(op => op.classifiedType === 'findBy');
16
+
17
+ const transitionOps = uniqueOps.filter(op => op.classifiedType === 'transition');
18
+ const subAddOps = uniqueOps.filter(op => op.classifiedType === 'subEntityAdd');
19
+ const subRemoveOps = uniqueOps.filter(op => op.classifiedType === 'subEntityRemove');
20
+ const findByOps = uniqueOps.filter(op => op.classifiedType === 'findBy');
21
+ const customCmdUCs = uniqueOps.filter(op => op.classifiedType === 'scaffold' && op.type !== 'query');
22
+ const customQueryUCs = uniqueOps.filter(op => op.classifiedType === 'scaffold' && op.type === 'query');
23
+ -%>
24
+ package <%= packageName %>.<%= moduleName %>.infrastructure.rest.controllers.<%= resourceNameCamel %>.<%= apiVersion %>;
25
+
26
+ <% if (hasCreate) { -%>
27
+ import <%= packageName %>.<%= moduleName %>.application.commands.Create<%= aggregateName %>Command;
28
+ <% } -%>
29
+ <% if (hasUpdate) { -%>
30
+ import <%= packageName %>.<%= moduleName %>.application.commands.Update<%= aggregateName %>Command;
31
+ <% } -%>
32
+ <% if (hasDelete) { -%>
33
+ import <%= packageName %>.<%= moduleName %>.application.commands.Delete<%= aggregateName %>Command;
34
+ <% } -%>
35
+ <% if (hasGetById) { -%>
36
+ import <%= packageName %>.<%= moduleName %>.application.queries.Get<%= aggregateName %>Query;
37
+ <% } -%>
38
+ <% if (hasFindAll) { -%>
39
+ import <%= packageName %>.<%= moduleName %>.application.queries.FindAll<%= aggregateNamePlural %>Query;
40
+ <% } -%>
41
+ <% customCmdUCs.forEach(function(op) { -%>
42
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= op.useCase %>Command;
43
+ <% }); -%>
44
+ <% customQueryUCs.forEach(function(op) { -%>
45
+ import <%= packageName %>.<%= moduleName %>.application.queries.<%= op.useCase %>Query;
46
+ <% }); -%>
47
+ <% transitionOps.forEach(function(op) { -%>
48
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= op.useCase %>Command;
49
+ <% }); -%>
50
+ <% subAddOps.forEach(function(op) { -%>
51
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= op.useCase %>Command;
52
+ <% }); -%>
53
+ <% subRemoveOps.forEach(function(op) { -%>
54
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= op.useCase %>Command;
55
+ <% }); -%>
56
+ <% findByOps.forEach(function(op) { -%>
57
+ import <%= packageName %>.<%= moduleName %>.application.queries.<%= op.useCase %>Query;
58
+ <% }); -%>
59
+ <% if (hasGetById || hasFindAll || hasFindBy || customQueryUCs.length > 0) { -%>
60
+ import <%= packageName %>.<%= moduleName %>.application.dtos.<%= aggregateName %>ResponseDto;
61
+ <% } -%>
62
+ <% if (hasFindAll || hasFindBy) { -%>
63
+ import <%= packageName %>.shared.application.dtos.PagedResponse;
64
+ <% } -%>
65
+ import <%= packageName %>.shared.infrastructure.configurations.useCaseConfig.UseCaseMediator;
66
+
67
+ import io.swagger.v3.oas.annotations.Operation;
68
+ import io.swagger.v3.oas.annotations.tags.Tag;
69
+ import jakarta.validation.Valid;
70
+ import lombok.extern.slf4j.Slf4j;
71
+ import org.springframework.http.HttpStatus;
72
+ import org.springframework.web.bind.annotation.*;
73
+
74
+ @RestController
75
+ @RequestMapping("/api/<%= apiVersion %><%= basePath %>")
76
+ @Slf4j
77
+ @Tag(name = "<%= aggregateName %>", description = "<%= aggregateName %> Management API")
78
+ public class <%= controllerName %> {
79
+
80
+ private final UseCaseMediator useCaseMediator;
81
+
82
+ public <%= controllerName %>(UseCaseMediator useCaseMediator) {
83
+ this.useCaseMediator = useCaseMediator;
84
+ }
85
+ <% operations.forEach(function(op) { %>
86
+ /**
87
+ * <%= op.description || op.useCase %>
88
+ */
89
+ @<%- op.httpAnnotation %>(<% if (op.path && op.path !== '/') { %>"<%= op.path %>"<% } %>)
90
+ @ResponseStatus(<%= op.httpStatus %>)
91
+ @Operation(summary = "<%= op.description || op.useCase %>")
92
+ <% if (op.isStandard && op.standardType === 'create') { %>
93
+ public void <%= op.methodName %>(@Valid @RequestBody Create<%= aggregateName %>Command command) {
94
+ log.info("Creating <%= aggregateName %>: {}", command);
95
+ useCaseMediator.dispatch(command);
96
+ }
97
+ <% } else if (op.isStandard && op.standardType === 'getById') { %>
98
+ public <%= aggregateName %>ResponseDto <%= op.methodName %>(@PathVariable <%- idType %> id) {
99
+ log.info("Finding <%= aggregateName %> by id: {}", id);
100
+ return useCaseMediator.dispatch(new Get<%= aggregateName %>Query(id));
101
+ }
102
+ <% } else if (op.isStandard && op.standardType === 'findAll') { %>
103
+ public PagedResponse<<%= aggregateName %>ResponseDto> <%= op.methodName %>(
104
+ @RequestParam(defaultValue = "0") int page,
105
+ @RequestParam(defaultValue = "20") int size,
106
+ @RequestParam(defaultValue = "id") String sortBy,
107
+ @RequestParam(defaultValue = "ASC") String sortDirection) {
108
+ log.info("Finding all <%= aggregateNamePlural %> — page={}, size={}, sortBy={}, sortDirection={}",
109
+ page, size, sortBy, sortDirection);
110
+ return useCaseMediator.dispatch(new FindAll<%= aggregateNamePlural %>Query(page, size, sortBy, sortDirection));
111
+ }
112
+ <% } else if (op.isStandard && op.standardType === 'delete') { %>
113
+ public void <%= op.methodName %>(@PathVariable <%- idType %> id) {
114
+ log.info("Deleting <%= aggregateName %> id: {}", id);
115
+ useCaseMediator.dispatch(new Delete<%= aggregateName %>Command(id));
116
+ }
117
+ <% } else if (op.isStandard && op.standardType === 'update') { %>
118
+ public void <%= op.methodName %>(
119
+ @PathVariable <%- idType %> id,
120
+ @RequestBody Update<%= aggregateName %>Command command) {
121
+ log.info("Updating <%= aggregateName %> id: {}", id);
122
+ useCaseMediator.dispatch(new Update<%= aggregateName %>Command(
123
+ id<% if (commandFields && commandFields.length > 0) { %>,<% } %>
124
+ <% (commandFields || []).forEach(function(field, idx) { %>
125
+ command.<%= field.name %>()<% if (idx < commandFields.length - 1 || (oneToManyRelationships && oneToManyRelationships.length > 0) || (oneToOneRelationships && oneToOneRelationships.length > 0)) { %>,<% } %>
126
+ <% }); %>
127
+ <% if (oneToManyRelationships && oneToManyRelationships.length > 0) { %>
128
+ <% oneToManyRelationships.forEach(function(rel, idx) { %>
129
+ command.<%= rel.fieldName %>()<% if (idx < oneToManyRelationships.length - 1 || (oneToOneRelationships && oneToOneRelationships.length > 0)) { %>,<% } %>
130
+ <% }); %>
131
+ <% } %>
132
+ <% if (oneToOneRelationships && oneToOneRelationships.length > 0) { %>
133
+ <% oneToOneRelationships.forEach(function(rel, idx) { %>
134
+ command.<%= rel.fieldName %>()<% if (idx < oneToOneRelationships.length - 1) { %>,<% } %>
135
+ <% }); %>
136
+ <% } %>
137
+ ));
138
+ }
139
+ <% } else if (op.classifiedType === 'transition') { %>
140
+ public void <%= op.methodName %>(@PathVariable <%- op.idType %> id) {
141
+ log.info("Handling <%= op.useCase %> for <%= aggregateName %> id: {}", id);
142
+ useCaseMediator.dispatch(new <%= op.useCase %>Command(id));
143
+ }
144
+ <% } else if (op.classifiedType === 'subEntityAdd') { %>
145
+ public void <%= op.methodName %>(@PathVariable <%- op.idType %> id, @Valid @RequestBody <%= op.useCase %>Command command) {
146
+ log.info("Handling <%= op.useCase %> for <%= aggregateName %> id: {}", id);
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
+ }
149
+ <% } else if (op.classifiedType === 'subEntityRemove') { %>
150
+ public void <%= op.methodName %>(@PathVariable <%- op.idType %> id, @PathVariable String itemId) {
151
+ log.info("Handling <%= op.useCase %> for <%= aggregateName %> id: {}, itemId: {}", id, itemId);
152
+ useCaseMediator.dispatch(new <%= op.useCase %>Command(id, itemId));
153
+ }
154
+ <% } else if (op.classifiedType === 'findBy') { %>
155
+ public PagedResponse<<%= aggregateName %>ResponseDto> <%= op.methodName %>(
156
+ @RequestParam <%- op.classification.fieldJavaType %> <%= op.classification.fieldName %>,
157
+ @RequestParam(defaultValue = "0") int page,
158
+ @RequestParam(defaultValue = "20") int size,
159
+ @RequestParam(defaultValue = "id") String sortBy,
160
+ @RequestParam(defaultValue = "ASC") String sortDirection) {
161
+ log.info("Handling <%= op.useCase %> — <%= op.classification.fieldName %>={}, page={}, size={}", <%= op.classification.fieldName %>, page, size);
162
+ return useCaseMediator.dispatch(new <%= op.useCase %>Query(<%= op.classification.fieldName %>, page, size, sortBy, sortDirection));
163
+ }
164
+ <% } else if (op.type === 'command') { %>
165
+ // TODO: Review the fields declared in <%= op.useCase %>Command and adapt the method signature if needed
166
+ public void <%= op.methodName %>(<% if (op.hasPathVar) { %>@PathVariable <%- op.idType %> <%= op.pathVarName %>, <% } %>@Valid @RequestBody <%= op.useCase %>Command command) {
167
+ log.info("Handling <%= op.useCase %>: {}", command);
168
+ useCaseMediator.dispatch(command);
169
+ }
170
+ <% } else { %>
171
+ // TODO: Review the parameters in <%= op.useCase %>Query and adapt the return type if needed
172
+ public <%- op.returnType %> <%= op.methodName %>(<% if (op.hasPathVar) { %>@PathVariable <%- op.idType %> <%= op.pathVarName %><% } %>) {
173
+ log.info("Handling <%= op.useCase %>");
174
+ return useCaseMediator.dispatch(new <%= op.useCase %>Query(<% if (op.hasPathVar) { %><%= op.pathVarName %><% } %>));
175
+ }
176
+ <% } -%>
177
+ <% }); %>
178
+ }
@@ -0,0 +1,17 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.queries;
2
+
3
+ import <%= packageName %>.shared.domain.interfaces.Query;
4
+ import <%= packageName %>.shared.application.dtos.PagedResponse;
5
+ import <%= packageName %>.<%= moduleName %>.application.dtos.<%= aggregateName %>ResponseDto;
6
+
7
+ /**
8
+ * <%= useCaseName %>Query
9
+ * Returns a paginated list of <%= aggregateNamePlural %> filtered by <%= fieldName %>.
10
+ */
11
+ public record <%= useCaseName %>Query(
12
+ <%- fieldJavaType %> <%= fieldName %>,
13
+ int page,
14
+ int size,
15
+ String sortBy,
16
+ String sortDirection
17
+ ) implements Query<PagedResponse<<%= aggregateName %>ResponseDto>> {}
@@ -0,0 +1,57 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.usecases;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.queries.<%= useCaseName %>Query;
4
+ import <%= packageName %>.<%= moduleName %>.application.dtos.<%= aggregateName %>ResponseDto;
5
+ import <%= packageName %>.<%= moduleName %>.application.mappers.<%= aggregateName %>ApplicationMapper;
6
+ import <%= packageName %>.<%= moduleName %>.domain.models.entities.<%= aggregateName %>;
7
+ import <%= packageName %>.<%= moduleName %>.domain.repositories.<%= aggregateName %>Repository;
8
+ import <%= packageName %>.shared.application.dtos.PagedResponse;
9
+ import <%= packageName %>.shared.domain.annotations.ApplicationComponent;
10
+ import <%= packageName %>.shared.domain.interfaces.QueryHandler;
11
+ import lombok.extern.slf4j.Slf4j;
12
+ import org.springframework.data.domain.Page;
13
+ import org.springframework.data.domain.PageRequest;
14
+ import org.springframework.data.domain.Pageable;
15
+ import org.springframework.data.domain.Sort;
16
+ import org.springframework.transaction.annotation.Transactional;
17
+
18
+ import java.util.List;
19
+
20
+ /**
21
+ * <%= useCaseName %>QueryHandler
22
+ * Returns paginated <%= aggregateNamePlural %> filtered by <%= fieldName %>.
23
+ * Calls repository.<%= jpaMethodName %>(<%= fieldName %>, pageable).
24
+ */
25
+ @Slf4j
26
+ @ApplicationComponent
27
+ public class <%= useCaseName %>QueryHandler implements QueryHandler<<%= useCaseName %>Query, PagedResponse<<%= aggregateName %>ResponseDto>> {
28
+
29
+ private final <%= aggregateName %>Repository repository;
30
+ private final <%= aggregateName %>ApplicationMapper mapper;
31
+
32
+ public <%= useCaseName %>QueryHandler(<%= aggregateName %>Repository repository,
33
+ <%= aggregateName %>ApplicationMapper mapper) {
34
+ this.repository = repository;
35
+ this.mapper = mapper;
36
+ }
37
+
38
+ @Override
39
+ @Transactional(readOnly = true)
40
+ public PagedResponse<<%= aggregateName %>ResponseDto> handle(<%= useCaseName %>Query query) {
41
+ log.info("Handling <%= useCaseName %>Query — <%= fieldName %>={}, page={}, size={}",
42
+ query.<%= fieldName %>(), query.page(), query.size());
43
+
44
+ Sort sort = Sort.by(Sort.Direction.fromString(query.sortDirection()), query.sortBy());
45
+ Pageable pageable = PageRequest.of(query.page(), query.size(), sort);
46
+
47
+ Page<<%= aggregateName %>> page = repository.<%= jpaMethodName %>(query.<%= fieldName %>(), pageable);
48
+ List<<%= aggregateName %>ResponseDto> content = page.getContent().stream()
49
+ .map(mapper::toDto)
50
+ .toList();
51
+
52
+ log.info("<%= useCaseName %> retrieved — page={}/{}, totalElements={}",
53
+ page.getNumber() + 1, page.getTotalPages(), page.getTotalElements());
54
+
55
+ return PagedResponse.of(content, page.getNumber(), page.getSize(), page.getTotalElements());
56
+ }
57
+ }
@@ -4,7 +4,7 @@ import <%= packageName %>.<%= moduleName %>.application.dtos.<%= aggregateName %
4
4
  import <%= packageName %>.shared.application.dtos.PagedResponse;
5
5
  import <%= packageName %>.shared.domain.interfaces.Query;
6
6
 
7
- public record FindAll<%= aggregateName %>sQuery(
7
+ public record FindAll<%= aggregateNamePlural %>Query(
8
8
  int page,
9
9
  int size,
10
10
  String sortBy,
@@ -1,6 +1,6 @@
1
1
  package <%= packageName %>.<%= moduleName %>.application.usecases;
2
2
 
3
- import <%= packageName %>.<%= moduleName %>.application.queries.FindAll<%= aggregateName %>sQuery;
3
+ import <%= packageName %>.<%= moduleName %>.application.queries.FindAll<%= aggregateNamePlural %>Query;
4
4
  import <%= packageName %>.<%= moduleName %>.application.dtos.<%= aggregateName %>ResponseDto;
5
5
  import <%= packageName %>.<%= moduleName %>.application.mappers.<%= aggregateName %>ApplicationMapper;
6
6
  import <%= packageName %>.<%= moduleName %>.domain.models.entities.<%= aggregateName %>;
@@ -18,17 +18,17 @@ import org.springframework.transaction.annotation.Transactional;
18
18
  import java.util.List;
19
19
 
20
20
  /**
21
- * FindAll<%= aggregateName %>sQueryHandler
22
- * Handles retrieval of all <%= aggregateName %>s (paginated)
21
+ * FindAll<%= aggregateNamePlural %>QueryHandler
22
+ * Handles retrieval of all <%= aggregateNamePlural %> (paginated)
23
23
  */
24
24
  @Slf4j
25
25
  @ApplicationComponent
26
- public class FindAll<%= aggregateName %>sQueryHandler implements QueryHandler<FindAll<%= aggregateName %>sQuery, PagedResponse<<%= aggregateName %>ResponseDto>> {
26
+ public class FindAll<%= aggregateNamePlural %>QueryHandler implements QueryHandler<FindAll<%= aggregateNamePlural %>Query, PagedResponse<<%= aggregateName %>ResponseDto>> {
27
27
 
28
28
  private final <%= aggregateName %>Repository repository;
29
29
  private final <%= aggregateName %>ApplicationMapper mapper;
30
30
 
31
- public FindAll<%= aggregateName %>sQueryHandler(<%= aggregateName %>Repository repository,
31
+ public FindAll<%= aggregateNamePlural %>QueryHandler(<%= aggregateName %>Repository repository,
32
32
  <%= aggregateName %>ApplicationMapper mapper) {
33
33
  this.repository = repository;
34
34
  this.mapper = mapper;
@@ -36,8 +36,8 @@ public class FindAll<%= aggregateName %>sQueryHandler implements QueryHandler<Fi
36
36
 
37
37
  @Override
38
38
  @Transactional(readOnly = true)
39
- public PagedResponse<<%= aggregateName %>ResponseDto> handle(FindAll<%= aggregateName %>sQuery query) {
40
- log.info("Handling FindAll<%= aggregateName %>sQuery — page={}, size={}, sortBy={}, sortDirection={}",
39
+ public PagedResponse<<%= aggregateName %>ResponseDto> handle(FindAll<%= aggregateNamePlural %>Query query) {
40
+ log.info("Handling FindAll<%= aggregateNamePlural %>Query — page={}, size={}, sortBy={}, sortDirection={}",
41
41
  query.page(), query.size(), query.sortBy(), query.sortDirection());
42
42
 
43
43
  Sort sort = Sort.by(Sort.Direction.fromString(query.sortDirection()), query.sortBy());
@@ -48,7 +48,7 @@ public class FindAll<%= aggregateName %>sQueryHandler implements QueryHandler<Fi
48
48
  .map(mapper::toDto)
49
49
  .toList();
50
50
 
51
- log.info("FindAll<%= aggregateName %>s retrieved — page={}/{}, totalElements={}",
51
+ log.info("FindAll<%= aggregateNamePlural %> retrieved — page={}/{}, totalElements={}",
52
52
  page.getNumber() + 1, page.getTotalPages(), page.getTotalElements());
53
53
 
54
54
  return PagedResponse.of(content, page.getNumber(), page.getSize(), page.getTotalElements());
@@ -0,0 +1,12 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.commands;
2
+
3
+ import <%= packageName %>.shared.domain.interfaces.Command;
4
+
5
+ /**
6
+ * <%= useCaseName %>Command
7
+ * TODO: Add the fields required by this use case and remove the placeholder.
8
+ */
9
+ public record <%= useCaseName %>Command(
10
+ // TODO: Replace with actual command fields
11
+ String id
12
+ ) implements Command {}
@@ -0,0 +1,43 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.usecases;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.commands.<%= useCaseName %>Command;
4
+ import <%= packageName %>.<%= moduleName %>.domain.repositories.<%= aggregateName %>Repository;
5
+ import <%= packageName %>.shared.domain.annotations.ApplicationComponent;
6
+ import <%= packageName %>.shared.domain.interfaces.CommandHandler;
7
+ import lombok.extern.slf4j.Slf4j;
8
+ import org.springframework.transaction.annotation.Transactional;
9
+
10
+ /**
11
+ * <%= useCaseName %>CommandHandler
12
+ * TODO: Implement the business logic for the use case "<%= useCaseName %>".
13
+ *
14
+ * Suggested steps:
15
+ * 1. Find the aggregate root by ID via repository
16
+ * 2. Call the appropriate domain method (e.g. entity.someBusinessMethod())
17
+ * 3. Persist the result via repository.save(entity)
18
+ */
19
+ @Slf4j
20
+ @ApplicationComponent
21
+ public class <%= useCaseName %>CommandHandler implements CommandHandler<<%= useCaseName %>Command> {
22
+
23
+ private final <%= aggregateName %>Repository repository;
24
+
25
+ public <%= useCaseName %>CommandHandler(<%= aggregateName %>Repository repository) {
26
+ this.repository = repository;
27
+ }
28
+
29
+ @Override
30
+ @Transactional
31
+ public void handle(<%= useCaseName %>Command command) {
32
+ log.info("Handling <%= useCaseName %>Command: {}", command);
33
+
34
+ // TODO: Implement business logic
35
+ // Example:
36
+ // <%= aggregateName %> entity = repository.findById(command.id())
37
+ // .orElseThrow(() -> new EntityNotFoundException("<%= aggregateName %> not found"));
38
+ // entity.someBusinessMethod();
39
+ // repository.save(entity);
40
+
41
+ throw new UnsupportedOperationException("<%= useCaseName %> not yet implemented");
42
+ }
43
+ }
@@ -0,0 +1,13 @@
1
+ package <%= packageName %>.<%= moduleName %>.application.queries;
2
+
3
+ import <%= packageName %>.<%= moduleName %>.application.dtos.<%= aggregateName %>ResponseDto;
4
+ import <%= packageName %>.shared.domain.interfaces.Query;
5
+
6
+ /**
7
+ * <%= useCaseName %>Query
8
+ * TODO: Add the parameters required by this query and adapt the return type if needed.
9
+ */
10
+ public record <%= useCaseName %>Query(
11
+ // TODO: Replace with actual query parameters
12
+ String id
13
+ ) implements Query<<%= aggregateName %>ResponseDto> {}