Class VersionedCodec
The frame is three bytes: two magic bytes (0xC0 0xDC), then an unsigned version in
1..255, then the bytes the codec registered at that version produced.
Codec<Person> codec = VersionedCodec.<Person>builder()
.version(1, jacksonFactory.create(Person.class))
.version(2, foryFactory.create(Person.class).andThen(new ZstdCodec()))
.writing(2)
.build();
The write version is named explicitly rather than inferred from the highest registration,
because that is what makes a two-phase rollout possible: deploy with the new version registered
but still .writing(1), let that reach every instance, then flip to .writing(2) in
a second deploy. No instance is ever handed data it cannot read.
Ordering matters. Anything layered outside a versioned codec must be undone before the header can be read, so it is frozen for the life of the store:
// header outermost: backend, transforms, everything inside may differ per version
Codec<Person> versioned = VersionedCodec.<Person>builder()
.version(1, jackson.create(Person.class).andThen(new GzipCodec()))
.version(2, fory.create(Person.class).andThen(new ZstdCodec()))
.writing(2)
.build();
// header now inside gzip: gzip must be undone to read it, so gzip can never change
Codec<Person> frozen = versioned.andThen(new GzipCodec());
Put whatever you might want to change inside the versioned codec; put only permanent commitments outside it.
Because the builder is generic over Codec, versioned byte transforms are simply the
T = byte[] case — a compression or key-wrapping layer that can be swapped under an
unchanged value codec.
Built codecs are immutable and thread-safe whenever their delegates are, which the Codec contract already requires. Builders are not thread-safe.
Failures. decode throws VersionedFormatException (an InvalidPayloadException) when the buffer is shorter than the three-byte
header or the magic does not match — bytes some other codec wrote — and UnknownVersionException (an UnsupportedFormatException, carrying the
version) when the framing is valid but names a version this codec has no registration for. The
two share no parent below CodecException, so a policy that quarantines
invalid payloads cannot accidentally discard a newer writer's output. Exceptions thrown by a
delegate codec propagate unchanged. encode throws NullPointerException on a
null value; wrap the built codec with Codec.nullSafe() to opt into passing
null straight through instead.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classCollects the per-version codecs and the version to write, and validates them. -
Method Summary
Modifier and TypeMethodDescriptionstatic <T> VersionedCodec.Builder<T> builder()Starts building a versioned codec.
-
Method Details
-
builder
Starts building a versioned codec.- Type Parameters:
T- the type the codec converts- Returns:
- a new builder
-