Versioning & Migration
Grove supports versioned actions and events with upcast declarations that transform old versions to new ones. This enables backward-compatible evolution of your domain model without breaking existing data.
Prerequisites: Build a Task Tracker, 5-Minute Tutorial. What you'll learn: How to version actions and events, write upcast transformations, and maintain backward compatibility.
Why Versioning?
Grove stores events permanently. When you change an event's shape -- adding fields, renaming fields, changing types -- you need a way to read old events with the new code. Grove solves this with explicit version tags and upcast declarations.
Version Tags
Actions and events can carry version tags. When no version is specified, v1 is implied.
// These are equivalent
action create { ... }
action create v1 { ... }
// This is a new version
action create v2 {
title: String
priority: Int // new field in v2
apply {
emit created v2 {
title: params.title
priority: params.priority
}
}
}
Both versions can coexist in the same module. The runtime uses the version specified in the incoming request.
Upcast Declarations
An upcast transforms an older version's parameters or event data into the newer version's shape:
// Original action
action create v1 {
title: String
apply {
emit created v1 { title: params.title }
}
}
// New version with priority field
action create v2 {
title: String
priority: Int = 1
apply {
emit created v2 {
title: params.title
priority: params.priority
}
}
}
// Transform v1 requests into v2 shape
upcast action create v1 -> v2 {
return {
title: params.title,
priority: 1
}
}
When a v1 request arrives, the runtime:
- Runs the upcast to transform the params to v2 shape
- Executes the v2 action logic
- Emits v2 events
Event Upcasts
Events stored in the database as v1 can be replayed through an upcast:
event created v1 {
title: String
}
event created v2 {
title: String
priority: Int
}
upcast event created v1 -> v2 {
return {
title: event.title,
priority: 1
}
}
When replaying history, v1 events are automatically transformed to v2 before being applied to the record.
Upcast Rules
- The source version must be lower than the target version.
- Upcast body uses
params(for action upcasts) orevent(for event upcasts) as the root object. - The
returnstatement must produce an object matching the target version's field shape. - Upcasts can chain: v1 -> v2 -> v3. The runtime applies them in sequence.
metais available in upcast blocks for access to request metadata.
Version Chains
For multiple version upgrades, you can write individual upcasts for each step:
action create v1 { title: String ... }
action create v2 { title: String, priority: Int ... }
action create v3 { title: String, priority: Int, tags: List<String> ... }
upcast action create v1 -> v2 {
return { title: params.title, priority: 1 }
}
upcast action create v2 -> v3 {
return { title: params.title, priority: params.priority, tags: [] }
}
A v1 request flows through: v1 -> v2 -> v3, applying each upcast in order.
State Machine Versioning
When events reference state machine transitions, versioned events must still name the event that the state machine expects:
record {
kind "todo"
status: TodoStatus = TodoStatus.open
state status {
TodoStatus.open -> TodoStatus.in_progress on started
TodoStatus.open -> TodoStatus.in_progress on started v2
}
}
The state machine can reference specific event versions in its transition declarations.
Best Practices
- Always add new versions -- don't modify existing versions. Old events in the store depend on the original shape.
- Provide sensible defaults -- upcasts should fill new fields with reasonable defaults, not arbitrary values.
- Test both versions -- write tests that exercise both the old version (with upcast) and the new version directly.
- Keep upcasts simple -- they should be pure data transformations, not business logic.
- Use explicit version tags -- even though
v1is implicit, making it explicit improves readability when multiple versions exist.
See Also
- Schema Migration -- Database-level migration strategies
- Semantic Constraints -- Upcast validation rules
- Build a Task Tracker -- Basic module tutorial