Skip to content

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

3D workbench illustration of robotic engineers sorting JaVers snapshots into Exposed, Redis, and Kafka stations
The word persistence is shared. The read, write, and recovery roles are not.

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.

Persistence role map from a JaVers commit to Exposed, Redis, and Kafka
Choose by repository role, not by module name.

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.

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.

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.

Diagram showing current Exposed, Redis, and Kafka adapters and the planned composite repository direction
The current adapters are role-specific. Exposed + Kafka fan-out is planned as a future composite repository.

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.

CriterionExposed JDBCRedisKafka
Main roleDurable snapshot historyCache-friendly snapshot readSnapshot event stream
ReadsJaVers queriesFast latest/history readsEmpty/false/zero
WritesExposed transaction pathLIST/multimap plus indexesKafka publish acknowledgement
RecoveryDatabase backup/restoreRedis persistence or rebuild policyConsumer replay/projection policy
Best fitAudit history with SQL operationsRead-heavy history/projectionDownstream 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.

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.