Bluetape4k JaVers Part 2: Exposed, Redis, and Kafka Persistence

Part 1 covered JaVers commits, snapshots, and diffs. Part 2 asks an operational question: where should those snapshots live?
bluetape4k-javers provides Exposed JDBC, Redis, and Kafka repositories. They should not be chosen
as interchangeable storage engines. Exposed is durable SQL history, Redis is a fast snapshot read
path, and Kafka is a write-only event stream.

In prose, the map says this: a JaVers commit is one audit boundary, but the operational destination depends on the role. If people will query history, use a durable store. If screens read latest snapshots often, add a fast read store. If downstream consumers need audit events, emit a stream. Expecting one repository to do all three jobs usually makes the design blurry.
Exposed JDBC
Section titled “Exposed JDBC”ExposedCdoSnapshotRepository stores commit metadata and CDO snapshots through Exposed tables.
It creates CommitTable and CdoSnapshotTable, then stores global id, commit id, version, type,
encoded state, changed properties, and managed type.
val snapshotRepository = ExposedCdoSnapshotRepository(database)snapshotRepository.ensureSchema()
val javers = JaversBuilder.javers() .registerJaversRepository(snapshotRepository) .registerEntity(Order::class.java) .build()This is the natural choice when audit history should live with SQL operations, backups, and transaction boundaries.
The Redis module provides Lettuce and Redisson implementations. The Lettuce repository stores
snapshots newest-first in Redis LIST keys and maintains global id and sequence indexes in HASH
keys. The Redisson repository uses RListMultimap for snapshots and RMap for commit sequences.
Both default to LZ4 + Fory encoding.
val repo = LettuceCdoSnapshotRepository("order", redisClient)val javers = JaversBuilder.javers() .registerJaversRepository(repo) .build()Redis works best as a fast snapshot read path. If durable history is elsewhere and readers need short history or latest-state lookup, Redis is a good fit.
KafkaCdoSnapshotRepository is intentionally write-only. saveSnapshot publishes the encoded
snapshot to Kafka with the global id as the key. Read methods return empty, false, or zero, and the
first read-path call logs a warning.
val repo = KafkaCdoSnapshotRepository(kafkaTemplate)val javers = JaversBuilder.javers() .registerJaversRepository(repo) .build()
javers.commit("system", order)That contract matters. Kafka is useful for downstream consumers, projections, and audit pipelines. It is not the repository that answers “give me the latest snapshot for this object.” Pair it with Exposed or Redis when query reads are required.
Combining Persistence Roles
Section titled “Combining Persistence Roles”Production systems often need a combination rather than a single adapter. Exposed + Kafka is the obvious shape: keep queryable durable history in SQL and publish the same snapshot event to downstream consumers. Exposed + Redis is another common shape: SQL keeps durable history, while Redis serves hot latest/history reads.

Today bluetape4k-javers does not provide a first-class CompositeCdoSnapshotRepository that
writes to Exposed and also fans out to Kafka. The design is feasible: delegate reads to a primary
repository such as Exposed or Redis, then fan out writes to secondary repositories such as Kafka.
The hard part is making the failure policy explicit. A Kafka publish failure might fail the whole
commit, become a best-effort secondary write, or move into a retry path.
So the article should keep current and planned behavior separate. Exposed, Redis, and Kafka are available today as individual adapters. Treat Exposed + Kafka as a planned capability until the composite repository is implemented.
Selection Table
Section titled “Selection Table”| Criterion | Exposed JDBC | Redis | Kafka |
|---|---|---|---|
| Main role | Durable snapshot history | Cache-friendly snapshot read | Snapshot event stream |
| Reads | JaVers queries | Fast latest/history reads | Empty/false/zero |
| Writes | Exposed transaction path | LIST/multimap plus indexes | Kafka publish acknowledgement |
| Recovery | Database backup/restore | Redis persistence or rebuild policy | Consumer replay/projection policy |
| Best fit | Audit history with SQL operations | Read-heavy history/projection | Downstream audit/event pipeline |
The easiest mistake is to use Kafka alone because it also says persistence. In this repository, Kafka is a stream adapter. It deliberately does not pretend to be a query store.
Source Links
Section titled “Source Links”- Repository: bluetape4k-javers
- Exposed repository:
ExposedCdoSnapshotRepository.kt - Lettuce repository:
LettuceCdoSnapshotRepository.kt - Redisson repository:
RedissonCdoSnapshotRepository.kt - Kafka repository:
KafkaCdoSnapshotRepository.kt
Closing
Section titled “Closing”Persistence choice in bluetape4k-javers is about role. Exposed keeps durable history near SQL,
Redis serves fast snapshot reads, and Kafka streams snapshot events to other consumers. Choose the
adapter by the question the application needs to answer.
Comments
Leave a note or reaction with your GitHub account.