Files
BACKBEAT/contracts/docs/schema-evolution.md
2025-10-17 08:56:25 +11:00

507 lines
12 KiB
Markdown

# BACKBEAT Schema Evolution and Versioning
This document defines how BACKBEAT message schemas evolve over time while maintaining compatibility across the CHORUS 2.0.0 ecosystem.
## Versioning Strategy
### Semantic Versioning for Schemas
BACKBEAT schemas follow semantic versioning (SemVer) with CHORUS-specific interpretations:
- **MAJOR** (`X.0.0`): Breaking changes that require code updates
- **MINOR** (`X.Y.0`): Backward-compatible additions (new optional fields, enum values)
- **PATCH** (`X.Y.Z`): Documentation updates, constraint clarifications, examples
### Schema Identification
Each schema includes version information:
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://chorus.services/schemas/backbeat/beatframe/v1.2.0",
"title": "BACKBEAT BeatFrame (INT-A)",
"version": "1.2.0"
}
```
### Message Type Versioning
Message types embed version information:
- `backbeat.beatframe.v1` → Schema version 1.x.x
- `backbeat.beatframe.v2` → Schema version 2.x.x
Only **major** version changes require new message type identifiers.
## Compatibility Matrix
### Current Schema Versions
| Interface | Schema Version | Message Type | Status |
|-----------|----------------|--------------|--------|
| INT-A (BeatFrame) | 1.0.0 | `backbeat.beatframe.v1` | Active |
| INT-B (StatusClaim) | 1.0.0 | `backbeat.statusclaim.v1` | Active |
| INT-C (BarReport) | 1.0.0 | `backbeat.barreport.v1` | Active |
### Version Compatibility Rules
1. **Minor/Patch Updates**: All v1.x.x schemas are compatible with `backbeat.*.v1` messages
2. **Major Updates**: Require new message type (e.g., `backbeat.beatframe.v2`)
3. **Transition Period**: Both old and new versions supported during migration
4. **Deprecation**: 6-month notice before removing support for old major versions
## Change Categories
### Minor Version Changes (Backward Compatible)
These changes increment the minor version (1.0.0 → 1.1.0):
#### 1. Adding Optional Fields
```json
// Before (v1.0.0)
{
"required": ["type", "cluster_id", "beat_index"],
"properties": {
"type": {...},
"cluster_id": {...},
"beat_index": {...}
}
}
// After (v1.1.0) - adds optional field
{
"required": ["type", "cluster_id", "beat_index"],
"properties": {
"type": {...},
"cluster_id": {...},
"beat_index": {...},
"priority": {
"type": "integer",
"minimum": 1,
"maximum": 10,
"description": "Optional processing priority (1=low, 10=high)"
}
}
}
```
#### 2. Adding Enum Values
```json
// Before (v1.0.0)
{
"properties": {
"phase": {
"enum": ["plan", "execute", "review"]
}
}
}
// After (v1.1.0) - adds new phase
{
"properties": {
"phase": {
"enum": ["plan", "execute", "review", "cleanup"]
}
}
}
```
#### 3. Relaxing Constraints
```json
// Before (v1.0.0)
{
"properties": {
"notes": {
"type": "string",
"maxLength": 256
}
}
}
// After (v1.1.0) - allows longer notes
{
"properties": {
"notes": {
"type": "string",
"maxLength": 512
}
}
}
```
#### 4. Adding Properties to Objects
```json
// Before (v1.0.0)
{
"properties": {
"metadata": {
"type": "object",
"properties": {
"version": {"type": "string"}
}
}
}
}
// After (v1.1.0) - adds new metadata field
{
"properties": {
"metadata": {
"type": "object",
"properties": {
"version": {"type": "string"},
"source": {"type": "string"}
}
}
}
}
```
### Major Version Changes (Breaking)
These changes increment the major version (1.x.x → 2.0.0):
#### 1. Removing Required Fields
```json
// v1.x.x
{
"required": ["type", "cluster_id", "beat_index", "deprecated_field"]
}
// v2.0.0
{
"required": ["type", "cluster_id", "beat_index"]
}
```
#### 2. Changing Field Types
```json
// v1.x.x
{
"properties": {
"beat_index": {"type": "integer"}
}
}
// v2.0.0
{
"properties": {
"beat_index": {"type": "string"}
}
}
```
#### 3. Removing Enum Values
```json
// v1.x.x
{
"properties": {
"state": {
"enum": ["idle", "executing", "deprecated_state"]
}
}
}
// v2.0.0
{
"properties": {
"state": {
"enum": ["idle", "executing"]
}
}
}
```
#### 4. Tightening Constraints
```json
// v1.x.x
{
"properties": {
"agent_id": {
"type": "string",
"maxLength": 256
}
}
}
// v2.0.0
{
"properties": {
"agent_id": {
"type": "string",
"maxLength": 128
}
}
}
```
### Patch Version Changes (Non-Breaking)
These changes increment the patch version (1.0.0 → 1.0.1):
1. **Documentation updates**
2. **Example additions**
3. **Description clarifications**
4. **Comment additions**
## Migration Strategies
### Minor Version Migration
Services automatically benefit from minor version updates:
```go
// This code works with both v1.0.0 and v1.1.0
func handleBeatFrame(frame BeatFrame) {
// Core fields always present
log.Printf("Beat %d in phase %s", frame.BeatIndex, frame.Phase)
// New optional fields checked safely
if frame.Priority != nil {
log.Printf("Priority: %d", *frame.Priority)
}
}
```
### Major Version Migration
Requires explicit handling of both versions during transition:
```go
func handleMessage(messageBytes []byte) error {
var msgType struct {
Type string `json:"type"`
}
if err := json.Unmarshal(messageBytes, &msgType); err != nil {
return err
}
switch msgType.Type {
case "backbeat.beatframe.v1":
return handleBeatFrameV1(messageBytes)
case "backbeat.beatframe.v2":
return handleBeatFrameV2(messageBytes)
default:
return fmt.Errorf("unsupported message type: %s", msgType.Type)
}
}
```
### Gradual Migration Process
1. **Preparation Phase** (Months 1-2)
- Announce upcoming major version change
- Publish v2.0.0 schemas alongside v1.x.x
- Update documentation and examples
- Provide migration tools and guides
2. **Dual Support Phase** (Months 3-4)
- Services support both v1 and v2 message types
- New services prefer v2 messages
- Monitoring tracks v1 vs v2 usage
3. **Migration Phase** (Months 5-6)
- All services updated to send v2 messages
- Services still accept v1 for backward compatibility
- Warnings logged for v1 message reception
4. **Cleanup Phase** (Month 7+)
- Drop support for v1 messages
- Remove v1 handling code
- Update schemas to mark v1 as deprecated
## Implementation Guidelines
### Schema Development
1. **Start Conservative**: Begin with strict constraints, relax later if needed
2. **Plan for Growth**: Design extensible structures with optional metadata objects
3. **Document Thoroughly**: Include clear descriptions and examples
4. **Test Extensively**: Validate with real-world data before releasing
### Version Detection
Services should detect schema versions:
```go
type SchemaInfo struct {
Version string `json:"version"`
MessageType string `json:"message_type"`
IsSupported bool `json:"is_supported"`
}
func detectSchemaVersion(messageType string) SchemaInfo {
switch messageType {
case "backbeat.beatframe.v1":
return SchemaInfo{
Version: "1.x.x",
MessageType: messageType,
IsSupported: true,
}
case "backbeat.beatframe.v2":
return SchemaInfo{
Version: "2.x.x",
MessageType: messageType,
IsSupported: true,
}
default:
return SchemaInfo{
MessageType: messageType,
IsSupported: false,
}
}
}
```
### Validation Strategy
```go
func validateWithVersionFallback(messageBytes []byte) error {
// Try latest version first
if err := validateV2(messageBytes); err == nil {
return nil
}
// Fall back to previous version
if err := validateV1(messageBytes); err == nil {
log.Warn("Received v1 message, consider upgrading sender")
return nil
}
return fmt.Errorf("message does not match any supported schema version")
}
```
## Testing Schema Evolution
### Compatibility Tests
```go
func TestSchemaBackwardCompatibility(t *testing.T) {
// Test that v1.1.0 accepts all valid v1.0.0 messages
v100Messages := loadTestMessages("v1.0.0")
v110Schema := loadSchema("beatframe-v1.1.0.schema.json")
for _, msg := range v100Messages {
err := validateAgainstSchema(msg, v110Schema)
assert.NoError(t, err, "v1.1.0 should accept v1.0.0 messages")
}
}
func TestSchemaForwardCompatibility(t *testing.T) {
// Test that v1.0.0 code gracefully handles v1.1.0 messages
v110Message := loadTestMessage("beatframe-v1.1.0-with-new-fields.json")
var beatFrame BeatFrameV1
err := json.Unmarshal(v110Message, &beatFrame)
assert.NoError(t, err, "v1.0.0 struct should parse v1.1.0 messages")
// Core fields should be populated
assert.NotEmpty(t, beatFrame.Type)
assert.NotEmpty(t, beatFrame.ClusterID)
}
```
### Migration Tests
```go
func TestDualVersionSupport(t *testing.T) {
handler := NewMessageHandler()
v1Message := generateBeatFrameV1()
v2Message := generateBeatFrameV2()
// Both versions should be handled correctly
err1 := handler.HandleMessage(v1Message)
err2 := handler.HandleMessage(v2Message)
assert.NoError(t, err1)
assert.NoError(t, err2)
}
```
## Deprecation Process
### Marking Deprecated Features
```json
{
"properties": {
"legacy_field": {
"type": "string",
"description": "DEPRECATED: Use new_field instead. Will be removed in v2.0.0",
"deprecated": true
},
"new_field": {
"type": "string",
"description": "Replacement for legacy_field"
}
}
}
```
### Communication Timeline
1. **6 months before**: Announce deprecation in release notes
2. **3 months before**: Add deprecation warnings to schemas
3. **1 month before**: Final migration reminder
4. **Release day**: Remove deprecated features
### Tooling Support
```bash
# Check for deprecated schema usage
backbeat-validate --schemas ./schemas --dir messages/ --check-deprecated
# Migration helper
backbeat-migrate --from v1 --to v2 --dir messages/
```
## Best Practices
### For Schema Authors
1. **Communicate Early**: Announce changes well in advance
2. **Provide Tools**: Create migration utilities and documentation
3. **Monitor Usage**: Track which versions are being used
4. **Be Conservative**: Prefer minor over major version changes
### For Service Developers
1. **Stay Updated**: Subscribe to schema change notifications
2. **Plan for Migration**: Build version handling into your services
3. **Test Thoroughly**: Validate against multiple schema versions
4. **Monitor Compatibility**: Alert on unsupported message versions
### For Operations Teams
1. **Version Tracking**: Monitor which schema versions are active
2. **Migration Planning**: Coordinate major version migrations
3. **Rollback Capability**: Be prepared to revert if migrations fail
4. **Performance Impact**: Monitor schema validation performance
## Future Considerations
### Planned Enhancements
1. **Schema Registry**: Centralized schema version management
2. **Auto-Migration**: Tools to automatically update message formats
3. **Version Negotiation**: Services negotiate supported versions
4. **Schema Analytics**: Usage metrics and compatibility reporting
### Long-term Vision
- **Continuous Evolution**: Schemas evolve without breaking existing services
- **Zero-Downtime Updates**: Schema changes deploy without service interruption
- **Automated Testing**: CI/CD pipelines validate schema compatibility
- **Self-Healing**: Services automatically adapt to schema changes