Bluetape4k JaVers Part 1: Audit and Diff Overview

This is Part 1 of the bluetape4k-javers series. Part 1 sets up the audit and diff model, Part 2
compares Exposed, Redis, and Kafka persistence roles, and Part 3 maps the workshop and DDD/CQRS
examples onto a practical command flow.
The easiest way to add audit history is to create one history table per entity. That works for a while. Then fields grow, nested objects appear, and readers start asking who changed what and why. At that point the audit code tends to repeat itself across every entity.
bluetape4k-javers solves that problem with JaVers’ object audit model. JaVers commits an object,
stores snapshots of its state, and computes diffs between objects or versions. This repository adds
Kotlin/JVM-friendly helpers, codecs, persistence adapters, and DDD examples around that model.
It helps to understand JaVers itself before looking at the bluetape4k adapters. JaVers is not tied to JPA revision tables in the way Hibernate Envers is. It is an object auditing and diff library: the application commits a domain object, JaVers inspects the object graph, records changed properties, and stores versioned snapshots. That makes it useful for Kotlin/JVM services that want to explain how a domain object changed, not merely which database row was updated.
| Feature | What it gives you |
|---|---|
| Object auditing | Commit object state as versioned snapshots |
| Object diff | Compute property-level differences between objects or versions |
| JQL query | Query history by instance, class, commit, or version range |
This is also different from CDC. Debezium-style CDC turns database log changes into a stream. JaVers starts from the application object and commit metadata. Use CDC when every database change must be streamed outward. Use JaVers when readers need object history and human-readable diffs.

JaVers Mental Model
Section titled “JaVers Mental Model”| Concept | Meaning |
|---|---|
| Commit | A change unit with author, commit id, and commit properties |
| CDO Snapshot | The state of one object at one version |
| GlobalId | JaVers identity such as Order/1 |
| Diff | Property-level changes between objects or snapshots |
| Shadow | An object reconstructed from snapshot history |
A JaVers commit is more than a log line. It carries object state, changed properties, and commit metadata together.
val product = Product(1L, "Widget", BigDecimal("9.99"), "Tools")
javers.commit("alice", product)
val snapshots = javers.findSnapshots( QueryBuilder.byInstanceId(1L, Product::class.java).build())That gives the application one audit boundary instead of a separate hand-written history path for every entity.
What bluetape4k Adds
Section titled “What bluetape4k Adds”bluetape4k-javers does not rewrite JaVers. It keeps the JaVers model and adds the pieces that
Kotlin/JVM services in the bluetape4k stack keep needing.
| Module | Responsibility |
|---|---|
javers-core | Extensions, codecs, and repository base classes |
javers-exposed | Exposed JDBC CDO snapshot repository |
javers-persistence-redis | Lettuce and Redisson Redis snapshot repositories |
javers-persistence-kafka | Write-only Kafka snapshot stream repository |
javers-ddd | Aggregate, domain event, repository, and publisher helpers |
examples/javers-exposed-ddd | Exposed + JaVers + event projection example |
The shared base is AbstractCdoSnapshotRepository. It handles snapshot encode/decode, commit head
state, sequence handling, and query filtering. Concrete repositories implement storage-specific
operations such as saveSnapshot, loadSnapshots, and getKeys.
abstract class AbstractCdoSnapshotRepository<T : Any>( protected val codec: JaversCodec<T>,) : CdoSnapshotRepository { override fun persist(commit: Commit?) { // commit snapshots -> repository-specific saveSnapshot(...) }}The Small Workshop Example
Section titled “The Small Workshop Example”The workshop ProductAuditService shows the smallest useful shape. It commits the product to
JaVers and upserts the current row through Exposed.

fun save(author: String, product: Product) { javers.commit(author, product) transaction { ProductTable.upsert { it[id] = product.id it[name] = product.name it[price] = product.price it[category] = product.category } }}Diffs can be computed without persisting either side:
val diff = javers.compare( oldProduct, oldProduct.copy(price = BigDecimal("7.50")),)The tests check INITIAL, UPDATE, and TERMINAL snapshots, latest snapshot lookup, and
property-level value changes.
When It Fits
Section titled “When It Fits”JaVers fits best when object-level history matters. If a business flow needs to show changed properties, author metadata, latest snapshots, and reconstructed object state, JaVers gives that model directly. If all the service needs is a simple append-only event row, a small audit table may still be enough.
Source Links
Section titled “Source Links”- Repository: bluetape4k-javers
- README: README.md
- Korean README: README.ko.md
- Core repository base:
AbstractCdoSnapshotRepository.kt - Workshop audit service:
ProductAuditService.kt
Closing
Section titled “Closing”Audit history is useful only when it can be read and explained later. bluetape4k-javers brings
JaVers commits, snapshots, and diffs into the bluetape4k stack so Kotlin services can avoid
rebuilding the same audit path per entity.
Next, we choose where those snapshots should live.
Comments
Leave a note or reaction with your GitHub account.