Schema Evolution
Apache Fory™ supports schema evolution in Compatible mode, allowing serialization and deserialization peers to have different type definitions.
Compatible Mode
Enable schema evolution with compatible(true):
use fory::Fory;
use fory::ForyObject;
use std::collections::HashMap;
#[derive(ForyObject, Debug)]
struct PersonV1 {
name: String,
age: i32,
address: String,
}
#[derive(ForyObject, Debug)]
struct PersonV2 {
name: String,
age: i32,
// address removed
// phone added
phone: Option<String>,
metadata: HashMap<String, String>,
}
let mut fory1 = Fory::default().compatible(true);
fory1.register::<PersonV1>(1);
let mut fory2 = Fory::default().compatible(true);
fory2.register::<PersonV2>(1);
let person_v1 = PersonV1 {
name: "Alice".to_string(),
age: 30,
address: "123 Main St".to_string(),
};
// Serialize with V1
let bytes = fory1.serialize(&person_v1);
// Deserialize with V2 - missing fields get default values
let person_v2: PersonV2 = fory2.deserialize(&bytes)?;
assert_eq!(person_v2.name, "Alice");
assert_eq!(person_v2.age, 30);
assert_eq!(person_v2.phone, None);
Schema Evolution Features
- Add new fields with default values
- Remove obsolete fields (skipped during deserialization)
- Change field nullability (
T↔Option<T>) - Reorder fields (matched by name, not position)
- Type-safe fallback to default values for missing fields
Compatibility Rules
- Field names must match (case-sensitive)
- Type changes are not supported (except nullable/non-nullable)
- Nested struct types must be registered on both sides
Enum Support
Apache Fory™ supports three types of enum variants with full schema evolution in Compatible mode:
Variant Types:
- Unit: C-style enums (
Status::Active) - Unnamed: Tuple-like variants (
Message::Pair(String, i32)) - Named: Struct-like variants (
Event::Click { x: i32, y: i32 })
use fory::{Fory, ForyObject};
#[derive(Default, ForyObject, Debug, PartialEq)]
enum Value {
#[default]
Null,
Bool(bool),
Number(f64),
Text(String),
Object { name: String, value: i32 },
}
let mut fory = Fory::default();
fory.register::<Value>(1)?;
let value = Value::Object { name: "score".to_string(), value: 100 };
let bytes = fory.serialize(&value)?;
let decoded: Value = fory.deserialize(&bytes)?;
assert_eq!(value, decoded);
Enum Schema Evolution
Compatible mode enables robust schema evolution with variant type encoding (2 bits):
0b0= Unit,0b1= Unnamed,0b10= Named
use fory::{Fory, ForyObject};
// Old version
#[derive(ForyObject)]
enum OldEvent {
Click { x: i32, y: i32 },
Scroll { delta: f64 },
}
// New version - added field and variant
#[derive(Default, ForyObject)]
enum NewEvent {
#[default]
Unknown,
Click { x: i32, y: i32, timestamp: u64 }, // Added field
Scroll { delta: f64 },
KeyPress(String), // New variant
}
let mut fory = Fory::builder().compatible().build();
// Serialize with old schema
let old_bytes = fory.serialize(&OldEvent::Click { x: 100, y: 200 })?;
// Deserialize with new schema - timestamp gets default value (0)
let new_event: NewEvent = fory.deserialize(&old_bytes)?;
assert!(matches!(new_event, NewEvent::Click { x: 100, y: 200, timestamp: 0 }));
Evolution capabilities:
- Unknown variants → Falls back to default variant
- Named variant fields → Add/remove fields (missing fields use defaults)
- Unnamed variant elements → Add/remove elements (extras skipped, missing use defaults)
- Variant type mismatches → Automatically uses default value for current variant
Best practices:
- Always mark a default variant with
#[default] - Named variants provide better evolution than unnamed
- Use compatible mode for cross-version communication
Tuple Support
Apache Fory™ supports tuples up to 22 elements out of the box with efficient serialization in both compatible and non-compatible modes.
Features:
- Automatic serialization for tuples from 1 to 22 elements
- Heterogeneous type support (each element can be a different type)
- Schema evolution in Compatible mode (handles missing/extra elements)
Serialization modes:
- Non-compatible mode: Serializes elements sequentially without collection headers for minimal overhead
- Compatible mode: Uses collection protocol with type metadata for schema evolution
use fory::{Fory, Error};
let mut fory = Fory::default();
// Tuple with heterogeneous types
let data: (i32, String, bool, Vec<i32>) = (
42,
"hello".to_string(),
true,
vec![1, 2, 3],
);
let bytes = fory.serialize(&data)?;
let decoded: (i32, String, bool, Vec<i32>) = fory.deserialize(&bytes)?;
assert_eq!(data, decoded);
Related Topics
- Configuration - Enabling compatible mode
- Polymorphism - Trait objects with schema evolution
- Cross-Language - Schema evolution across languages