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
|
@@ -1,498 +1,219 @@
|
|
|
1
|
-
# Command `generate kafka-event` (alias: `g kafka-event`)
|
|
2
|
-
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
Generates infrastructure for publishing domain
|
|
6
|
-
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
Implement event
|
|
10
|
-
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### Parameters
|
|
19
|
-
|
|
20
|
-
| Parameter | Required | Description |
|
|
21
|
-
|-----------|----------|-------------|
|
|
22
|
-
| `
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
public
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
import
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
@
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
|
221
|
-
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
|
|
222
|
-
config.put(ProducerConfig.ACKS_CONFIG, "all");
|
|
223
|
-
config.put(ProducerConfig.RETRIES_CONFIG, 3);
|
|
224
|
-
config.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
|
|
225
|
-
return new DefaultKafkaProducerFactory<>(config);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
@Bean
|
|
229
|
-
public KafkaTemplate<String, Object> kafkaTemplate() {
|
|
230
|
-
return new KafkaTemplate<>(producerFactory());
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
## ✨ Features
|
|
236
|
-
|
|
237
|
-
### Event Publishing
|
|
238
|
-
- ✅ **Asynchronous** - Non-blocking event publishing
|
|
239
|
-
- ✅ **Idempotent** - Prevents duplicate events
|
|
240
|
-
- ✅ **Reliable** - Acknowledgment and retries
|
|
241
|
-
- ✅ **Partitioned** - Uses orderId as partition key
|
|
242
|
-
- ✅ **Logged** - Success/failure logging
|
|
243
|
-
|
|
244
|
-
### Event Structure
|
|
245
|
-
- ✅ **Event ID** - Unique identifier for deduplication
|
|
246
|
-
- ✅ **Timestamp** - When event occurred
|
|
247
|
-
- ✅ **Type** - Event type identifier
|
|
248
|
-
- ✅ **Version** - Schema versioning
|
|
249
|
-
- ✅ **Payload** - Business data
|
|
250
|
-
|
|
251
|
-
## 🔧 Configuration
|
|
252
|
-
|
|
253
|
-
### Application Properties
|
|
254
|
-
|
|
255
|
-
```yaml
|
|
256
|
-
# application.yaml
|
|
257
|
-
|
|
258
|
-
spring:
|
|
259
|
-
kafka:
|
|
260
|
-
bootstrap-servers: localhost:9092
|
|
261
|
-
producer:
|
|
262
|
-
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
|
263
|
-
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
|
|
264
|
-
acks: all
|
|
265
|
-
retries: 3
|
|
266
|
-
properties:
|
|
267
|
-
enable.idempotence: true
|
|
268
|
-
max.in.flight.requests.per.connection: 5
|
|
269
|
-
|
|
270
|
-
# Topic configuration
|
|
271
|
-
kafka:
|
|
272
|
-
topics:
|
|
273
|
-
order-created: order.created
|
|
274
|
-
payment-processed: payment.processed
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
## 🎯 Usage Examples
|
|
278
|
-
|
|
279
|
-
### 1. Publish from Command Handler
|
|
280
|
-
|
|
281
|
-
```java
|
|
282
|
-
@Service
|
|
283
|
-
@RequiredArgsConstructor
|
|
284
|
-
public class CreateOrderCommandHandler {
|
|
285
|
-
|
|
286
|
-
private final OrderRepository repository;
|
|
287
|
-
private final OrderEventPublisher eventPublisher;
|
|
288
|
-
|
|
289
|
-
@Transactional
|
|
290
|
-
public OrderResponseDto handle(CreateOrderCommand command) {
|
|
291
|
-
// Create order
|
|
292
|
-
Order order = Order.create(command.getCustomerId(), command.getItems());
|
|
293
|
-
Order savedOrder = repository.save(order);
|
|
294
|
-
|
|
295
|
-
// Publish event
|
|
296
|
-
OrderCreatedEvent event = new OrderCreatedEvent(
|
|
297
|
-
savedOrder.getId(),
|
|
298
|
-
savedOrder.getCustomerId(),
|
|
299
|
-
savedOrder.getOrderNumber()
|
|
300
|
-
);
|
|
301
|
-
eventPublisher.publishOrderCreated(event);
|
|
302
|
-
|
|
303
|
-
return OrderMapper.toDto(savedOrder);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
### 2. Publish from Domain Entity
|
|
309
|
-
|
|
310
|
-
```java
|
|
311
|
-
@Entity
|
|
312
|
-
public class Order {
|
|
313
|
-
|
|
314
|
-
@Transient
|
|
315
|
-
private final List<DomainEvent> domainEvents = new ArrayList<>();
|
|
316
|
-
|
|
317
|
-
public static Order create(Long customerId, List<OrderItem> items) {
|
|
318
|
-
Order order = new Order();
|
|
319
|
-
order.setCustomerId(customerId);
|
|
320
|
-
order.setItems(items);
|
|
321
|
-
|
|
322
|
-
// Register domain event
|
|
323
|
-
order.registerEvent(new OrderCreatedEvent(
|
|
324
|
-
order.getId(),
|
|
325
|
-
order.getCustomerId(),
|
|
326
|
-
order.getOrderNumber()
|
|
327
|
-
));
|
|
328
|
-
|
|
329
|
-
return order;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
private void registerEvent(DomainEvent event) {
|
|
333
|
-
domainEvents.add(event);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
public List<DomainEvent> getDomainEvents() {
|
|
337
|
-
return Collections.unmodifiableList(domainEvents);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// In handler
|
|
342
|
-
@Transactional
|
|
343
|
-
public void handle(CreateOrderCommand command) {
|
|
344
|
-
Order order = Order.create(command.getCustomerId(), command.getItems());
|
|
345
|
-
Order savedOrder = repository.save(order);
|
|
346
|
-
|
|
347
|
-
// Publish collected events
|
|
348
|
-
savedOrder.getDomainEvents().forEach(event -> {
|
|
349
|
-
if (event instanceof OrderCreatedEvent) {
|
|
350
|
-
eventPublisher.publishOrderCreated((OrderCreatedEvent) event);
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### 3. Publish Multiple Events
|
|
357
|
-
|
|
358
|
-
```java
|
|
359
|
-
@Service
|
|
360
|
-
@RequiredArgsConstructor
|
|
361
|
-
public class OrderEventPublisher {
|
|
362
|
-
|
|
363
|
-
private final KafkaTemplate<String, Object> kafkaTemplate;
|
|
364
|
-
|
|
365
|
-
public void publishOrderCreated(OrderCreatedEvent event) {
|
|
366
|
-
kafkaTemplate.send("order.created", event.getOrderId().toString(),
|
|
367
|
-
mapToKafkaEvent(event));
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
public void publishOrderCancelled(OrderCancelledEvent event) {
|
|
371
|
-
kafkaTemplate.send("order.cancelled", event.getOrderId().toString(),
|
|
372
|
-
mapToKafkaEvent(event));
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
public void publishOrderShipped(OrderShippedEvent event) {
|
|
376
|
-
kafkaTemplate.send("order.shipped", event.getOrderId().toString(),
|
|
377
|
-
mapToKafkaEvent(event));
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
## 🔄 Event-Driven Patterns
|
|
383
|
-
|
|
384
|
-
### 1. Event Notification
|
|
385
|
-
|
|
386
|
-
```java
|
|
387
|
-
// Order service publishes
|
|
388
|
-
eventPublisher.publishOrderCreated(event);
|
|
389
|
-
|
|
390
|
-
// Inventory service listens and reacts
|
|
391
|
-
@KafkaListener(topics = "order.created")
|
|
392
|
-
public void handleOrderCreated(OrderCreatedKafkaEvent event) {
|
|
393
|
-
inventoryService.reserveStock(event.getOrderId());
|
|
394
|
-
}
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### 2. Event-Carried State Transfer
|
|
398
|
-
|
|
399
|
-
```java
|
|
400
|
-
// Include all necessary data in event
|
|
401
|
-
public class OrderCreatedEvent {
|
|
402
|
-
private Long orderId;
|
|
403
|
-
private Long customerId;
|
|
404
|
-
private List<OrderItem> items; // Full state
|
|
405
|
-
private BigDecimal totalAmount;
|
|
406
|
-
private String shippingAddress;
|
|
407
|
-
}
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### 3. Event Sourcing (Advanced)
|
|
411
|
-
|
|
412
|
-
```java
|
|
413
|
-
// Store events as source of truth
|
|
414
|
-
public class OrderEventStore {
|
|
415
|
-
public void save(OrderCreatedEvent event) {
|
|
416
|
-
// Persist event to event store
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
public Order rebuild(Long orderId) {
|
|
420
|
-
// Replay events to rebuild state
|
|
421
|
-
List<DomainEvent> events = loadEvents(orderId);
|
|
422
|
-
return Order.fromEvents(events);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
## 🚀 Next Steps
|
|
428
|
-
|
|
429
|
-
After generating Kafka event infrastructure:
|
|
430
|
-
|
|
431
|
-
1. **Define event payload:**
|
|
432
|
-
```java
|
|
433
|
-
public class OrderCreatedEvent {
|
|
434
|
-
private Long orderId;
|
|
435
|
-
private Long customerId;
|
|
436
|
-
private BigDecimal totalAmount;
|
|
437
|
-
private List<OrderItemDto> items;
|
|
438
|
-
}
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
2. **Publish from business logic:**
|
|
442
|
-
```java
|
|
443
|
-
eventPublisher.publishOrderCreated(event);
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
3. **Create listeners in other modules:**
|
|
447
|
-
```bash
|
|
448
|
-
eva4j g kafka-listener OrderCreated
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
4. **Monitor events:**
|
|
452
|
-
- Use Kafka UI (http://localhost:8080 if using docker-compose)
|
|
453
|
-
- Check logs for publish confirmations
|
|
454
|
-
|
|
455
|
-
## ⚠️ Prerequisites
|
|
456
|
-
|
|
457
|
-
- Be in a project created with `eva4j create`
|
|
458
|
-
- Module must exist
|
|
459
|
-
- Kafka must be running (use `docker-compose up -d`)
|
|
460
|
-
- Spring Kafka dependency (automatically added)
|
|
461
|
-
|
|
462
|
-
## 🔍 Validations
|
|
463
|
-
|
|
464
|
-
The command validates:
|
|
465
|
-
- ✅ Valid eva4j project
|
|
466
|
-
- ✅ Event name is in PascalCase
|
|
467
|
-
- ✅ Module exists
|
|
468
|
-
- ✅ Kafka is configured
|
|
469
|
-
|
|
470
|
-
## 📚 See Also
|
|
471
|
-
|
|
472
|
-
- [generate-kafka-listener](./GENERATE_KAFKA_LISTENER.md) - Consume Kafka events
|
|
473
|
-
- [add-kafka-client](./ADD_KAFKA_CLIENT.md) - Add Kafka to module
|
|
474
|
-
- [generate-entities](./GENERATE_ENTITIES.md) - Generate domain models
|
|
475
|
-
|
|
476
|
-
## 🐛 Troubleshooting
|
|
477
|
-
|
|
478
|
-
**Error: "Kafka connection refused"**
|
|
479
|
-
- Solution: Ensure Kafka is running: `docker-compose up -d kafka`
|
|
480
|
-
|
|
481
|
-
**Events not appearing in topic**
|
|
482
|
-
- Solution: Check Kafka logs and verify topic exists
|
|
483
|
-
```bash
|
|
484
|
-
docker exec -it kafka kafka-topics --list --bootstrap-server localhost:9092
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
**Serialization errors**
|
|
488
|
-
- Solution: Ensure event classes are serializable and have no-arg constructor
|
|
489
|
-
|
|
490
|
-
**Lost events**
|
|
491
|
-
- Solution: Configure appropriate `acks` and `retries`:
|
|
492
|
-
```yaml
|
|
493
|
-
spring:
|
|
494
|
-
kafka:
|
|
495
|
-
producer:
|
|
496
|
-
acks: all
|
|
497
|
-
retries: 3
|
|
498
|
-
```
|
|
1
|
+
# Command `generate kafka-event` (alias: `g kafka-event`)
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Generates the infrastructure for publishing a domain event to an Apache Kafka topic, enabling asynchronous event-driven communication between modules or microservices.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Implement event publishing without coupling the sender to the consumer. The generated `MessageBroker` port keeps the domain free of Kafka dependencies, while the adapter handles the actual publishing using `EventEnvelope` for correlation tracking.
|
|
10
|
+
|
|
11
|
+
## Syntax
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
eva generate kafka-event <module> [event-name]
|
|
15
|
+
eva g kafka-event <module> [event-name] # Short alias
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Parameters
|
|
19
|
+
|
|
20
|
+
| Parameter | Required | Description |
|
|
21
|
+
|-----------|----------|-------------|
|
|
22
|
+
| `module` | Yes | Module that owns and publishes the event (e.g., `user`, `order`) |
|
|
23
|
+
| `event-name` | No | Event name in kebab-case — prompted if omitted; `Event` suffix is added automatically |
|
|
24
|
+
|
|
25
|
+
> **Interactive prompts:**
|
|
26
|
+
> 1. **Event name** — if not provided (e.g., `user-created`, `order-placed`)
|
|
27
|
+
> 2. **Topic name** — Kafka topic identifier (e.g., `USER_CREATED`)
|
|
28
|
+
> 3. **Number of partitions** — default: 3
|
|
29
|
+
> 4. **Number of replicas** — default: 1
|
|
30
|
+
|
|
31
|
+
**Note:** Running the command multiple times on the same module safely **appends** a new method to the existing `MessageBroker.java` without overwriting it.
|
|
32
|
+
|
|
33
|
+
## Prerequisites
|
|
34
|
+
|
|
35
|
+
Kafka client must be installed in the project:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
eva add kafka-client
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Examples
|
|
42
|
+
|
|
43
|
+
### Example 1: User created event
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
eva g kafka-event user user-created
|
|
47
|
+
# Event class: UserCreatedEvent
|
|
48
|
+
# Topic: USER_CREATED (entered at prompt)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Generates:
|
|
52
|
+
- `user/application/events/UserCreatedEvent.java`
|
|
53
|
+
- `user/application/ports/MessageBroker.java` (created or appended)
|
|
54
|
+
- `user/infrastructure/adapters/kafkaMessageBroker/UserKafkaMessageBroker.java`
|
|
55
|
+
- Adds topic config to `parameters/*/kafka.yaml`
|
|
56
|
+
|
|
57
|
+
### Example 2: Order placed event
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
eva g kafka-event order order-placed
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Example 3: Multiple events in the same module
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
eva g kafka-event user user-created # Creates MessageBroker with publishUserCreatedEvent
|
|
67
|
+
eva g kafka-event user user-updated # Appends publishUserUpdatedEvent to MessageBroker
|
|
68
|
+
eva g kafka-event user user-deleted # Appends publishUserDeletedEvent to MessageBroker
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Each run adds a new method without overwriting existing ones.
|
|
72
|
+
|
|
73
|
+
## Generated Code Structure
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
<module>/
|
|
77
|
+
├── application/
|
|
78
|
+
│ ├── events/
|
|
79
|
+
│ │ └── UserCreatedEvent.java # Java record (event data)
|
|
80
|
+
│ └── ports/
|
|
81
|
+
│ └── MessageBroker.java # Port interface (append-safe)
|
|
82
|
+
│
|
|
83
|
+
└── infrastructure/
|
|
84
|
+
└── adapters/
|
|
85
|
+
└── kafkaMessageBroker/
|
|
86
|
+
└── UserKafkaMessageBroker.java # Kafka adapter
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Generated Files
|
|
90
|
+
|
|
91
|
+
### 1. Event (Application Layer)
|
|
92
|
+
|
|
93
|
+
**UserCreatedEvent.java** (`application/events/`):
|
|
94
|
+
```java
|
|
95
|
+
package com.example.project.user.application.events;
|
|
96
|
+
|
|
97
|
+
public record UserCreatedEvent(
|
|
98
|
+
// TODO: Add your event fields here
|
|
99
|
+
// Example:
|
|
100
|
+
// Long id,
|
|
101
|
+
// String name,
|
|
102
|
+
// LocalDateTime createdAt
|
|
103
|
+
) {
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> Events are pure Java records — immutable, no Lombok, no framework annotations.
|
|
108
|
+
|
|
109
|
+
### 2. MessageBroker Port (Application Layer)
|
|
110
|
+
|
|
111
|
+
**MessageBroker.java** (`application/ports/`):
|
|
112
|
+
```java
|
|
113
|
+
package com.example.project.user.application.ports;
|
|
114
|
+
|
|
115
|
+
import com.example.project.user.application.events.UserCreatedEvent;
|
|
116
|
+
|
|
117
|
+
public interface MessageBroker {
|
|
118
|
+
|
|
119
|
+
void publishUserCreatedEvent(UserCreatedEvent event);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> If `MessageBroker.java` already exists (from a previous run), the new `publishXxx` method is **appended** to the interface rather than overwriting it.
|
|
124
|
+
|
|
125
|
+
### 3. Kafka Adapter (Infrastructure)
|
|
126
|
+
|
|
127
|
+
**UserKafkaMessageBroker.java** (`infrastructure/adapters/kafkaMessageBroker/`):
|
|
128
|
+
```java
|
|
129
|
+
package com.example.project.user.infrastructure.adapters.kafkaMessageBroker;
|
|
130
|
+
|
|
131
|
+
import com.example.project.user.application.events.UserCreatedEvent;
|
|
132
|
+
import com.example.project.user.application.ports.MessageBroker;
|
|
133
|
+
import com.example.project.shared.infrastructure.eventEnvelope.EventEnvelope;
|
|
134
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
135
|
+
import org.springframework.kafka.core.KafkaTemplate;
|
|
136
|
+
import org.springframework.stereotype.Component;
|
|
137
|
+
import org.slf4j.MDC;
|
|
138
|
+
|
|
139
|
+
@Component("userKafkaMessageBroker")
|
|
140
|
+
public class UserKafkaMessageBroker implements MessageBroker {
|
|
141
|
+
|
|
142
|
+
@Value("${topics.user-created}")
|
|
143
|
+
private String usercreatedTopic;
|
|
144
|
+
|
|
145
|
+
private final KafkaTemplate<String, Object> kafkaTemplate;
|
|
146
|
+
|
|
147
|
+
public UserKafkaMessageBroker(KafkaTemplate<String, Object> kafkaTemplate) {
|
|
148
|
+
this.kafkaTemplate = kafkaTemplate;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@Override
|
|
152
|
+
public void publishUserCreatedEvent(UserCreatedEvent event) {
|
|
153
|
+
EventEnvelope<UserCreatedEvent> envelope = EventEnvelope.of(
|
|
154
|
+
usercreatedTopic,
|
|
155
|
+
event,
|
|
156
|
+
MDC.get("correlationId")
|
|
157
|
+
);
|
|
158
|
+
kafkaTemplate.send(usercreatedTopic, envelope);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> `EventEnvelope.of(topic, payload, correlationId)` wraps the event with routing metadata and the current MDC correlation ID for distributed tracing.
|
|
164
|
+
|
|
165
|
+
## Configuration Added
|
|
166
|
+
|
|
167
|
+
```yaml
|
|
168
|
+
# parameters/local/kafka.yaml
|
|
169
|
+
spring.kafka:
|
|
170
|
+
topics:
|
|
171
|
+
user-created: USER_CREATED
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The same entry is added to every environment (`local`, `develop`, `test`, `production`).
|
|
175
|
+
|
|
176
|
+
## Usage in a Command Handler
|
|
177
|
+
|
|
178
|
+
Inject `MessageBroker` (the port, not the Kafka class) and call the publish method:
|
|
179
|
+
|
|
180
|
+
```java
|
|
181
|
+
@ApplicationComponent
|
|
182
|
+
public class CreateUserCommandHandler {
|
|
183
|
+
|
|
184
|
+
private final UserRepository userRepository;
|
|
185
|
+
private final MessageBroker messageBroker;
|
|
186
|
+
|
|
187
|
+
public CreateUserCommandHandler(UserRepository userRepository, MessageBroker messageBroker) {
|
|
188
|
+
this.userRepository = userRepository;
|
|
189
|
+
this.messageBroker = messageBroker;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public void handle(CreateUserCommand command) {
|
|
193
|
+
User user = new User(command.name(), command.email());
|
|
194
|
+
userRepository.save(user);
|
|
195
|
+
|
|
196
|
+
messageBroker.publishUserCreatedEvent(
|
|
197
|
+
new UserCreatedEvent(/* fill fields */)
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Bean Naming
|
|
204
|
+
|
|
205
|
+
The adapter is registered with an explicit bean name to avoid conflicts when multiple modules have a `MessageBroker`:
|
|
206
|
+
|
|
207
|
+
| Module | Bean name |
|
|
208
|
+
|--------|-----------|
|
|
209
|
+
| `user` | `userKafkaMessageBroker` |
|
|
210
|
+
| `order` | `orderKafkaMessageBroker` |
|
|
211
|
+
| `product` | `productKafkaMessageBroker` |
|
|
212
|
+
|
|
213
|
+
Spring resolves the correct adapter for each module via constructor injection by type + qualifier if needed.
|
|
214
|
+
|
|
215
|
+
## See Also
|
|
216
|
+
|
|
217
|
+
- [generate-kafka-listener](./GENERATE_KAFKA_LISTENER.md) — Create Kafka event consumer
|
|
218
|
+
- [add-kafka-client](./ADD_MODULE.md) — Install Kafka dependencies
|
|
219
|
+
- [generate-usecase](./GENERATE_USECASE.md) — Create use case handlers
|