OutboxRepositoryJdbc.java

package io.slayyyyer.orders.repository.jdbc;

import io.slayyyyer.orders.domain.Order;
import io.slayyyyer.orders.repository.OutboxRepository;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Repository;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;

@Repository
public class OutboxRepositoryJdbc implements OutboxRepository {

    // Spring Boot 4 auto-configures a Jackson 3 mapper (tools.jackson). The Jackson 2
    // ObjectMapper that this class used to inject only exists here as a transitive
    // dependency of springdoc, so there was no bean of that type and the application
    // context failed to start — invisible to unit tests, fatal in the integration run.
    //
    // The outbox serialises one flat map of primitives, so owning a mapper is simpler than
    // injecting the application's JSON configuration, and it removes the wiring dependency
    // entirely.
    private static final ObjectMapper JSON = JsonMapper.builder().build();

    private final JdbcClient jdbc;

    public OutboxRepositoryJdbc(JdbcClient jdbc) {
        this.jdbc = jdbc;
    }

    @Override
    public void append(Order order, String eventType) {
        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("orderId", order.id().toString());
        payload.put("customerId", order.customerId().toString());
        payload.put("status", order.status().name());
        payload.put("totalCents", order.totalCents());
        payload.put("currency", order.currency());
        payload.put("occurredAt", order.createdAt().toString());

        jdbc.sql("""
                insert into outbox_events (aggregate_id, event_type, payload)
                values (:aggregateId, :eventType, cast(:payload as jsonb))
                """)
                .param("aggregateId", order.id())
                .param("eventType", eventType)
                .param("payload", JSON.writeValueAsString(payload))
                .update();
    }
}