Skip to content
SametGoktepe edited this page Jun 17, 2026 · 1 revision

Common questions about eventferry — what it is, what it isn't, how it compares to alternatives, and recurring "should I…" judgment calls.


What problem does eventferry solve?

The dual-write problem. You want to change business state in a database and emit an event about that change. Doing both naively (commit; publish or publish; commit) has a real failure mode: one side succeeds, the other doesn't, and your system becomes inconsistent.

eventferry implements the transactional outbox pattern: the event row commits with the business change inside the same transaction, and a separate process publishes from the outbox table to the broker. Either both rows commit, or neither does. The broker eventually hears about every committed change exactly once (idempotent producer) or exactly-once with transactional EOS.


When should I use eventferry?

You're a good fit if any of these are true:

  • You're publishing domain events to Kafka / Redpanda from a service backed by Postgres / MySQL.
  • You're already running into the dual-write problem (lost events on crash, duplicate events on retry).
  • You want strict per-aggregate ordering without sharding or partition assignment glue.
  • You want type-safe payloads without dragging Confluent Schema Registry into a same-monorepo setup.
  • You want a publisher you own — no Connect cluster, no Debezium, no separate operational surface.

You're NOT a good fit if:

  • Your sink is something other than Kafka / Redpanda (no MQ-style publishers shipped today).
  • You're at Debezium scale (millions of events / second per topic) — Debezium + Kafka Connect is the right answer at that volume.
  • You want consumer-side framework features (consumer groups, exactly-once consume) — eventferry is publisher-only.

How is this different from Debezium?

Debezium reads the database's binlog/WAL with no application coupling — your code doesn't know an event was emitted. eventferry's enqueue is an explicit call in your business handler — you choose what becomes an event and shape its payload.

eventferry Debezium
Coupling to your code Explicit enqueue call None (reads the binlog)
Payload shape Whatever you write Whatever the row looks like
Schema evolution Your code controls Tied to table schema
Operational surface Your app process Kafka Connect cluster
Scaling unit Run more relays Run more Connect tasks
Schema Registry Optional Required-ish

Use Debezium when you want CDC of an existing schema unchanged. Use eventferry when you want to design domain events as a first-class concept.


Does eventferry work with Prisma / Drizzle / TypeORM / Kysely?

Yes — the store's enqueue takes any object that exposes a query method (Postgres) or execute (MySQL). All four ORMs let you grab the underlying pg.Client / mysql2 connection mid-transaction:

// Prisma
await prisma.$transaction(async (tx) => {
  await tx.order.create({ data: { ... } });
  await store.enqueue(
    (tx as any).$queryRawUnsafe.bind(tx),    // grab the raw client
    { topic: "orders.created", ... },
  );
});

This is loosely typed but works. See Postgres Adapter for the canonical pattern (raw pg client).

A typed Prisma / Drizzle / TypeORM adapter package is on the roadmap — open an issue with your stack if you want it sooner.


Why not just publish to Kafka after db.commit()?

That's the dual-write problem in one sentence. Commit succeeds, your process dies before the publish, broker never hears about the change. Or publish succeeds, commit fails on a constraint violation, broker hears about a change that didn't happen.

The whole point of the outbox pattern is to make the event durable inside the same transaction as the business change so this failure mode is impossible.


Does eventferry support exactly-once delivery?

eventferry's default is at-least-once to the broker: every committed change reaches Kafka, possibly with duplicates on retry. Combined with idempotent producer (default on), duplicates are deduplicated on the wire, so the broker only commits each record once per partition.

For end-to-end exactly-once (producer-broker-consumer), opt into the transactional producer (transactional: true, transactionalId: "...") and configure consumers with isolation.level=read_committed. See Transactions and EOS.

The outbox layer dedups on retry too — once a row is done, it stays done. So a relay restart that re-claims a row mid-publish doesn't produce a duplicate at the outbox layer.


Can I have multiple outbox tables in the same database?

Yes. Pass any table name to createMigrationSql and PostgresStore({ table }):

const ordersStore = new PostgresStore({ pool, table: "outbox_orders" });
const usersStore = new PostgresStore({ pool, table: "outbox_users" });

Each gets its own relay, its own publisher, its own DLQ topics. Common pattern for high-volume systems → see Operations Guide.


Should I run the relay in the same process as my API, or separately?

Either works. In-process is simpler — your service starts, the relay starts, lifecycle is shared. Separate process isolates the publish load and lets you scale relays independently.

Rule of thumb:

  • Single-replica service → in-process.
  • Multi-replica service → in-process; every replica runs a relay, SKIP LOCKED partitions the work.
  • Massive publish load (> 1000 events/sec) → separate relay deployment, fewer replicas, larger batches.

Why so many @eventferry/* packages?

To match what you actually use. If you're on Postgres + kafkajs, you don't need @eventferry/mysql's schema, @confluentinc/kafka-javascript's native build, or @eventferry/kafka-iam's AWS SDK chain. Optional peer dependencies + independent SemVer let you install only what you reach for.

For prototyping, use @eventferry/all — it pulls everything in one shot. Move to individual packages when your install footprint matters.


Does eventferry work with Aurora / Cloud SQL / RDS / MSK / Confluent Cloud?

Yes to all. Each has its config quirks:

  • Aurora / Cloud SQL — set wal_level=logical via the parameter group if you want PostgresStreamingRelay.
  • RDS MySQL / Aurora MySQL — enable binlog via the parameter group for MysqlBinlogRelay.
  • MSK — IAM auth via AWS MSK IAM. Topic auto-create is OFF by default; use validateTopicsOnConnect or Terraform-provisioned topics.
  • Confluent Cloud — SASL/PLAIN with the cluster API key. Schemas registered via the Cloud UI or Confluent CLI; use autoRegister: false in production.

Is the schema versioned?

The eventferry-emitted outbox table schema is versioned with the package itself. Migrations and Upgrades lists schema diffs per minor.

Your event payload schemas (the things in defineOutbox(registry)) are versioned by you — eventferry doesn't enforce schema evolution rules. For runtime-enforced compatibility, use Schema Registry → Schema Registry.


Why is the table called outbox by default?

Tradition. The outbox pattern's name predates eventferry — outbox is the conventional table name in literature (Pat Helland's "Life Beyond Distributed Transactions", Chris Richardson's Microservices Patterns, etc.). You can name it whatever you want — pass { table: "your_name" } everywhere.


Where's the CHANGELOG?

Each package ships its own CHANGELOG.md inside the npm tarball:

npm view @eventferry/kafka@latest dist
# look at CHANGELOG.md inside the extracted tarball
# or:
npm view @eventferry/kafka changelog

For the source-of-truth, the .changeset/ directory in the repo accumulates pending changes; merging the Version Packages PR cuts a release and rolls CHANGELOG.md per package.


Why TypeScript only? Can I use this from JS?

The packages are TypeScript-first. The compiled output is plain ES modules + CJS — JS consumers work fine, you just lose the type checking that catches schema mismatches at compile time.

Cross-language consumers (Go, Java, Python) are the explicit use case for Schema Registry → Schema Registry.


Are there examples I can copy?

The integration test suite (packages/integration/test/) is the canonical example. Every supported configuration is exercised against real Postgres / MySQL / Redpanda / Schema Registry containers — clone the repo, read the tests, lift what fits.

git clone https://github.com/SametGoktepe/eventferry
cd eventferry
pnpm install
pnpm test:integration                     # requires Docker

The setup harness at packages/integration/test/setup/containers.ts shows the canonical container config (wal_level=logical, binlog flags, MSK-style brokers, etc.).

Clone this wiki locally