v0.3.0
Reliable domain events: a transactional outbox on Spring Modulith's event publication registry, delivery to Kafka, RocketMQ, AMQP and JMS, event contracts, and idempotent consumers. The repository contract now reports optimistic-lock conflicts.
Added
@IntegrationEventincom.ddk.core.domainmarks domain events for delivery outside the process and names their target and message key, without framework annotations in the domain layerddk-event-starterhands@IntegrationEventevents to Spring Modulith's event externalization when it is on the classpath, so they are recorded in the business transaction and delivered after commit; the BOM manages Spring Modulith 2.1@IntegrationEvent(id = ...)puts the event's own identifier into theddk-event-idmessage header, so it stays stable across resubmissions@IntegrationEvent(type, version)and theddk-event-type/ddk-event-versionheaders give each integration event a stable contract name and version- The event starter verifies
@IntegrationEventdeclarations at startup (accessors, version, duplicate contracts) instead of failing at delivery time - The event starter delivers
@IntegrationEventevents to RocketMQ whenrocketmq-clientis on the classpath and aDefaultMQProducerexists orddk.event.rocketmq.name-serveris set:topic:tagtargets, per-key queue ordering, contract headers as user properties; the BOM manages the client version IdempotentConsumer(ddk.event.inbox.enabled=true) runs a handler once per consumer and message ID, registering the message in the handler's transaction behind a savepointConcurrentUpdateException(CONCURRENT_UPDATE), which the web starter maps to 409 ConflictIdentifierJacksonModulewrites typed identifiers such asUserIdas their raw value and reads them back through the subclass'sof(...)factory; the web and event starters register it. Without it Jackson wrote identifiers as{}, so a serialized domain event lost its IDsGenericRepositoryImplaccepts typed identifiers such asUserIdand unwraps them to key values; the example and archetype skills useGenericRepository<User, UserId>
Fixed
GenericRepositoryImpl.updatesilently ignored an optimistic-lock conflict or a removed row and still published the aggregate's domain events; it now throwsConcurrentUpdateExceptionand publishes nothingupdateAllchecks each row's version instead of a batch update that cannot report per-row conflicts