Skip to main content

ADR 015: Asynchronous Outbox for Config Adapter Synchronization

Date: 2026-01-15 Status: Proposed Decision Makers: @luckey @cr0ssing @jonasfrz

Context

The Portal Backend manages e.g. users, groups, and roles and synchronizes these changes to external systems (e.g. Keycloak) via the Config Adapter.

Current flow:

  1. A CRUD request is received by the Portal Backend
  2. The entity is saved in the database
  3. A ConfigEvent is published to Kafka
  4. The Portal Backend waits for a ConfigResultEvent from the ConfigAdapter
  5. The database transaction stays open while waiting
  6. On success, the external ID is stored and the transaction is committed
  7. On failure or timeout, the transaction is rolled back

This flow guarantees strong consistency but causes:

  • Long-running transactions
  • Tight runtime coupling to Kafka and the Config Adapter
  • User facing errors when external systems are slow or unavailable

Checked Architecture Principles

  • [full] Model-centric data flow – Events stored alongside domain changes ensure data consistency
  • [full] Distributed architecture with unified user experience – Async pattern enables better service independence
  • [full] Modular design – Clear separation between Portal Backend and Config Adapter
  • [full] Integration capability through defined interfaces – Uses CloudEvents standard via Kafka
  • [full] Open source as the default – Pattern based on open-source best practices
  • [full] Cloud-native architecture – Outbox pattern is well-suited for distributed cloud environments
  • [full] Prefer standard solutions over custom development – Transactional Outbox is a proven pattern
  • [full] Self-contained deployment – Each service manages its own outbox independently
  • [full] Technological consistency to ensure maintainability – Consistent event-driven architecture
  • [full] Multi-tenancy – Sync state tracked per entity enables tenant isolation
  • [full] Security by design – Transactional guarantees prevent data loss and inconsistencies

Decision

We replace the synchronous request–reply flow with an asynchronous outbox-based pattern.

The Portal Backend no longer waits for the Config Adapter. Changes are committed immediately and synchronized asynchronously.

What Changes

1. Asynchronous Processing

  • CRUD operations return after database commit
  • No blocking while waiting for Kafka or Config Adapter responses

2. Transactional Outbox

  • Entity change and outgoing event are stored in the same transaction
  • Events are published to Kafka after commit
  • Event delivery is reliable

3. Explicit Sync State

Each synchronized entity maintains an explicit synchronization state that reflects its progress and outcome when applying changes to external systems:

NOT_SYNCED        – change persisted locally, synchronization pending
SYNCED – change successfully applied externally
FAILED_RETRIABLE – synchronization failed and will be retried
FAILED_PERMANENT – synchronization failed and requires intervention

The synchronization state represents the authoritative view of external consistency for each entity and is persisted alongside the domain data.


4. Result Handling

  • The Config Adapter publishes result events after processing configuration changes
  • Result events are consumed asynchronously by the Portal Backend
  • Synchronization state is updated based on the reported outcome
  • Temporary failures transition the entity into a retriable failure state
  • Persistent failures are marked explicitly and handled operationally
  • The original database change is never rolled back

New Flow (Simplified)

API request
→ save entity (NOT_SYNCED)
→ save outbox event
→ commit
→ response returned

Outbox publisher
→ publish ConfigEvent

Config Adapter
→ apply change
→ publish result

Result listener
→ update sync state
(SYNCED / FAILED_RETRIABLE / FAILED_PERMANENT)

Consequences

  • API requests complete immediately after local persistence, resulting in faster and more predictable response times.
  • Database transactions remain short-lived and are no longer coupled to external systems.
  • The Portal Backend becomes resilient to temporary outages or slowdowns of Kafka or the Config Adapter.
  • Synchronization with external systems becomes eventually consistent by design.

Failures during synchronization are handled explicitly:

  • Temporary failures are retried automatically until synchronization succeeds or a defined limit is reached.
  • Persistently failing changes are marked accordingly and require operational attention.
  • Failed synchronizations do not roll back the original change

This approach improves system stability and observability but introduces the need for operational processes to monitor and resolve failed synchronizations.

Alternatives

  • Keep synchronous validation → rejected (scalability and reliability)

Migration Notes

  • Add sync state column
  • Introduce outbox table and publisher
  • Remove synchronous waiting and rollbacks
  • Keep existing Kafka topics and event formats

See Also