Repository navigation
feat(event): consume integration events through declared consumers - #78
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
The event starter covered publishing: one
@IntegrationEventannotation and the event is recorded in the business transaction and delivered after commit. Consuming was left to the application. Connecting two contexts inddk-malltook about 250 lines of consumer plumbing: creating and closing RocketMQ consumers, routing by topic and tag, decoding the payload, reading the event ID, retrying on failure, and an in-process bridge to run without a broker. Every DDK project that consumes events would write the same code.Writing the test for it also showed that a documented guarantee was wrong, see the last section.
Changes
IntegrationEventConsumer<T>: a consumer declares its group, its source (topicortopic:tag, the form@IntegrationEventuses) and its payload type, and handles aReceivedEvent<T>with the payload and the contract headers.IntegrationEventDispatcherroutes a received message to the matching consumers and decodes the payload. Withidempotent()it wraps the consumer in the inbox. It is public, so a listener for another broker can call it.ddk.event.rocketmq.consumer.orderly).ddk.event.local-delivery.enabled, off by default) for development and tests without a broker: after commit the event is serialized and handed to the same dispatcher on a single thread.flowchart LR MQ[RocketMQ<br/>one consumer per group] --> D L[In-process delivery<br/>after commit, single thread] --> D X[Your Kafka / AMQP / JMS listener] --> D D[IntegrationEventDispatcher<br/>match group + source, decode JSON, read ddk-event-* headers] D -->|idempotent| I[IdempotentConsumer<br/>same transaction as handle] I --> C[IntegrationEventConsumer.handle] D --> CIn-process delivery is a plain listener plus a transaction synchronization, not a
@TransactionalEventListener. A transactional listener forObjectwould make Spring Modulith write every event of the application into its publication registry.A corrected guarantee
The README said that events with the same key keep their order. The end-to-end test published three events of one aggregate in three transactions and received them in reverse order. A key puts events into the same queue; delivery after commit runs on a thread pool, so events committed close together can be sent out of order.
spring.task.execution.pool.core-size=1, which the test now uses and which passed three runs in a row), and that consumers should otherwise be independent of order.背景
事件 starter 之前只管发布:加一个
@IntegrationEvent注解,事件就会在业务事务里登记、提交后投递。消费完全留给应用自己做。在ddk-mall里把两个上下文连起来时,消费端的样板代码写了大约 250 行:创建和关闭 RocketMQ 消费者、按主题和标签分发、解析消息体、读取事件标识、失败重试,还有一个没有中间件时用的进程内转发。每个要消费事件的 DDK 项目都会把这些再写一遍。为它写测试时还发现文档里的一条保证是错的,见最后一节。
改动
IntegrationEventConsumer<T>:消费方声明消费组、来源(topic或topic:tag,与@IntegrationEvent的写法相同)和消息体类型,处理的是带消息体和契约头的ReceivedEvent<T>。IntegrationEventDispatcher把收到的消息分发给匹配的消费方并解析消息体;消费方声明idempotent()时用去重表包住它。它是公开的,其他中间件的监听器可以直接调用。ddk.event.rocketmq.consumer.orderly)。ddk.event.local-delivery.enabled,默认关闭):用于没有中间件的本地开发和测试,事务提交后把事件序列化,再用单线程交给同一个分发器。各部分的关系见上文流程图。进程内转发用的是普通监听器加事务同步,而不是
@TransactionalEventListener:监听Object的事务监听器会让 Spring Modulith 把应用里的每一个事件都写进发布记录。一条被更正的保证
README 原来说同一个 key 的事件保持顺序。端到端测试在三个事务里发布了同一个聚合的三个事件,收到时顺序是反的。key 只保证事件进入同一个队列;提交后的投递由线程池执行,相邻提交的事件可能乱序发出。
spring.task.execution.pool.core-size=1,测试现在就是这样配置的,连续三次通过),以及否则消费方应当不依赖顺序。