eva4j 1.0.11 → 1.0.13
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 +441 -14
- package/DOMAIN_YAML_GUIDE.md +425 -21
- package/FUTURE_FEATURES.md +315 -115
- package/QUICK_REFERENCE.md +101 -153
- package/README.md +77 -70
- package/bin/eva4j.js +57 -1
- package/config/defaults.json +3 -0
- package/docs/commands/GENERATE_ENTITIES.md +662 -1968
- package/docs/commands/GENERATE_HTTP_EXCHANGE.md +274 -450
- package/docs/commands/GENERATE_KAFKA_EVENT.md +219 -498
- package/docs/commands/GENERATE_KAFKA_LISTENER.md +18 -18
- package/docs/commands/GENERATE_RECORD.md +335 -311
- package/docs/commands/GENERATE_TEMPORAL_ACTIVITY.md +174 -0
- package/docs/commands/GENERATE_TEMPORAL_FLOW.md +237 -0
- package/docs/commands/GENERATE_USECASE.md +216 -282
- package/docs/commands/INDEX.md +36 -7
- package/examples/doctor-evaluation.yaml +3 -3
- package/examples/domain-audit-complete.yaml +2 -2
- package/examples/domain-collections.yaml +2 -2
- package/examples/domain-ecommerce.yaml +2 -2
- package/examples/domain-events.yaml +201 -0
- package/examples/domain-field-visibility.yaml +11 -5
- package/examples/domain-multi-aggregate.yaml +12 -6
- package/examples/domain-one-to-many.yaml +1 -1
- package/examples/domain-one-to-one.yaml +1 -1
- package/examples/domain-secondary-onetomany.yaml +1 -1
- package/examples/domain-secondary-onetoone.yaml +1 -1
- package/examples/domain-simple.yaml +1 -1
- package/examples/domain-soft-delete.yaml +3 -3
- package/examples/domain-transitions.yaml +1 -1
- package/examples/domain-value-objects.yaml +1 -1
- package/package.json +2 -2
- package/src/commands/add-kafka-client.js +3 -1
- package/src/commands/add-temporal-client.js +286 -0
- package/src/commands/generate-entities.js +75 -4
- package/src/commands/generate-kafka-event.js +273 -89
- package/src/commands/generate-temporal-activity.js +228 -0
- package/src/commands/generate-temporal-flow.js +216 -0
- package/src/generators/module-generator.js +1 -0
- package/src/generators/shared-generator.js +26 -0
- package/src/utils/yaml-to-entity.js +93 -4
- package/templates/aggregate/AggregateRepository.java.ejs +3 -2
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +15 -7
- package/templates/aggregate/AggregateRoot.java.ejs +38 -2
- package/templates/aggregate/DomainEntity.java.ejs +6 -2
- package/templates/aggregate/DomainEventHandler.java.ejs +62 -0
- package/templates/aggregate/DomainEventRecord.java.ejs +50 -0
- package/templates/aggregate/JpaAggregateRoot.java.ejs +3 -1
- package/templates/aggregate/JpaEntity.java.ejs +3 -1
- package/templates/base/docker/kafka-services.yaml.ejs +2 -2
- package/templates/base/docker/temporal-services.yaml.ejs +29 -0
- package/templates/base/resources/parameters/develop/temporal.yaml.ejs +9 -0
- package/templates/base/resources/parameters/local/temporal.yaml.ejs +9 -0
- package/templates/base/resources/parameters/production/temporal.yaml.ejs +9 -0
- package/templates/base/resources/parameters/test/temporal.yaml.ejs +9 -0
- package/templates/base/root/AGENTS.md.ejs +916 -51
- package/templates/crud/Controller.java.ejs +36 -6
- package/templates/crud/ListQuery.java.ejs +6 -2
- package/templates/crud/ListQueryHandler.java.ejs +24 -10
- package/templates/crud/UpdateCommand.java.ejs +52 -0
- package/templates/crud/UpdateCommandHandler.java.ejs +105 -0
- package/templates/kafka-event/DomainEventHandlerMethod.ejs +1 -0
- package/templates/kafka-event/Event.java.ejs +23 -0
- package/templates/shared/application/dtos/PagedResponse.java.ejs +30 -0
- package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +104 -0
- package/templates/shared/domain/DomainEvent.java.ejs +40 -0
- package/templates/shared/interfaces/HeavyActivity.java.ejs +4 -0
- package/templates/shared/interfaces/LightActivity.java.ejs +4 -0
- package/templates/temporal-activity/ActivityImpl.java.ejs +14 -0
- package/templates/temporal-activity/ActivityInterface.java.ejs +11 -0
- package/templates/temporal-flow/WorkFlowImpl.java.ejs +64 -0
- package/templates/temporal-flow/WorkFlowInterface.java.ejs +19 -0
- package/templates/temporal-flow/WorkFlowService.java.ejs +49 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
const ora = require('ora');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const yaml = require('js-yaml');
|
|
6
|
+
const ConfigManager = require('../utils/config-manager');
|
|
7
|
+
const { isEva4jProject } = require('../utils/validator');
|
|
8
|
+
const { toPackagePath } = require('../utils/naming');
|
|
9
|
+
const { renderAndWrite, renderTemplate } = require('../utils/template-engine');
|
|
10
|
+
const defaults = require('../../config/defaults.json');
|
|
11
|
+
|
|
12
|
+
const TEMPORAL_SDK_VERSION = defaults.temporalSdkVersion;
|
|
13
|
+
|
|
14
|
+
async function addTemporalClientCommand() {
|
|
15
|
+
const projectDir = process.cwd();
|
|
16
|
+
|
|
17
|
+
// Validate we're in an eva4j project
|
|
18
|
+
if (!(await isEva4jProject(projectDir))) {
|
|
19
|
+
console.error(chalk.red('❌ Not in an eva4j project directory'));
|
|
20
|
+
console.error(chalk.gray('Run this command inside a project created with eva4j'));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check if temporal is already installed
|
|
25
|
+
const configManager = new ConfigManager(projectDir);
|
|
26
|
+
if (await configManager.featureExists('temporal')) {
|
|
27
|
+
console.error(chalk.red('❌ Temporal client is already installed in this project'));
|
|
28
|
+
console.log(chalk.gray('\nTemporal dependencies and configuration already exist.'));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Load project configuration
|
|
33
|
+
const projectConfig = await configManager.loadProjectConfig();
|
|
34
|
+
if (!projectConfig) {
|
|
35
|
+
console.error(chalk.red('❌ Could not load project configuration'));
|
|
36
|
+
console.error(chalk.gray('Make sure .eva4j.json exists in the project root'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { packageName, projectName, groupId, artifactId } = projectConfig;
|
|
41
|
+
const packagePath = toPackagePath(packageName);
|
|
42
|
+
|
|
43
|
+
// Check if shared module exists
|
|
44
|
+
const sharedPath = path.join(projectDir, 'src', 'main', 'java', packagePath, 'shared');
|
|
45
|
+
if (!(await fs.pathExists(sharedPath))) {
|
|
46
|
+
console.error(chalk.red('❌ Shared module not found'));
|
|
47
|
+
console.error(chalk.gray('Create at least one module first using: eva4j add module <name>'));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const spinner = ora('Adding Temporal client support...').start();
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const context = {
|
|
55
|
+
packageName,
|
|
56
|
+
packagePath,
|
|
57
|
+
projectName,
|
|
58
|
+
groupId,
|
|
59
|
+
artifactId,
|
|
60
|
+
temporalDockerVersion: defaults.temporalDockerVersion
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// 1. Add dependency to build.gradle
|
|
64
|
+
spinner.text = 'Adding Temporal dependency to build.gradle...';
|
|
65
|
+
await addTemporalDependency(projectDir);
|
|
66
|
+
|
|
67
|
+
// 2. Generate temporal.yaml files for all environments
|
|
68
|
+
spinner.text = 'Generating Temporal configuration files...';
|
|
69
|
+
await generateTemporalConfigFiles(projectDir, context);
|
|
70
|
+
|
|
71
|
+
// 3. Add temporal.yaml imports to application-*.yaml files
|
|
72
|
+
spinner.text = 'Updating application configuration files...';
|
|
73
|
+
await addTemporalImports(projectDir);
|
|
74
|
+
|
|
75
|
+
// 4. Generate activity marker interfaces
|
|
76
|
+
spinner.text = 'Generating activity interfaces...';
|
|
77
|
+
await generateActivityInterfaces(projectDir, context);
|
|
78
|
+
|
|
79
|
+
// 5. Generate TemporalConfig.java
|
|
80
|
+
spinner.text = 'Generating TemporalConfig class...';
|
|
81
|
+
await generateTemporalConfigClass(projectDir, context);
|
|
82
|
+
|
|
83
|
+
// 6. Update docker-compose.yaml if it exists
|
|
84
|
+
spinner.text = 'Updating docker-compose.yaml...';
|
|
85
|
+
await updateDockerCompose(projectDir, context);
|
|
86
|
+
|
|
87
|
+
// 7. Save feature to configuration
|
|
88
|
+
await configManager.addFeature('temporal');
|
|
89
|
+
|
|
90
|
+
spinner.succeed(chalk.green('Temporal client support added successfully! ✨'));
|
|
91
|
+
|
|
92
|
+
console.log(chalk.blue('\n📦 Added components:'));
|
|
93
|
+
console.log(chalk.gray(' ├── build.gradle (io.temporal:temporal-sdk:' + TEMPORAL_SDK_VERSION + ')'));
|
|
94
|
+
console.log(chalk.gray(' ├── docker-compose.yaml (Temporal cluster)'));
|
|
95
|
+
console.log(chalk.gray(' ├── src/main/resources/parameters/'));
|
|
96
|
+
console.log(chalk.gray(' │ ├── local/temporal.yaml'));
|
|
97
|
+
console.log(chalk.gray(' │ ├── develop/temporal.yaml'));
|
|
98
|
+
console.log(chalk.gray(' │ ├── test/temporal.yaml'));
|
|
99
|
+
console.log(chalk.gray(' │ └── production/temporal.yaml'));
|
|
100
|
+
console.log(chalk.gray(' ├── shared/domain/interfaces/HeavyActivity.java'));
|
|
101
|
+
console.log(chalk.gray(' ├── shared/domain/interfaces/LightActivity.java'));
|
|
102
|
+
console.log(chalk.gray(' └── shared/infrastructure/configurations/temporalConfig/TemporalConfig.java'));
|
|
103
|
+
|
|
104
|
+
console.log(chalk.blue('\n✅ Temporal client configured successfully!'));
|
|
105
|
+
console.log(chalk.white('\n Service URL: localhost:7233'));
|
|
106
|
+
console.log(chalk.white(' Namespace: default'));
|
|
107
|
+
console.log(chalk.white(' Temporal UI: http://localhost:8088'));
|
|
108
|
+
console.log(chalk.yellow('\n ⚠️ Register your workflow implementation types in TemporalConfig.java'));
|
|
109
|
+
console.log(chalk.gray(' Run "docker-compose up -d" to start the Temporal cluster'));
|
|
110
|
+
console.log(chalk.gray(' Update temporal.yaml files to customize service URLs per environment'));
|
|
111
|
+
console.log();
|
|
112
|
+
|
|
113
|
+
} catch (error) {
|
|
114
|
+
spinner.fail(chalk.red('Failed to add Temporal client support'));
|
|
115
|
+
console.error(chalk.red('\n❌ Error:'), error.message);
|
|
116
|
+
if (error.stack) {
|
|
117
|
+
console.error(chalk.gray(error.stack));
|
|
118
|
+
}
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Add Temporal SDK dependency to build.gradle
|
|
125
|
+
*/
|
|
126
|
+
async function addTemporalDependency(projectDir) {
|
|
127
|
+
const buildGradlePath = path.join(projectDir, 'build.gradle');
|
|
128
|
+
let buildGradleContent = await fs.readFile(buildGradlePath, 'utf-8');
|
|
129
|
+
|
|
130
|
+
// Check if dependency already exists
|
|
131
|
+
if (buildGradleContent.includes('temporal-sdk')) {
|
|
132
|
+
return; // Already added
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Find the dependencies block and add Temporal dependency after spring-modulith-starter-core
|
|
136
|
+
const dependenciesMatch = buildGradleContent.match(
|
|
137
|
+
/(dependencies\s*\{[^}]*)(implementation 'org\.springframework\.modulith:spring-modulith-starter-core'[^\n]*\n)/s
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (!dependenciesMatch) {
|
|
141
|
+
throw new Error('Could not find dependencies block in build.gradle');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const temporalDependency = `\n\t// Temporal\n\timplementation 'io.temporal:temporal-sdk:${TEMPORAL_SDK_VERSION}'\n\n\t`;
|
|
145
|
+
|
|
146
|
+
buildGradleContent = buildGradleContent.replace(
|
|
147
|
+
dependenciesMatch[0],
|
|
148
|
+
dependenciesMatch[1] + dependenciesMatch[2] + temporalDependency
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
await fs.writeFile(buildGradlePath, buildGradleContent, 'utf-8');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Generate temporal.yaml configuration files for all environments
|
|
156
|
+
*/
|
|
157
|
+
async function generateTemporalConfigFiles(projectDir, context) {
|
|
158
|
+
const templatePath = path.join(__dirname, '..', '..', 'templates', 'base', 'resources', 'parameters');
|
|
159
|
+
const environments = ['local', 'develop', 'test', 'production'];
|
|
160
|
+
|
|
161
|
+
for (const env of environments) {
|
|
162
|
+
const outputPath = path.join(projectDir, 'src', 'main', 'resources', 'parameters', env, 'temporal.yaml');
|
|
163
|
+
const templateFile = path.join(templatePath, env, 'temporal.yaml.ejs');
|
|
164
|
+
|
|
165
|
+
await renderAndWrite(templateFile, outputPath, context);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Add temporal.yaml imports to application-*.yaml files
|
|
171
|
+
*/
|
|
172
|
+
async function addTemporalImports(projectDir) {
|
|
173
|
+
const resourcesDir = path.join(projectDir, 'src', 'main', 'resources');
|
|
174
|
+
const environments = ['local', 'develop', 'test', 'production'];
|
|
175
|
+
|
|
176
|
+
for (const env of environments) {
|
|
177
|
+
const appYmlPath = path.join(resourcesDir, `application-${env}.yaml`);
|
|
178
|
+
|
|
179
|
+
if (await fs.pathExists(appYmlPath)) {
|
|
180
|
+
let content = await fs.readFile(appYmlPath, 'utf-8');
|
|
181
|
+
|
|
182
|
+
// Check if temporal.yaml import already exists
|
|
183
|
+
if (content.includes('temporal.yaml')) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Add temporal.yaml import after existing imports
|
|
188
|
+
const importPattern = /(spring:\s*\n\s*config:\s*\n\s*import:\s*\n(?:\s*-\s*"[^"]+"\s*\n)*)/;
|
|
189
|
+
|
|
190
|
+
if (importPattern.test(content)) {
|
|
191
|
+
content = content.replace(
|
|
192
|
+
importPattern,
|
|
193
|
+
`$1 - "classpath:parameters/${env}/temporal.yaml"\n`
|
|
194
|
+
);
|
|
195
|
+
} else {
|
|
196
|
+
// If no imports section exists, add it
|
|
197
|
+
content = `spring:\n config:\n import:\n - "classpath:parameters/${env}/temporal.yaml"\n\n` + content;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await fs.writeFile(appYmlPath, content, 'utf-8');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Generate HeavyActivity and LightActivity marker interfaces
|
|
207
|
+
*/
|
|
208
|
+
async function generateActivityInterfaces(projectDir, context) {
|
|
209
|
+
const interfacesTemplateDir = path.join(__dirname, '..', '..', 'templates', 'shared', 'interfaces');
|
|
210
|
+
const interfacesOutputDir = path.join(
|
|
211
|
+
projectDir, 'src', 'main', 'java', context.packagePath, 'shared', 'domain', 'interfaces'
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const interfaces = ['HeavyActivity', 'LightActivity'];
|
|
215
|
+
|
|
216
|
+
for (const iface of interfaces) {
|
|
217
|
+
const templateFile = path.join(interfacesTemplateDir, `${iface}.java.ejs`);
|
|
218
|
+
const outputFile = path.join(interfacesOutputDir, `${iface}.java`);
|
|
219
|
+
await renderAndWrite(templateFile, outputFile, context);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Generate TemporalConfig.java class
|
|
225
|
+
*/
|
|
226
|
+
async function generateTemporalConfigClass(projectDir, context) {
|
|
227
|
+
const templatePath = path.join(
|
|
228
|
+
__dirname, '..', '..', 'templates', 'shared', 'configurations', 'temporalConfig', 'TemporalConfig.java.ejs'
|
|
229
|
+
);
|
|
230
|
+
const outputPath = path.join(
|
|
231
|
+
projectDir, 'src', 'main', 'java', context.packagePath,
|
|
232
|
+
'shared', 'infrastructure', 'configurations', 'temporalConfig', 'TemporalConfig.java'
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
await renderAndWrite(templatePath, outputPath, context);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Update docker-compose.yaml to add Temporal services
|
|
240
|
+
*/
|
|
241
|
+
async function updateDockerCompose(projectDir, context) {
|
|
242
|
+
const dockerComposePath = path.join(projectDir, 'docker-compose.yaml');
|
|
243
|
+
|
|
244
|
+
// Check if docker-compose.yaml exists
|
|
245
|
+
if (!(await fs.pathExists(dockerComposePath))) {
|
|
246
|
+
return; // No docker-compose to update
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let dockerComposeContent = await fs.readFile(dockerComposePath, 'utf-8');
|
|
250
|
+
|
|
251
|
+
// Check if Temporal services already exist
|
|
252
|
+
if (dockerComposeContent.includes('temporal:') || dockerComposeContent.includes('temporalio')) {
|
|
253
|
+
return; // Temporal already configured
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Parse existing docker-compose.yaml
|
|
257
|
+
const dockerComposeObj = yaml.load(dockerComposeContent);
|
|
258
|
+
|
|
259
|
+
// Ensure services section exists
|
|
260
|
+
if (!dockerComposeObj.services) {
|
|
261
|
+
dockerComposeObj.services = {};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Render Temporal services template
|
|
265
|
+
const temporalTemplateContent = await renderTemplate(
|
|
266
|
+
path.join(__dirname, '..', '..', 'templates', 'base', 'docker', 'temporal-services.yaml.ejs'),
|
|
267
|
+
context
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// Parse the rendered Temporal services
|
|
271
|
+
const temporalServices = yaml.load(temporalTemplateContent);
|
|
272
|
+
|
|
273
|
+
// Merge Temporal services into existing docker-compose
|
|
274
|
+
Object.assign(dockerComposeObj.services, temporalServices);
|
|
275
|
+
|
|
276
|
+
// Write updated docker-compose.yaml
|
|
277
|
+
const updatedYaml = yaml.dump(dockerComposeObj, {
|
|
278
|
+
indent: 2,
|
|
279
|
+
lineWidth: -1,
|
|
280
|
+
noRefs: true
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await fs.writeFile(dockerComposePath, updatedYaml, 'utf-8');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = addTemporalClientCommand;
|
|
@@ -190,10 +190,19 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
190
190
|
agg.secondaryEntities.some(e => e.audit && e.audit.trackUser)
|
|
191
191
|
);
|
|
192
192
|
|
|
193
|
+
// Always generate PagedResponse shared DTO (used by all ListQueryHandlers)
|
|
194
|
+
const sharedBasePath = path.join(projectDir, 'src', 'main', 'java', packagePath, 'shared');
|
|
195
|
+
const sharedGenerator = new SharedGenerator({ packageName, packagePath });
|
|
196
|
+
await sharedGenerator.generatePagedResponse(sharedBasePath);
|
|
197
|
+
|
|
198
|
+
// Check if any aggregate declares domain events and generate shared DomainEvent base class
|
|
199
|
+
const hasDomainEventsInModule = aggregates.some(agg => agg.domainEvents && agg.domainEvents.length > 0);
|
|
200
|
+
if (hasDomainEventsInModule) {
|
|
201
|
+
await sharedGenerator.generateDomainEvent(sharedBasePath);
|
|
202
|
+
}
|
|
203
|
+
|
|
193
204
|
// Generate audit-related shared components if needed
|
|
194
205
|
if (hasAuditableEntities || hasTrackUserEntities) {
|
|
195
|
-
const sharedBasePath = path.join(projectDir, 'src', 'main', 'java', packagePath, 'shared');
|
|
196
|
-
const sharedGenerator = new SharedGenerator({ packageName, packagePath });
|
|
197
206
|
|
|
198
207
|
// Always generate base AuditableEntity if any audit is enabled
|
|
199
208
|
if (hasAuditableEntities) {
|
|
@@ -241,6 +250,11 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
241
250
|
agg.valueObjects.forEach(vo => {
|
|
242
251
|
console.log(chalk.gray(` │ └── ${vo.name} (VO)`));
|
|
243
252
|
});
|
|
253
|
+
if (agg.domainEvents && agg.domainEvents.length > 0) {
|
|
254
|
+
agg.domainEvents.forEach(event => {
|
|
255
|
+
console.log(chalk.gray(` │ └── ${event.name} (Event${event.kafka ? ' · kafka' : ''})`))
|
|
256
|
+
});
|
|
257
|
+
}
|
|
244
258
|
});
|
|
245
259
|
console.log();
|
|
246
260
|
|
|
@@ -284,7 +298,8 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
284
298
|
imports: rootEntity.imports,
|
|
285
299
|
valueObjects,
|
|
286
300
|
aggregateMethods: aggregate.aggregateMethods,
|
|
287
|
-
auditable: rootEntity.auditable
|
|
301
|
+
auditable: rootEntity.auditable,
|
|
302
|
+
domainEvents: aggregate.domainEvents || []
|
|
288
303
|
};
|
|
289
304
|
|
|
290
305
|
await renderAndWrite(
|
|
@@ -449,7 +464,8 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
449
464
|
packageName,
|
|
450
465
|
moduleName,
|
|
451
466
|
aggregateName,
|
|
452
|
-
rootEntity
|
|
467
|
+
rootEntity,
|
|
468
|
+
hasDomainEvents: (aggregate.domainEvents || []).length > 0
|
|
453
469
|
};
|
|
454
470
|
|
|
455
471
|
await renderAndWrite(
|
|
@@ -459,6 +475,45 @@ async function generateEntitiesCommand(moduleName, options = {}) {
|
|
|
459
475
|
writeOptions
|
|
460
476
|
);
|
|
461
477
|
generatedFiles.push({ type: 'Repository Impl', name: `${rootEntity.name}RepositoryImpl`, path: `${moduleName}/infrastructure/database/repositories/${rootEntity.name}RepositoryImpl.java` });
|
|
478
|
+
|
|
479
|
+
// 9. Generate Domain Events (if declared in domain.yaml)
|
|
480
|
+
const aggregateDomainEvents = aggregate.domainEvents || [];
|
|
481
|
+
if (aggregateDomainEvents.length > 0) {
|
|
482
|
+
|
|
483
|
+
for (const event of aggregateDomainEvents) {
|
|
484
|
+
const eventContext = {
|
|
485
|
+
packageName,
|
|
486
|
+
moduleName,
|
|
487
|
+
aggregateName,
|
|
488
|
+
name: event.name,
|
|
489
|
+
fields: event.fields,
|
|
490
|
+
kafka: event.kafka
|
|
491
|
+
};
|
|
492
|
+
await renderAndWrite(
|
|
493
|
+
path.join(__dirname, '..', '..', 'templates', 'aggregate', 'DomainEventRecord.java.ejs'),
|
|
494
|
+
path.join(moduleBasePath, 'domain', 'models', 'events', `${event.name}.java`),
|
|
495
|
+
eventContext,
|
|
496
|
+
writeOptions
|
|
497
|
+
);
|
|
498
|
+
generatedFiles.push({ type: 'Domain Event', name: event.name, path: `${moduleName}/domain/models/events/${event.name}.java` });
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Generate the bridge handler
|
|
502
|
+
const handlerContext = {
|
|
503
|
+
packageName,
|
|
504
|
+
moduleName,
|
|
505
|
+
aggregateName,
|
|
506
|
+
domainEvents: aggregateDomainEvents,
|
|
507
|
+
hasKafkaEvents: aggregateDomainEvents.some(e => e.kafka)
|
|
508
|
+
};
|
|
509
|
+
await renderAndWrite(
|
|
510
|
+
path.join(__dirname, '..', '..', 'templates', 'aggregate', 'DomainEventHandler.java.ejs'),
|
|
511
|
+
path.join(moduleBasePath, 'application', 'usecases', `${aggregateName}DomainEventHandler.java`),
|
|
512
|
+
handlerContext,
|
|
513
|
+
writeOptions
|
|
514
|
+
);
|
|
515
|
+
generatedFiles.push({ type: 'Domain Event Handler', name: `${aggregateName}DomainEventHandler`, path: `${moduleName}/application/usecases/${aggregateName}DomainEventHandler.java` });
|
|
516
|
+
}
|
|
462
517
|
}
|
|
463
518
|
|
|
464
519
|
spinner.succeed(chalk.green(`Generated ${generatedFiles.length} files! ✨`));
|
|
@@ -813,6 +868,14 @@ async function generateCrudResources(aggregate, moduleName, moduleBasePath, pack
|
|
|
813
868
|
writeOptions
|
|
814
869
|
);
|
|
815
870
|
generatedFiles.push({ type: 'Command', name: `Delete${aggregateName}Command`, path: `${moduleName}/application/commands/Delete${aggregateName}Command.java` });
|
|
871
|
+
|
|
872
|
+
await renderAndWrite(
|
|
873
|
+
path.join(templatesDir, 'UpdateCommand.java.ejs'),
|
|
874
|
+
path.join(moduleBasePath, 'application', 'commands', `Update${aggregateName}Command.java`),
|
|
875
|
+
{ ...baseContext, imports: commandAppImports },
|
|
876
|
+
writeOptions
|
|
877
|
+
);
|
|
878
|
+
generatedFiles.push({ type: 'Command', name: `Update${aggregateName}Command`, path: `${moduleName}/application/commands/Update${aggregateName}Command.java` });
|
|
816
879
|
|
|
817
880
|
// 3. Generate Queries
|
|
818
881
|
await renderAndWrite(
|
|
@@ -869,6 +932,14 @@ async function generateCrudResources(aggregate, moduleName, moduleBasePath, pack
|
|
|
869
932
|
writeOptions
|
|
870
933
|
);
|
|
871
934
|
generatedFiles.push({ type: 'Handler', name: `Delete${aggregateName}CommandHandler`, path: `${moduleName}/application/usecases/Delete${aggregateName}CommandHandler.java` });
|
|
935
|
+
|
|
936
|
+
await renderAndWrite(
|
|
937
|
+
path.join(templatesDir, 'UpdateCommandHandler.java.ejs'),
|
|
938
|
+
path.join(moduleBasePath, 'application', 'usecases', `Update${aggregateName}CommandHandler.java`),
|
|
939
|
+
baseContext,
|
|
940
|
+
writeOptions
|
|
941
|
+
);
|
|
942
|
+
generatedFiles.push({ type: 'Handler', name: `Update${aggregateName}CommandHandler`, path: `${moduleName}/application/usecases/Update${aggregateName}CommandHandler.java` });
|
|
872
943
|
|
|
873
944
|
// 5. Generate DTOs
|
|
874
945
|
const responseDtoContext = {
|