Repository navigation
v0.6.0
Both sides of integration events: declared consumers join the publishing side, and the trace follows an event from the request that caused it to the handler that processes it. ddk-mall, a reference application with three bounded contexts, uses every starter side by side; building it surfaced the fixes below.
Added
ddk-examples/ddk-mall: a reference application with order, inventory and payment contexts in one modular monolith. It has the order context (an aggregate with a child table, Flyway migrations, audit fields), the inventory context (reservations under per-SKU locks), their integration through the outbox and RocketMQ with idempotent consumers, and the payment context (a simulated channel, refunds when a payment crosses a cancellation) with a scheduled job that closes unpaid orders, and a read side with cached products and stock, API docs grouped by context MCP tools for a support agent, and traces and metrics exported over OTLP to a one-container observability backend, tested end to end on MySQL, Redis and RocketMQ containers- Consuming integration events in
ddk-event-starter: declare anIntegrationEventConsumerbean and DDK subscribes to RocketMQ (orderly by default), decodes the payload, exposes the contract headers asReceivedEventand can deduplicate through the inbox.ddk.event.local-delivery.enableddelivers in-process for development, andIntegrationEventDispatcherlets listeners of other brokers reuse the same handling - Integration events carry the trace:
ddk-event-starterobserves publishing and consuming (ddk.event.publish,ddk.event.consume), writes the trace context into the message headers and continues from it on the consuming side, over RocketMQ and local delivery alike.ddk-tracer-starterregisters aContextPropagatingTaskDecorator(ddk.tracer.async-propagation.enabled) so the trace reaches@Asyncmethods and the post-commit event delivery AggregateLocks.executeAll(type, ids, action)locks several aggregates of one type in a fixed order, so two operations that need the same aggregates cannot deadlock on each otherGenericRepositoryImplhas extension points for aggregates stored in a root table plus child tables:afterInsert,afterUpdate,afterLoadandbeforeRemoverun around the root table's reads and writes in the same transaction
Fixed
- Numbers in error messages were written with grouping separators: an order ID
1234567in订单不存在:{0}came out as1,234,567, which nobody can search for.ErrorCode.getMessage(args)now writes numbers as they are; a placeholder with an explicit format such as{0,number,#,##0.00}is still honoured - Trace export failed at runtime (
NoSuchMethodError: okio.Okio.socket) in applications that use RocketMQ and the tracer starter together:rocketmq-clientbrings an old okio that Maven preferred over the one the OpenTelemetry exporter's OkHttp needs. The BOM now manages okio to the version OkHttp 5 requires - The Redis starter failed the application at startup when no
RedisConnectionFactoryexisted, for example with Spring Boot's Redis auto-configuration excluded for a local profile. This also defeated the cache starter's local-only fallback. TheredisTemplatebean is now created lazily, so the application starts and only using the template without a connection fails - The event starter's documentation claimed that events with the same key keep their order. A key only puts them in the same partition or queue; delivery after commit is concurrent by default and can send them out of order. The README now explains this and how to get ordered delivery