Store the original domain event in the Namastack outbox

Open Beginner friendly
#1,895 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
java, spring-boot

Research direction

Start with NamastackOutboxEventRecorder and trace the existing outbox handler that restores records for externalization. Verify that the recorder evaluates the key and schedules the original payload, while mapping occurs during delivery and the mapped payload reaches the configured broker without changing module-listener behavior.

Written by the indexing model from the issue text.

Description

Problem

NamastackOutboxEventRecorder applies the configured externalization mapping before it schedules an event in the outbox:

var target = configuration.determineTarget(payload);
var mapped = configuration.map(payload);
var routing = BrokerRouting.of(target, context);
var key = routing.getKey(mapped);

scheduleToOutbox(mapped, key);

This makes the Namastack outbox behave differently from the regular module-listener externalization flow. The outbox contains the mapped transport payload instead of the original domain event.

This causes several related problems:

  • The mapped payload has to be serializable by the outbox serializer. A mapping to an Avro-generated type, for example, can fail although the original domain event is Jackson-friendly.
  • Mapping and serialization failures occur while the business transaction is recorded instead of during outbox delivery. They therefore cannot be handled and reported as outbox delivery failures, such as through failure_reason.
  • The outbox handler receives an already mapped object and passes it to the externalizer. Selection and mapping are then applied to that object again, which can skip the event or attempt to map it twice.
  • The key stored with the outbox record is evaluated against the mapped payload rather than the original event.

This was originally reported in namastack/namastack-outbox#466.

Example

@Externalized("orders::#{getOrderId()}")
record OrderCompleted(UUID orderId) {

	public UUID getOrderId() {
		return orderId;
	}
}

@Bean
EventExternalizationConfiguration externalization() {
	return EventExternalizationConfiguration.externalizing()
			.select(EventExternalizationConfiguration.annotatedAsExternalized())
			.mapping(OrderCompleted.class, this::toAvro)
			.build();
}

Publishing OrderCompleted currently causes toAvro(…) to be invoked by the recorder. The resulting Avro object is serialized into the Namastack outbox and later handed back to the externalizer.

Expected behavior

The recorder should:

  1. determine whether the original event is externalized;
  2. determine the outbox record key from the original event;
  3. schedule the original event in the Namastack outbox without applying the externalization mapping.

When Namastack delivers the record, the existing outbox handler should pass the restored domain event to the externalizer. The normal externalization pipeline can then select, map and publish it exactly once.

This gives module-listener and Namastack outbox mode the same mapping lifecycle while keeping mapping and transport serialization failures within outbox processing.

Suggested change

Change NamastackOutboxEventRecorder to evaluate the outbox key against and schedule the original payload:

var target = configuration.determineTarget(payload);
var routing = BrokerRouting.of(target, context);
var key = routing.getKey(payload);

scheduleToOutbox(payload, key);

No change to the common externalizer or broker transports is required.

Acceptance criteria

  • The Namastack outbox stores the original domain event.
  • The externalization mapper is not invoked while the outbox record is created.
  • The outbox record key is evaluated against the original event.
  • The mapper is invoked during delivery by the outbox handler.
  • The mapped payload is sent to the configured broker.
  • Existing module-listener behavior remains unchanged.
Dominant language
Java
Stars
1.2k
Forks
222
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from spring-projects/spring-modulith

All issues in spring-projects/spring-modulith

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.