Evaluate broker routing SpEL expressions against the original event
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- java, kafka, spring-boot
- Domain
- backend, distributed-systems
Research direction
Start by tracing EventExternalizerSupport from determineTarget(event) and map(event) into the Kafka, AMQP, JMS and Spring Messaging transports, focusing on how RoutingTarget expressions receive their evaluation root. Verify the behavior across module-listener, JobRunr outbox and Namastack outbox modes; done means dynamic target and key expressions use the original event while the mapped payload and existing static/header behavior remain unchanged.
Written by the indexing model from the issue text.
Description
Problem
Broker routing SpEL expressions are currently evaluated against the mapped transport payload instead of the original domain event.
EventExternalizerSupport first determines the unresolved RoutingTarget from the original event and then applies the configured mapping:
var target = configuration.determineTarget(event);
var mapped = configuration.map(event);
return externalize(mapped, target);
The broker transport receives only mapped and target. It therefore uses the mapped payload as the root object when it resolves dynamic target and key expressions. Kafka currently does the equivalent of:
var routing = BrokerRouting.of(target, context);
builder
.setHeaderIfAbsent(KafkaHeaders.KEY, routing.getKey(payload))
.setHeaderIfAbsent(KafkaHeaders.TOPIC, routing.getTarget(payload));
At this point, payload is the result of configuration.map(event).
The same pattern is used by the AMQP, JMS and Spring Messaging transports. This behavior is independent of the selected externalization mode and also occurs in regular module-listener mode.
Example
@Externalized("#{'orders-' + getTenant()}::#{getOrderId()}")
record OrderCompleted(String tenant, UUID orderId) {
public String getTenant() {
return tenant;
}
public UUID getOrderId() {
return orderId;
}
}
record OrderCompletedMessage(byte[] data) {}
@Bean
EventExternalizationConfiguration externalization() {
return EventExternalizationConfiguration.externalizing()
.select(EventExternalizationConfiguration.annotatedAsExternalized())
.mapping(OrderCompleted.class, this::toMessage)
.build();
}
The expressions declared on OrderCompleted refer to getTenant() and getOrderId(). Externalization fails because those expressions are evaluated against OrderCompletedMessage, which does not expose either method.
The failure occurs with direct module-listener externalization as well as with JobRunr or Namastack outbox delivery.
Expected behavior
Target and key expressions declared for a domain event should use that original domain event as their evaluation root. Mapping should control the payload sent to the broker, but should not change the object used to resolve the event's routing metadata.
For the example above, the expected result is:
- target:
orders-<tenant>resolved fromOrderCompleted; - key: the original event's
orderId; - broker payload:
OrderCompletedMessage.
Static routing values and explicitly supplied message headers should retain their existing behavior.
Design consideration
The transport currently receives only the mapped payload and the unresolved RoutingTarget, so it cannot evaluate an expression against the original event. The externalization pipeline needs to preserve the original event as routing context or resolve the routing information before handing the mapped payload to the transport.
The solution should apply consistently to Kafka, AMQP, JMS and Spring Messaging and should not depend on thread-local state.
Acceptance criteria
- Dynamic target expressions are evaluated against the original event.
- Dynamic key expressions are evaluated against the original event.
- The mapped payload is still sent or serialized by the broker transport.
- Behavior is identical in module-listener, JobRunr outbox and Namastack outbox modes.
- Kafka, AMQP, JMS and Spring Messaging use the same routing semantics.
- Mapping to a type that does not expose properties referenced by the event's routing expressions succeeds.
- Existing static routing and message-header behavior remains unchanged.
- Dominant language
- Java
- Stars
- 1.2k
- Forks
- 222
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from spring-projects/spring-modulith
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
spring-projects/spring-modulith#1895 ·
-
in: infrastructure type: dependency-upgrade
spring-projects/spring-modulith#1889 · 1 assignee ·
-
in: infrastructure type: dependency-upgrade
spring-projects/spring-modulith#1888 · 1 assignee ·
-
in: infrastructure type: dependency-upgrade
spring-projects/spring-modulith#1887 · 1 assignee ·
-
Release 2.2 M2 Openin: infrastructure type: task
spring-projects/spring-modulith#1882 · 1 assignee ·
All issues in spring-projects/spring-modulith
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
bug needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
Difficulty 1/5 Under an hour Newbie friendliness 94/100
objectionary/hone-maven-plugin#1061 ·
-
Fix before the next release
Difficulty 2/5 1-3 hours Newbie friendliness 78/100