Services
Digital Platform ModernizationSoon
Intelligent Automation & AISoon
Decentralized & Trust SystemsSoon
Dedicated Technology TeamsSoon
Capabilities
Enterprise Platform EngineeringStrangler-Fig MigrationMulti-Tenant SaaS TopologyEvent-Driven ArchitectureZero-Trust Infrastructure & Cloud IAMFederated API GatewaysE-commerce & Marketplace
Cloud DevOps & ScalabilitySoon
Intelligent Automation & AISoon
Blockchain & Trust SystemsSoon
Dedicated Engineering TeamsSoon
Industries
Healthcare & Life Sciences
Financial Services & FinTech
Supply Chain & Logistics
Education & eLearning
Retail & eCommerce
Oil & Gas
Case StudiesOur ThinkingAboutContactStart a POC
Architecture pattern · Distributed systems

Fault-tolerant event-driven architecture for enterprise scale.

Design resilient event streams with transactional outbox consistency, idempotent consumers, schema contract governance and dead-letter handling, so services stay decoupled without losing or double-applying events.

Design targets · not measured results from past client work

Target · Message Reliability99.999%

Replicated durable logs and at-least-once delivery, with idempotent consumers, designed to avoid lost or double-applied events.

Target · Event Latency<5ms

Producer publish to consumer acknowledgement, for a well-sized cluster under normal load.

Target · Events Per Second10M+

Horizontally scaled Kafka clusters designed for sustained traffic bursts.

Target · Cascading Timeouts0

Asynchronous decoupling removes synchronous dependency chains, the usual source of timeout cascades.

The short answer

What is event-driven architecture?

Event-driven architecture is a design in which services communicate by publishing and reacting to events, meaning records that something happened, such as an order being paid. A producer publishes an event, a broker or router delivers it, and any number of consumers react in their own time. Because services only know the event, not each other, they can scale, fail and be deployed independently. The price is new work: handling duplicates, ordering, schema changes and failures that no longer show up as a simple error response.

Producers

Services that publish events when state changes, without waiting for anyone to react.

Broker or router

Kafka, RabbitMQ or a managed bus that stores or routes events and delivers them to interested consumers.

Consumers

Services that react to events, and must cope with duplicates, retries, ordering limits and changing schemas.

Robral provides event-driven architecture consulting and engineering for enterprise platforms: topic and broker design, transactional outbox, idempotent consumers, schema governance and the failure tests that prove it, delivered first as a 14-day proof of concept.
Core engineering pillars

Three pillars. One fault-tolerant messaging backbone.

Each pillar is independently deployable. Applied together they are designed to address the most common failure modes in distributed event systems.

01
Topology & Throughput

Event Broker & Topic Design

Kafka and RabbitMQ cluster topologies with partition strategies that preserve ordering where it matters, replication sized for durability targets, and in-sync replica settings that let the cluster tolerate broker failures without losing acknowledged writes.

  • Topic partition layout aligned to consumer group concurrency
  • In-sync replica settings with min.insync.replicas enforced
  • Retention policies per topic class (compacted vs time-bounded)
  • Schema-aware producer serialization with Avro / Protobuf
  • Rolling Kafka upgrades planned to avoid consumer downtime
02
Contract Governance

Schema Registry & Contracts

Confluent Schema Registry with compatibility checks enforced in CI. Breaking changes are blocked before they reach producers, which prevents silent data corruption across service boundaries. Schemas are versioned, discoverable and used automatically by consumer deserialization.

  • Confluent Schema Registry with compatibility enforcement
  • Breaking-change detection in CI / pre-deploy gates
  • Schema IDs carried with every message
  • Avro and Protobuf schema generation from domain models
  • Consumer migration tooling for backward-incompatible schemas
03
Audit & Replay

Event Sourcing & CQRS

An immutable event store as the system of record. State is derived by replaying ordered events rather than mutating rows. Independent CQRS projections per read use case keep reads fast without touching the write path, and replay supports audit trails and point-in-time state.

  • Immutable event store — state derived from event replay
  • Independent CQRS projections per read query pattern
  • Audit trail with point-in-time state hydration
  • Snapshots for efficient aggregate reconstruction at scale
  • Re-hydrate new microservices from historical event streams
Delivery guarantees

At-most-once, at-least-once or effectively-once: which do you need?

Every messaging design chooses what happens when something fails between send and processing. Knowing the trade-off is what keeps events from being lost or applied twice.

GuaranteeWhat can go wrongHow it is achievedBest forWhat you must build
At-most-onceEvents can be lost, never duplicated.Fire-and-forget sends, or commit the consumer position before processing.Metrics, logs and other data where a gap is acceptable.Nothing special, but accept the loss.
At-least-onceEvents are never lost but can arrive more than once.Acknowledged writes (acks=all), retries, and committing the consumer position after processing. This is the Kafka default.Most business events: orders, payments, inventory.Idempotent consumers, so a repeated event has no extra effect.
Effectively-onceDuplicates may still be delivered, but their effects are applied once.At-least-once delivery plus idempotent producers, an outbox, and inbox or key-based deduplication. Kafka transactions cover Kafka-to-Kafka processing.Financial and stock changes where double-applying is costly.Deduplication keys, transactional boundaries and tests that replay and crash.

Kafka documents at-most-once, at-least-once and exactly-once, with idempotent producers and transactions inside Kafka. Effects on external systems, such as a database or a payment call, still need idempotent handling. See the references below.

Platform capabilities

Every messaging primitive, engineered for production.

From dead-letter queues and idempotency to CDC pipelines and lag monitoring, the full event delivery stack.

Dead-Letter Queues & Circuit Breaking

Retries with exponential backoff and jitter, poison-message isolation, and circuit breakers that stop a blocked consumer from backing up the system. Failed messages land in dead-letter topics with context metadata for controlled replay.

DLQ TopicsExponential BackoffCircuit BreakerPoison Pill Isolation

Idempotent Consumers & Deduplication

Brokers usually deliver at least once, so consumers must tolerate duplicates. Event IDs are recorded in an inbox table in the same transaction as the state change, so a retry or replay is detected and skipped.

Idempotency KeysInbox TableDeduplicationConsumer Guards

Transactional Outbox & CDC

Events are written to an outbox table in the same database transaction as the domain change. Debezium change data capture streams committed outbox rows to Kafka, which removes the dual-write problem. Delivery is at least once, so consumers stay idempotent.

Debezium CDCTransactional OutboxDual-Write EliminationPostgres / MongoDB

Consumer Lag Monitoring

Consumer group lag tracking with per-partition alert thresholds. Trends surface backpressure before it becomes latency spikes, and lag depth can drive scaling of consumer groups.

Lag AlertingPer-Partition MonitoringBackpressure DetectionAuto-Scaling
Engineering notes

The outbox and the idempotent consumer.

Two small pieces of SQL remove most of the data-consistency problems in event-driven systems.

Transactional outbox and inbox (PostgreSQL, illustrative)
-- Producer: state change and event in ONE transaction
BEGIN;
UPDATE orders SET status = 'PAID' WHERE id = :order_id;
INSERT INTO outbox (id, aggregatetype, aggregateid, type, payload)
VALUES (gen_random_uuid(), 'order', :order_id, 'OrderPaid', :payload);
COMMIT;   -- Debezium streams committed outbox rows to Kafka

-- Consumer: skip events already handled
BEGIN;
INSERT INTO inbox (event_id) VALUES (:event_id)
ON CONFLICT (event_id) DO NOTHING;
-- 0 rows inserted: duplicate, roll back and acknowledge
-- 1 row inserted: apply the change in this same transaction
COMMIT;

Outbox rows are only ever inserted. Delivery is at least once, so the consumer-side inbox is what makes processing safe to repeat. The column names above follow the defaults of the Debezium outbox event router.

Failure test checklist
  • Kill the consumer after processing but before committing the offset; the event is reprocessed with no double effect.
  • Redeliver the same event twice; the state changes once.
  • Crash the producer between the database write and the publish; the outbox still delivers the event.
  • Publish events out of order for one key; the consumer handles or rejects stale versions.
  • Send a malformed event; it goes to the dead-letter topic and the consumer keeps moving.
  • Publish a breaking schema change; CI rejects it before deployment.
  • Stop a consumer for an hour; lag alerts fire, and catch-up does not overwhelm downstream systems.
  • Replay a topic into a fresh consumer; the result matches the original state.
Settings that matter

The Kafka settings behind reliable delivery.

Exact settings are where most reliability problems hide. These are the ones we check first.

Producer durability

Use acks=all with enable.idempotence=true, so acknowledged writes are replicated and retries cannot create duplicate log entries. Set these explicitly rather than relying on client defaults.

acks=allenable.idempotence=trueRetries

Replication and in-sync replicas

A replication factor of 3 with min.insync.replicas=2 lets a topic lose one broker while still accepting acknowledged writes, and refuses writes when too few replicas are in sync.

replication.factor=3min.insync.replicas=2Unclean election off

Consumer offsets

Turn off auto-commit and commit offsets after the work is done. A crash then causes a repeat, never a gap, which is why the consumer must be idempotent.

enable.auto.commit=falseCommit after processingIdempotent handlers

Transactional reads

When producers use Kafka transactions, consumers should read with isolation.level=read_committed so they never see messages from aborted transactions.

isolation.level=read_committedTransactional producers

Schema compatibility

Agree a compatibility mode per topic and enforce it in CI. Confluent Schema Registry defaults to backward compatibility; long-lived topics with replay often need stricter, transitive modes.

BACKWARDFULL_TRANSITIVECI gate

Retries and dead letters

Separate retry topics with increasing delay, a capped attempt count, and a dead-letter topic that keeps the original payload, error and attempt history for replay after a fix.

Retry topicsBackoff + jitterDLQ with headers
What you receive

An event streaming topology. In 14 days.

01

Event Streaming Topology Blueprint

Kafka or RabbitMQ cluster design, topic partition strategy, consumer group topology and retention policy configuration tailored to your throughput targets.

02

Schema Governance Runbook

Confluent Schema Registry setup, Avro/Protobuf schema definitions for your domain events, compatibility gate configuration and CI enforcement hooks.

03

Phased Migration Roadmap

Prioritized sequence for moving existing synchronous API calls to event-driven patterns, with risk assessment, rollback strategies and consumer cutover playbooks.

Delivered in 14 days: a topology blueprint, a schema governance runbook and a phased migration roadmap.
Choosing a partner

How to choose an event-driven architecture partner.

Six questions to ask any firm, including us, before you commit.

01

Delivery guarantees

Ask which guarantee they design for and how duplicates are handled. Look for at-least-once delivery with idempotent consumers, and an honest view of what exactly-once does and does not cover.

Red flag: "exactly-once, no duplicates ever"
02

Consistency at the database boundary

Ask how a state change and its event stay in step. Look for a transactional outbox with change data capture, or an equivalent, not two separate writes.

Red flag: write to the database, then publish
03

Failure handling

Ask what happens to a message that keeps failing. Look for retry topics, dead-letter queues, replay tooling and alerts.

Red flag: infinite retries on the main topic
04

Schema governance

Ask how a producer team ships a schema change safely. Look for a registry, compatibility checks in CI and a consumer migration path.

Red flag: schemas agreed in a document
05

Operations

Ask how lag, throughput and broker health are watched, and who is on call. Look for per-partition metrics, alerts and runbooks.

Red flag: no lag monitoring
06

Ownership and handover

Ask what you own at the end. Look for code, infrastructure as code, runbooks and training handed over, with acceptance criteria agreed up front.

Red flag: black-box delivery
Questions and answers

Event-driven architecture: common questions.

What is event-driven architecture?

It is a design where services communicate through events, records that something changed. Producers publish events, a broker or router delivers them, and consumers react independently. AWS describes it as using events to trigger and communicate between decoupled services. The benefit is loose coupling and independent scaling; the cost is handling duplicates, ordering, schema changes and asynchronous failures.

Does Kafka guarantee exactly-once delivery?

Kafka supports exactly-once processing inside Kafka, using idempotent producers and transactions, and consumers can read with isolation.level=read_committed. Its default delivery is at least once. When a consumer writes to an external system such as a database or a payment API, the effect is only applied once if that write is idempotent or part of the same transaction.

What is the transactional outbox pattern?

A service writes the event to an outbox table in the same database transaction as its state change, so either both happen or neither does. A separate process, often Debezium change data capture, reads committed outbox rows and publishes them to the broker. This avoids the dual-write problem. Delivery is at least once, so consumers must handle duplicates.

What is an idempotent consumer?

It is a consumer that produces the same result no matter how many times it receives the same event. A common way is an inbox table: record the event ID in the same transaction as the state change, and skip the event if the ID already exists. Debezium outbox messages carry an event ID header that consumers can use for this.

When should I use Kafka and when RabbitMQ?

Kafka is built around a replicated, replayable log, which suits high-throughput streams, event sourcing and many independent consumers reading the same history. RabbitMQ is a message broker with flexible routing and acknowledgment, which suits task queues and request-style workflows. Many platforms use both. The choice depends on throughput, replay needs and routing patterns.

How do you handle schema changes without breaking consumers?

Register schemas in a registry such as Confluent Schema Registry, choose a compatibility mode per topic, and check every change in CI before deployment. Add optional fields rather than renaming or removing them, and migrate consumers before producers when a breaking change is unavoidable.

What do you receive from a 14-day proof of concept?

A streaming topology blueprint (brokers, topics, partitions, retention), a schema governance runbook with CI hooks, and a phased roadmap for moving your synchronous calls to events, including rollback and consumer cutover. It is meant to show the design working on one of your flows, not to replace the full build.

Eliminate synchronous bottlenecks

Ready to eliminate synchronous bottlenecks?

Collaborate with principal platform architects to design a fault-tolerant event streaming strategy tailored to your domain model.