68 lines
3.6 KiB
Markdown
68 lines
3.6 KiB
Markdown
# ADR-JPA-006 — `audit` is the canonical technical audit model; `auditing` stays a frozen candidate
|
|
|
|
- Status: Accepted
|
|
- Date: 2026-08-24
|
|
- Review: `docs/reviews/2026-08-14-jpa-module-code-review.md` JPA-022
|
|
|
|
## Context
|
|
|
|
Two complete technical-audit mechanisms live in this leaf and they disagree about the schema.
|
|
|
|
`audit/AuditableEntity` stamps `created_at`/`created_by`/`updated_at`/`updated_by` with an actor
|
|
column of length 256, captured through explicit `initializeAudit`/`applyModification` calls and an
|
|
`AuditContextPort`. `auditing/AuditMetadata` is a Spring Data embeddable that stamps
|
|
`created_*`/`modified_*` with an actor column of length 64, captured by `@CreatedDate` and friends
|
|
through an `AuditorAware`.
|
|
|
|
Only the first is real: it is what the sample entities extend and what the migrations were written
|
|
for. `JpaAuditingConfiguration` is not a Spring `@Configuration`, and nothing in production
|
|
constructs any of the three `auditing` types.
|
|
|
|
The review asked for one canonical model with a migration or activation decision. The failure mode
|
|
it was protecting against is specific: an author of a new entity picks whichever package they find
|
|
first, and column names, actor lengths and capture lifecycles then diverge per table.
|
|
|
|
## Decision
|
|
|
|
`audit/AuditableEntity` is canonical. `auditing` stays in the tree as a candidate and is excluded
|
|
from the Stable capability report.
|
|
|
|
The candidate is not deleted and not promoted. Deleting it would discard a working Spring Data
|
|
integration that a deployment preferring declarative auditing would want. Promoting it would mean
|
|
either renaming `modified_*` to `updated_*` and widening the actor column — a schema migration of
|
|
every audited table to gain nothing a caller asked for — or moving the sample entities onto
|
|
`modified_*`, which is the same migration in the other direction.
|
|
|
|
Neither is worth doing now. What the divergence actually needed was not consolidation but a rule
|
|
that an entity cannot straddle the two, and that rule is cheaper than either migration.
|
|
|
|
## Consequences
|
|
|
|
Two audit mechanisms remain readable in one leaf, and a reader has to be told which one is live.
|
|
That cost is paid in this document, in the package javadoc and in a test whose name says so.
|
|
|
|
Two failure modes stay silent unless they are asserted, so both are:
|
|
|
|
- The candidate acquires a stereotype and starts stamping in every deployment that has this module
|
|
on the classpath, including the ones whose tables have no `modified_*` columns — where the result
|
|
is a failed startup rather than a feature.
|
|
- Somebody "harmonises" the two by editing one side's column names, at which point the schema a
|
|
deployed table was migrated for and the schema its entity expects diverge with no migration
|
|
between them.
|
|
|
|
If the candidate is ever promoted, it is promoted atomically: forward migration, sample conversion,
|
|
`AuditContextPort → AuditorAware` and `Clock → DateTimeProvider` bridges land together, and this
|
|
ADR is superseded rather than amended.
|
|
|
|
Bulk and native updates stamp nothing under either mechanism. That is a property of JPA, not of the
|
|
choice made here, so it is enforced separately rather than assumed away.
|
|
|
|
## Enforcement
|
|
|
|
`JpaAuditMechanismRule.entitiesUseExactlyOneAuditMechanism` and
|
|
`bulkUpdatesOfAuditedEntitiesStampAudit`, run against the real production graph by
|
|
`JpaProductionArchitectureTest` at the composition root — not against fixtures, which is how the
|
|
earlier version of this rule pack passed while applying to nothing. `AuditingCandidateStatusTest`
|
|
asserts the candidate carries no composing stereotype and that the two column sets stay distinct.
|
|
`JpaAuditMechanismRuleTest` exercises the rules' own negative cases.
|