Payloads and codecs

PayloadCodec, the Payload record, and using attributes for correlation data so a duplicate need not republish.

A duplicate can only be given the original answer back if the original answer was stored. That is what a PayloadCodec<T> is for: the library does not guess at a serialisation format, so you supply one for whatever your action returns.

The Payload

A Payload is three things:

  • type - a string saying how to read the bytes. Yours to choose; the store keeps it verbatim.
  • body - the bytes themselves.
  • attributes - flat string key-value pairs the store returns verbatim.

Here the action, handler.handle(event), returns a Handled - your own type, carrying a publicationId() - and objectMapper is your Jackson 3 ObjectMapper, the one Spring Boot 4 provides, whose exceptions are unchecked. engine and context are built as on the engine page.

PayloadCodec<Handled> codec = new PayloadCodec<>() {
    @Override
    public Payload encode(Handled handled) {
        return new Payload(
                "shipment/handled",
                objectMapper.writeValueAsBytes(handled),
                Map.of("publicationId", handled.publicationId()));
    }

    @Override
    public Handled decode(Payload payload) {
        return objectMapper.readValue(payload.body(), Handled.class);
    }
};

Outcome<Handled> outcome = engine.execute(context, () -> handler.handle(event), codec);
Java

type is worth setting honestly. It is what lets a decoder refuse a payload written by an older version of the code rather than deserialising it into something wrong.

What attributes are for

attributes are where correlation data goes - the ids of the messages the first execution published, say - so a duplicate can reference them instead of publishing again.

Storing their ids as attributes gives the duplicate something to point at. For an action that returns an OrderAccepted of your own, carrying the ids in outboxIds(), the codec’s encode becomes:

@Override
public Payload encode(OrderAccepted accepted) {
    return new Payload(
            "order/accepted",
            objectMapper.writeValueAsBytes(accepted),
            Map.of("outboxIds", String.join(",", accepted.outboxIds())));
}
Java

They are flat strings by design. An attribute map that could nest would become a second serialisation format with no schema, and the body already exists for structured data.

When you need no codec

A void annotated method has nothing to replay and must leave the annotation’s codec attribute empty - supplying one is rejected at startup. The engine’s runnable overload is the same case: it takes no codec and returns an Outcome<Void>, switched on as in outcomes.

switch (engine.execute(context, () -> handler.handle(event))) { ... }
Java

A value-returning annotated method without a codec fails at startup rather than at the first duplicate, which is the only moment the mistake would otherwise show.

Over HTTP

The HTTP adapter’s StoredResponseCodec is exactly this for HTTP responses: status, headers and body encoded into a Payload so a duplicate replays the response the first caller got. You do not write it.