@corbat-tech/coding-standards-mcp 1.0.0

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +371 -0
  3. package/assets/demo.gif +0 -0
  4. package/dist/agent.d.ts +53 -0
  5. package/dist/agent.d.ts.map +1 -0
  6. package/dist/agent.js +629 -0
  7. package/dist/agent.js.map +1 -0
  8. package/dist/cli/init.d.ts +3 -0
  9. package/dist/cli/init.d.ts.map +1 -0
  10. package/dist/cli/init.js +651 -0
  11. package/dist/cli/init.js.map +1 -0
  12. package/dist/config.d.ts +73 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +105 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +73 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/profiles.d.ts +39 -0
  21. package/dist/profiles.d.ts.map +1 -0
  22. package/dist/profiles.js +526 -0
  23. package/dist/profiles.js.map +1 -0
  24. package/dist/prompts-legacy.d.ts +25 -0
  25. package/dist/prompts-legacy.d.ts.map +1 -0
  26. package/dist/prompts-legacy.js +600 -0
  27. package/dist/prompts-legacy.js.map +1 -0
  28. package/dist/prompts-v2.d.ts +30 -0
  29. package/dist/prompts-v2.d.ts.map +1 -0
  30. package/dist/prompts-v2.js +310 -0
  31. package/dist/prompts-v2.js.map +1 -0
  32. package/dist/prompts.d.ts +30 -0
  33. package/dist/prompts.d.ts.map +1 -0
  34. package/dist/prompts.js +310 -0
  35. package/dist/prompts.js.map +1 -0
  36. package/dist/resources.d.ts +18 -0
  37. package/dist/resources.d.ts.map +1 -0
  38. package/dist/resources.js +95 -0
  39. package/dist/resources.js.map +1 -0
  40. package/dist/tools-legacy.d.ts +196 -0
  41. package/dist/tools-legacy.d.ts.map +1 -0
  42. package/dist/tools-legacy.js +1230 -0
  43. package/dist/tools-legacy.js.map +1 -0
  44. package/dist/tools-v2.d.ts +92 -0
  45. package/dist/tools-v2.d.ts.map +1 -0
  46. package/dist/tools-v2.js +410 -0
  47. package/dist/tools-v2.js.map +1 -0
  48. package/dist/tools.d.ts +92 -0
  49. package/dist/tools.d.ts.map +1 -0
  50. package/dist/tools.js +410 -0
  51. package/dist/tools.js.map +1 -0
  52. package/dist/types.d.ts +3054 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/types.js +515 -0
  55. package/dist/types.js.map +1 -0
  56. package/dist/utils/index.d.ts +6 -0
  57. package/dist/utils/index.d.ts.map +1 -0
  58. package/dist/utils/index.js +5 -0
  59. package/dist/utils/index.js.map +1 -0
  60. package/dist/utils/retry.d.ts +44 -0
  61. package/dist/utils/retry.d.ts.map +1 -0
  62. package/dist/utils/retry.js +74 -0
  63. package/dist/utils/retry.js.map +1 -0
  64. package/package.json +79 -0
  65. package/profiles/README.md +199 -0
  66. package/profiles/custom/.gitkeep +2 -0
  67. package/profiles/templates/_template.yaml +159 -0
  68. package/profiles/templates/angular.yaml +494 -0
  69. package/profiles/templates/java-spring-backend.yaml +512 -0
  70. package/profiles/templates/minimal.yaml +102 -0
  71. package/profiles/templates/nodejs.yaml +338 -0
  72. package/profiles/templates/python.yaml +340 -0
  73. package/profiles/templates/react.yaml +331 -0
  74. package/profiles/templates/vue.yaml +598 -0
  75. package/standards/architecture/ddd.md +173 -0
  76. package/standards/architecture/hexagonal.md +97 -0
  77. package/standards/cicd/github-actions.md +567 -0
  78. package/standards/clean-code/naming.md +175 -0
  79. package/standards/clean-code/principles.md +179 -0
  80. package/standards/containerization/dockerfile.md +419 -0
  81. package/standards/database/selection-guide.md +443 -0
  82. package/standards/documentation/guidelines.md +189 -0
  83. package/standards/event-driven/domain-events.md +527 -0
  84. package/standards/kubernetes/deployment.md +518 -0
  85. package/standards/observability/guidelines.md +665 -0
  86. package/standards/project-setup/initialization-checklist.md +650 -0
  87. package/standards/spring-boot/best-practices.md +598 -0
  88. package/standards/testing/guidelines.md +559 -0
  89. package/standards/workflow/llm-development-workflow.md +542 -0
@@ -0,0 +1,527 @@
1
+ # Event-Driven Architecture Guidelines
2
+
3
+ ## Overview
4
+
5
+ Event-driven architecture enables loose coupling between components through asynchronous messaging. This guide covers domain events, event publishing, and messaging patterns for Spring Boot applications.
6
+
7
+ ## Domain Events
8
+
9
+ Domain events represent something significant that happened in the domain. They are named in the past tense and carry immutable data.
10
+
11
+ ### Event Structure
12
+
13
+ ```java
14
+ // Base domain event
15
+ public abstract class DomainEvent {
16
+ private final String eventId;
17
+ private final Instant occurredOn;
18
+ private final String aggregateId;
19
+ private final String aggregateType;
20
+
21
+ protected DomainEvent(String aggregateId, String aggregateType) {
22
+ this.eventId = UUID.randomUUID().toString();
23
+ this.occurredOn = Instant.now();
24
+ this.aggregateId = aggregateId;
25
+ this.aggregateType = aggregateType;
26
+ }
27
+
28
+ // Getters...
29
+ }
30
+
31
+ // Concrete domain event
32
+ public class OrderCreatedEvent extends DomainEvent {
33
+ private final String customerId;
34
+ private final BigDecimal totalAmount;
35
+ private final List<OrderLineData> lines;
36
+
37
+ public OrderCreatedEvent(
38
+ String orderId,
39
+ String customerId,
40
+ BigDecimal totalAmount,
41
+ List<OrderLineData> lines) {
42
+ super(orderId, "Order");
43
+ this.customerId = customerId;
44
+ this.totalAmount = totalAmount;
45
+ this.lines = List.copyOf(lines);
46
+ }
47
+
48
+ // Immutable record for nested data
49
+ public record OrderLineData(
50
+ String productId,
51
+ int quantity,
52
+ BigDecimal unitPrice
53
+ ) {}
54
+ }
55
+ ```
56
+
57
+ ### Event Naming Conventions
58
+
59
+ | Pattern | Examples |
60
+ |---------|----------|
61
+ | Past tense | `OrderCreatedEvent`, `PaymentReceivedEvent`, `OrderShippedEvent` |
62
+ | Aggregate + Action | `Order` + `Created` = `OrderCreatedEvent` |
63
+ | Descriptive | `CustomerEmailVerifiedEvent`, `InventoryReservedEvent` |
64
+
65
+ ### Common Domain Events
66
+
67
+ ```java
68
+ // Order domain events
69
+ public class OrderCreatedEvent extends DomainEvent { }
70
+ public class OrderConfirmedEvent extends DomainEvent { }
71
+ public class OrderShippedEvent extends DomainEvent { }
72
+ public class OrderCancelledEvent extends DomainEvent { }
73
+ public class OrderDeliveredEvent extends DomainEvent { }
74
+
75
+ // Payment domain events
76
+ public class PaymentInitiatedEvent extends DomainEvent { }
77
+ public class PaymentReceivedEvent extends DomainEvent { }
78
+ public class PaymentFailedEvent extends DomainEvent { }
79
+ public class RefundProcessedEvent extends DomainEvent { }
80
+
81
+ // Customer domain events
82
+ public class CustomerRegisteredEvent extends DomainEvent { }
83
+ public class CustomerEmailVerifiedEvent extends DomainEvent { }
84
+ public class CustomerAddressUpdatedEvent extends DomainEvent { }
85
+ ```
86
+
87
+ ## Event Publishing
88
+
89
+ ### Domain Event Publisher Interface
90
+
91
+ Define the interface in the domain layer (port):
92
+
93
+ ```java
94
+ // Domain layer - port
95
+ public interface DomainEventPublisher {
96
+ void publish(DomainEvent event);
97
+ void publishAll(List<DomainEvent> events);
98
+ }
99
+ ```
100
+
101
+ ### Aggregate Event Collection
102
+
103
+ Aggregates collect domain events during business operations:
104
+
105
+ ```java
106
+ public abstract class AggregateRoot {
107
+ private final List<DomainEvent> domainEvents = new ArrayList<>();
108
+
109
+ protected void registerEvent(DomainEvent event) {
110
+ domainEvents.add(event);
111
+ }
112
+
113
+ public List<DomainEvent> getDomainEvents() {
114
+ return Collections.unmodifiableList(domainEvents);
115
+ }
116
+
117
+ public void clearDomainEvents() {
118
+ domainEvents.clear();
119
+ }
120
+ }
121
+
122
+ public class Order extends AggregateRoot {
123
+ private OrderId id;
124
+ private OrderStatus status;
125
+
126
+ public static Order create(OrderId id, CustomerId customerId, List<OrderLine> lines) {
127
+ Order order = new Order();
128
+ order.id = id;
129
+ order.customerId = customerId;
130
+ order.lines = new ArrayList<>(lines);
131
+ order.status = OrderStatus.PENDING;
132
+ order.createdAt = Instant.now();
133
+
134
+ // Register domain event
135
+ order.registerEvent(new OrderCreatedEvent(
136
+ id.value(),
137
+ customerId.value(),
138
+ order.calculateTotal().amount(),
139
+ lines.stream().map(OrderLine::toEventData).toList()
140
+ ));
141
+
142
+ return order;
143
+ }
144
+
145
+ public void confirm() {
146
+ if (status != OrderStatus.PENDING) {
147
+ throw new IllegalStateException("Order can only be confirmed when pending");
148
+ }
149
+ this.status = OrderStatus.CONFIRMED;
150
+ registerEvent(new OrderConfirmedEvent(id.value()));
151
+ }
152
+
153
+ public void ship(TrackingNumber trackingNumber) {
154
+ if (status != OrderStatus.CONFIRMED) {
155
+ throw new IllegalStateException("Order must be confirmed before shipping");
156
+ }
157
+ this.status = OrderStatus.SHIPPED;
158
+ this.trackingNumber = trackingNumber;
159
+ registerEvent(new OrderShippedEvent(id.value(), trackingNumber.value()));
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### Use Case Event Publishing
165
+
166
+ Publish events after the use case completes successfully:
167
+
168
+ ```java
169
+ @Service
170
+ @RequiredArgsConstructor
171
+ @Transactional
172
+ public class PlaceOrderUseCase {
173
+
174
+ private final OrderRepository orderRepository;
175
+ private final DomainEventPublisher eventPublisher;
176
+
177
+ public OrderId execute(PlaceOrderCommand command) {
178
+ // Create the order (events are registered)
179
+ Order order = Order.create(
180
+ OrderId.generate(),
181
+ command.customerId(),
182
+ command.toOrderLines()
183
+ );
184
+
185
+ // Persist the order
186
+ orderRepository.save(order);
187
+
188
+ // Publish events after successful persistence
189
+ eventPublisher.publishAll(order.getDomainEvents());
190
+ order.clearDomainEvents();
191
+
192
+ return order.getId();
193
+ }
194
+ }
195
+ ```
196
+
197
+ ## Messaging with Kafka
198
+
199
+ ### Kafka Event Publisher Implementation
200
+
201
+ ```java
202
+ @Component
203
+ @RequiredArgsConstructor
204
+ @Slf4j
205
+ public class KafkaDomainEventPublisher implements DomainEventPublisher {
206
+
207
+ private final KafkaTemplate<String, DomainEvent> kafkaTemplate;
208
+ private final TopicResolver topicResolver;
209
+
210
+ @Override
211
+ public void publish(DomainEvent event) {
212
+ String topic = topicResolver.resolve(event);
213
+ String key = event.getAggregateId();
214
+
215
+ kafkaTemplate.send(topic, key, event)
216
+ .whenComplete((result, ex) -> {
217
+ if (ex != null) {
218
+ log.error("Failed to publish event {} to topic {}",
219
+ event.getClass().getSimpleName(), topic, ex);
220
+ } else {
221
+ log.debug("Published event {} to topic {} partition {}",
222
+ event.getClass().getSimpleName(), topic,
223
+ result.getRecordMetadata().partition());
224
+ }
225
+ });
226
+ }
227
+
228
+ @Override
229
+ public void publishAll(List<DomainEvent> events) {
230
+ events.forEach(this::publish);
231
+ }
232
+ }
233
+ ```
234
+
235
+ ### Topic Naming Convention
236
+
237
+ Format: `{domain}.{aggregate}.{event-type}`
238
+
239
+ ```java
240
+ @Component
241
+ public class TopicResolver {
242
+
243
+ public String resolve(DomainEvent event) {
244
+ String aggregateType = event.getAggregateType().toLowerCase();
245
+ String eventType = extractEventType(event.getClass().getSimpleName());
246
+ return String.format("%s.%s.%s", "orders", aggregateType, eventType);
247
+ }
248
+
249
+ private String extractEventType(String className) {
250
+ // OrderCreatedEvent -> created
251
+ return className
252
+ .replace("Event", "")
253
+ .replaceAll("([a-z])([A-Z])", "$1-$2")
254
+ .toLowerCase()
255
+ .substring(className.indexOf(className.replaceAll("^[A-Z][a-z]+", "").substring(0, 1)));
256
+ }
257
+ }
258
+ ```
259
+
260
+ Example topics:
261
+ - `orders.order.created`
262
+ - `orders.order.confirmed`
263
+ - `orders.order.shipped`
264
+ - `payments.payment.received`
265
+ - `inventory.stock.reserved`
266
+
267
+ ### Kafka Configuration
268
+
269
+ ```java
270
+ @Configuration
271
+ public class KafkaProducerConfig {
272
+
273
+ @Bean
274
+ public ProducerFactory<String, DomainEvent> producerFactory(KafkaProperties kafkaProperties) {
275
+ Map<String, Object> props = new HashMap<>(kafkaProperties.buildProducerProperties());
276
+ props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
277
+ props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
278
+ props.put(ProducerConfig.ACKS_CONFIG, "all");
279
+ props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
280
+ props.put(ProducerConfig.RETRIES_CONFIG, 3);
281
+ return new DefaultKafkaProducerFactory<>(props);
282
+ }
283
+
284
+ @Bean
285
+ public KafkaTemplate<String, DomainEvent> kafkaTemplate(
286
+ ProducerFactory<String, DomainEvent> producerFactory) {
287
+ KafkaTemplate<String, DomainEvent> template = new KafkaTemplate<>(producerFactory);
288
+ template.setObservationEnabled(true); // Enable tracing
289
+ return template;
290
+ }
291
+ }
292
+ ```
293
+
294
+ ### Application Properties
295
+
296
+ ```yaml
297
+ spring:
298
+ kafka:
299
+ bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092}
300
+ producer:
301
+ acks: all
302
+ retries: 3
303
+ properties:
304
+ enable.idempotence: true
305
+ max.in.flight.requests.per.connection: 5
306
+ consumer:
307
+ group-id: ${spring.application.name}
308
+ auto-offset-reset: earliest
309
+ enable-auto-commit: false
310
+ properties:
311
+ isolation.level: read_committed
312
+ ```
313
+
314
+ ## Event Consumers
315
+
316
+ ### Kafka Listener
317
+
318
+ ```java
319
+ @Component
320
+ @RequiredArgsConstructor
321
+ @Slf4j
322
+ public class OrderEventListener {
323
+
324
+ private final NotificationService notificationService;
325
+ private final InventoryService inventoryService;
326
+
327
+ @KafkaListener(
328
+ topics = "orders.order.created",
329
+ groupId = "notification-service"
330
+ )
331
+ public void onOrderCreated(
332
+ @Payload OrderCreatedEvent event,
333
+ @Header(KafkaHeaders.RECEIVED_KEY) String key,
334
+ Acknowledgment ack) {
335
+
336
+ log.info("Received OrderCreatedEvent for order {}", event.getAggregateId());
337
+
338
+ try {
339
+ // Process the event
340
+ notificationService.sendOrderConfirmation(event);
341
+
342
+ // Acknowledge successful processing
343
+ ack.acknowledge();
344
+ } catch (Exception e) {
345
+ log.error("Failed to process OrderCreatedEvent", e);
346
+ // Don't acknowledge - message will be redelivered
347
+ throw e;
348
+ }
349
+ }
350
+
351
+ @KafkaListener(
352
+ topics = "orders.order.confirmed",
353
+ groupId = "inventory-service"
354
+ )
355
+ public void onOrderConfirmed(OrderConfirmedEvent event, Acknowledgment ack) {
356
+ log.info("Order {} confirmed, reserving inventory", event.getAggregateId());
357
+ inventoryService.reserveInventory(event.getAggregateId());
358
+ ack.acknowledge();
359
+ }
360
+ }
361
+ ```
362
+
363
+ ### Consumer Configuration
364
+
365
+ ```java
366
+ @Configuration
367
+ @EnableKafka
368
+ public class KafkaConsumerConfig {
369
+
370
+ @Bean
371
+ public ConsumerFactory<String, DomainEvent> consumerFactory(KafkaProperties kafkaProperties) {
372
+ Map<String, Object> props = new HashMap<>(kafkaProperties.buildConsumerProperties());
373
+ props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
374
+ props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
375
+ props.put(JsonDeserializer.TRUSTED_PACKAGES, "com.example.domain.event");
376
+
377
+ return new DefaultKafkaConsumerFactory<>(
378
+ props,
379
+ new StringDeserializer(),
380
+ new JsonDeserializer<>(DomainEvent.class)
381
+ );
382
+ }
383
+
384
+ @Bean
385
+ public ConcurrentKafkaListenerContainerFactory<String, DomainEvent> kafkaListenerContainerFactory(
386
+ ConsumerFactory<String, DomainEvent> consumerFactory) {
387
+
388
+ ConcurrentKafkaListenerContainerFactory<String, DomainEvent> factory =
389
+ new ConcurrentKafkaListenerContainerFactory<>();
390
+
391
+ factory.setConsumerFactory(consumerFactory);
392
+ factory.getContainerProperties().setAckMode(AckMode.MANUAL);
393
+ factory.getContainerProperties().setObservationEnabled(true);
394
+
395
+ return factory;
396
+ }
397
+ }
398
+ ```
399
+
400
+ ## Transactional Outbox Pattern
401
+
402
+ For guaranteed event delivery, use the Transactional Outbox pattern:
403
+
404
+ ### Outbox Entity
405
+
406
+ ```java
407
+ @Entity
408
+ @Table(name = "outbox_events")
409
+ public class OutboxEvent {
410
+ @Id
411
+ private String id;
412
+
413
+ @Column(nullable = false)
414
+ private String aggregateType;
415
+
416
+ @Column(nullable = false)
417
+ private String aggregateId;
418
+
419
+ @Column(nullable = false)
420
+ private String eventType;
421
+
422
+ @Column(columnDefinition = "TEXT", nullable = false)
423
+ private String payload;
424
+
425
+ @Column(nullable = false)
426
+ private Instant createdAt;
427
+
428
+ @Column
429
+ private Instant processedAt;
430
+
431
+ // Factory method
432
+ public static OutboxEvent from(DomainEvent event, ObjectMapper objectMapper) {
433
+ OutboxEvent outbox = new OutboxEvent();
434
+ outbox.id = event.getEventId();
435
+ outbox.aggregateType = event.getAggregateType();
436
+ outbox.aggregateId = event.getAggregateId();
437
+ outbox.eventType = event.getClass().getSimpleName();
438
+ outbox.payload = objectMapper.writeValueAsString(event);
439
+ outbox.createdAt = Instant.now();
440
+ return outbox;
441
+ }
442
+ }
443
+ ```
444
+
445
+ ### Outbox Publisher
446
+
447
+ ```java
448
+ @Component
449
+ @RequiredArgsConstructor
450
+ public class OutboxEventPublisher implements DomainEventPublisher {
451
+
452
+ private final OutboxRepository outboxRepository;
453
+ private final ObjectMapper objectMapper;
454
+
455
+ @Override
456
+ @Transactional
457
+ public void publish(DomainEvent event) {
458
+ OutboxEvent outboxEvent = OutboxEvent.from(event, objectMapper);
459
+ outboxRepository.save(outboxEvent);
460
+ }
461
+ }
462
+ ```
463
+
464
+ ### Outbox Processor (Scheduled)
465
+
466
+ ```java
467
+ @Component
468
+ @RequiredArgsConstructor
469
+ @Slf4j
470
+ public class OutboxProcessor {
471
+
472
+ private final OutboxRepository outboxRepository;
473
+ private final KafkaTemplate<String, String> kafkaTemplate;
474
+ private final TopicResolver topicResolver;
475
+
476
+ @Scheduled(fixedDelayString = "${outbox.processing.interval:1000}")
477
+ @Transactional
478
+ public void processOutbox() {
479
+ List<OutboxEvent> events = outboxRepository.findUnprocessed(100);
480
+
481
+ for (OutboxEvent event : events) {
482
+ try {
483
+ String topic = topicResolver.resolve(event.getAggregateType(), event.getEventType());
484
+ kafkaTemplate.send(topic, event.getAggregateId(), event.getPayload()).get();
485
+ event.markAsProcessed();
486
+ outboxRepository.save(event);
487
+ } catch (Exception e) {
488
+ log.error("Failed to process outbox event {}", event.getId(), e);
489
+ }
490
+ }
491
+ }
492
+ }
493
+ ```
494
+
495
+ ## Best Practices
496
+
497
+ 1. **Events are immutable**: Never modify event data after creation
498
+ 2. **Events are facts**: Name them in past tense (OrderCreated, not CreateOrder)
499
+ 3. **Include enough context**: Events should be self-contained for consumers
500
+ 4. **Version your events**: Plan for schema evolution
501
+ 5. **Use idempotent consumers**: Handle duplicate deliveries gracefully
502
+ 6. **Consider ordering**: Use partition keys for ordered delivery when needed
503
+ 7. **Monitor event processing**: Track lag, errors, and processing time
504
+ 8. **Handle failures gracefully**: Implement retry and dead letter queues
505
+ 9. **Test event flows**: Include integration tests for event producers and consumers
506
+ 10. **Document event schemas**: Maintain an event catalog for discovery
507
+
508
+ ## Event Schema Evolution
509
+
510
+ When events need to change:
511
+
512
+ ```java
513
+ // Version 1
514
+ public class OrderCreatedEventV1 extends DomainEvent {
515
+ private String customerId;
516
+ private BigDecimal total;
517
+ }
518
+
519
+ // Version 2 - Add new field with default
520
+ public class OrderCreatedEventV2 extends DomainEvent {
521
+ private String customerId;
522
+ private BigDecimal total;
523
+ private String currency = "USD"; // New field with default
524
+ }
525
+ ```
526
+
527
+ Use schema registry (e.g., Confluent Schema Registry) for production systems.