Summary
A production audit log failure occurred where Javers failed to record granular property changes for nested objects. Instead of capturing specific field updates (e.g., product.code), the system only reported a change in the identity (ID) of the referenced entity. This resulted in a complete loss of visibility into the actual state changes of downstream domain models when updated through a parent entity.
Root Cause
The issue stems from a fundamental misunderstanding of how Javers handles the distinction between Entities and Value Objects during the diffing process:
- Entity Identity vs. Value State: By registering
Productas anEntity(via theEntityabstract class), Javers treats the object as a unique, independent identity. - Reference-based Diffing: When
Foocontains a reference toProduct, and both are registered as Entities, Javers performs a reference comparison. - Identity Mismatch: If the
idof theProductinsideFoochanges, Javers concludes that the oldProductwas replaced by a completely newProductentity. - Loss of Granularity: Because the engine sees a substitution of identity rather than a mutation of state, it stops traversing the object tree to look for property changes, reporting only the change from
id: 1toid: 3.
Why This Happens in Real Systems
In complex distributed systems, we often use a Unified Domain Model where most objects extend a base Entity class to ensure database compatibility. This leads to two systemic risks:
- Over-registration: Developers often apply the same auditing rules to all objects to ensure “everything is covered,” inadvertently turning complex object graphs into a web of strictly-identity-bound nodes.
- Abstraction Leaks: The technical requirement to audit an object independently (as an Entity) conflicts with the functional requirement to see its evolution when embedded within a parent (as a Value Object).
Real-World Impact
- Audit Compliance Failure: Security audits fail because the system cannot prove what changed, only that something was replaced.
- Debugging Blindness: When a production bug is caused by a specific field change, engineers cannot use the audit logs to reconstruct the erroneous state.
- Data Integrity Gaps: Troubleshooting “how a value reached this state” becomes impossible if the audit trail only shows identity swaps.
Example or Code (if necessary and relevant)
// The problematic configuration
Javers javers = JaversBuilder.javers()
.registerEntity(EntityDefinitionBuilder.entityDefinition(Entity.class)
.withIdPropertyName("id")
.build())
.build();
// The resulting incorrect JSON change log
{
"changeType": "PROPERTY_VALUE_CHANGED",
"propertyName": "product",
"left": { "typeName": "Product", "cdoId": 1 },
"right": { "typeName": "Product", "cdoId": 3 }
}
How Senior Engineers Fix It
A senior engineer approaches this by decoupling Identity from Auditability. To fix this while still allowing Product to be audited independently, we use one of the following strategies:
- Manual Shadowing/Value Object Mapping: If
Productis frequently embedded, define aProductValueversion of the class (or a projection) registered as a Value Object for the purpose of theFooaudit. - Custom Javers Shadowing: Implement a
JaversComparatoror use@DiffIgnoreon the ID field within specific contexts to force the engine to look deeper. - Explicit Audit Traversal: Instead of relying on the parent-child diff, ensure that every change to
Productis committed via its ownjavers.commit(product)call. This ensures theProducttimeline is populated regardless of whether it was updated viaFooor directly. - Refined Registration: Use
registerValuefor nested components that do not require identity-based tracking within the context of the parent, even if they are technically entities in the database.
Why Juniors Miss It
- Focus on “What” vs “How”: Juniors focus on the fact that the code runs without errors and the ID change is visible, assuming the audit is “working.”
- Identity Over-reliance: They tend to treat the
Entitystatus as a “magic flag” that makes everything auditable, without realizing that Identity is the enemy of deep-diffing. - Lack of Graph Mental Model: They view objects as isolated units rather than nodes in a directed graph where the link type (Reference vs. Value) dictates the behavior of the auditing engine.