Version Upgrades
Comprehensive guide to upgrading Azu applications between versions, including breaking changes and migration strategies.
Overview
This guide covers the process of upgrading Azu applications from one version to another, including preparation, execution, and verification steps. Each version upgrade is documented with specific changes and migration requirements.
Upgrade Process
Pre-Upgrade Checklist
# Pre-upgrade verification script
# scripts/pre_upgrade_check.cr
require "azu"
class PreUpgradeCheck
def self.run
puts "🔍 Running pre-upgrade checks..."
# Check current version
current_version = Azu::VERSION
puts "Current Azu version: #{current_version}"
# Check compatibility
check_crystal_version
check_dependencies
check_deprecated_features
check_breaking_changes
puts "✅ Pre-upgrade checks completed"
end
private def self.check_crystal_version
required_version = "1.16.0"
current_version = Crystal::VERSION
if Crystal::VERSION < required_version
puts "⚠️ Warning: Crystal #{required_version}+ required, current: #{current_version}"
end
end
private def self.check_dependencies
# Check shard dependencies
shard_file = File.read("shard.yml")
if shard_file.includes?("deprecated_shard")
puts "⚠️ Warning: Using deprecated shard 'deprecated_shard'"
end
end
private def self.check_deprecated_features
# Scan codebase for deprecated features
deprecated_patterns = [
"Azu::DeprecatedHandler",
"old_endpoint_pattern",
"legacy_middleware"
]
deprecated_patterns.each do |pattern|
if File.find("src/", pattern).any?
puts "⚠️ Warning: Found deprecated pattern: #{pattern}"
end
end
end
end
PreUpgradeCheck.run
Upgrade Strategy
# Upgrade strategy configuration
# config/upgrade_strategy.cr
CONFIG.upgrade = {
# Upgrade approach: "incremental" or "direct"
approach: "incremental",
# Target version
target_version: "0.5.0",
# Rollback configuration
rollback_enabled: true,
rollback_version: "0.4.14",
# Testing configuration
run_tests_after_upgrade: true,
test_coverage_threshold: 80
}
Version-Specific Upgrades
Upgrading to v0.5.0
Breaking Changes
# v0.4.14 -> v0.5.0 breaking changes
# 1. Handler interface changes
# OLD (v0.4.14)
class OldHandler
def call(request, response)
# Old interface
end
end
# NEW (v0.5.0)
class NewHandler
include Azu::Handler
def call(request, response)
# New interface with proper typing
end
end
Migration Steps
# Migration script for v0.5.0
# scripts/migrate_to_v0_5_0.cr
class V050Migration
def self.migrate
puts "🚀 Migrating to Azu v0.5.0..."
# Step 1: Update shard.yml
update_shard_yml
# Step 2: Update handler implementations
update_handlers
# Step 3: Update endpoint patterns
update_endpoints
# Step 4: Update configuration
update_configuration
# Step 5: Run tests
run_tests
puts "✅ Migration to v0.5.0 completed"
end
private def self.update_shard_yml
shard_content = File.read("shard.yml")
updated_content = shard_content.gsub(
/azu:\s*0\.4\.\d+/,
"azu: 0.5.0"
)
File.write("shard.yml", updated_content)
end
private def self.update_handlers
# Update handler files
Dir.glob("src/**/*_handler.cr").each do |file|
content = File.read(file)
# Add Handler include
unless content.includes?("include Azu::Handler")
content = content.gsub(
/class (\w+Handler)/,
"class \\1\n include Azu::Handler"
)
end
File.write(file, content)
end
end
private def self.update_endpoints
# Update endpoint patterns
Dir.glob("src/**/*_endpoint.cr").each do |file|
content = File.read(file)
# Update endpoint include pattern
content = content.gsub(
/include Endpoint\(([^,]+), ([^)]+)\)/,
"include Endpoint(\\1, \\2)"
)
File.write(file, content)
end
end
end
Upgrading to v0.4.14
New Features
# v0.4.13 -> v0.4.14 new features
# 1. Enhanced WebSocket support
class EnhancedChannel < Azu::Channel
ws "/enhanced"
# New: Automatic reconnection
def on_connect
@auto_reconnect = true
@reconnect_interval = 5.seconds
end
# New: Message validation
def validate_message(message) : Bool
message.size > 0 && message.size < 1000
end
end
# 2. Improved error handling
struct ErrorResponse
include Response
def initialize(@error : Exception, @context : Hash(String, String) = {} of String => String)
end
def render
{
error: @error.message,
type: @error.class.name,
context: @context,
timestamp: Time.utc
}.to_json
end
end
Migration Steps
# Migration script for v0.4.14
# scripts/migrate_to_v0_4_14.cr
class V0414Migration
def self.migrate
puts "🚀 Migrating to Azu v0.4.14..."
# Step 1: Update dependencies
update_dependencies
# Step 2: Enable new features
enable_new_features
# Step 3: Update error handling
update_error_handling
puts "✅ Migration to v0.4.14 completed"
end
private def self.update_dependencies
# Update shard.yml
shard_content = File.read("shard.yml")
updated_content = shard_content.gsub(
/azu:\s*0\.4\.\d+/,
"azu: 0.4.14"
)
File.write("shard.yml", updated_content)
end
private def self.enable_new_features
# Enable WebSocket auto-reconnection
Dir.glob("src/**/*_channel.cr").each do |file|
content = File.read(file)
unless content.includes?("@auto_reconnect")
content = content.gsub(
/def on_connect/,
"def on_connect\n @auto_reconnect = true"
)
end
File.write(file, content)
end
end
end
Automated Migration Tools
Migration Generator
# Migration generator
# scripts/generate_migration.cr
class MigrationGenerator
def self.generate(from_version : String, to_version : String)
puts "🔧 Generating migration from #{from_version} to #{to_version}..."
# Create migration file
migration_file = "migrations/#{from_version}_to_#{to_version}.cr"
migration_content = <<-CRYSTAL
# Migration: #{from_version} -> #{to_version}
# Generated on: #{Time.utc}
class #{from_version.capitalize}To#{to_version.capitalize}Migration
def self.migrate
puts "🚀 Migrating from #{from_version} to #{to_version}..."
# TODO: Add migration steps
puts "✅ Migration completed"
end
def self.rollback
puts "🔄 Rolling back from #{to_version} to #{from_version}..."
# TODO: Add rollback steps
puts "✅ Rollback completed"
end
end
CRYSTAL
File.write(migration_file, migration_content)
puts "📝 Generated migration file: #{migration_file}"
end
end
Migration Runner
# Migration runner
# scripts/run_migration.cr
class MigrationRunner
def self.run(migration_file : String)
puts "🏃 Running migration: #{migration_file}"
# Load and run migration
require migration_file
migration_class = get_migration_class(migration_file)
migration_class.migrate
puts "✅ Migration completed successfully"
rescue ex
puts "❌ Migration failed: #{ex.message}"
puts "🔄 Starting rollback..."
migration_class = get_migration_class(migration_file)
migration_class.rollback
raise ex
end
private def self.get_migration_class(file_path : String) : Class
# Extract class name from file path
class_name = File.basename(file_path, ".cr").split("_").map(&.capitalize).join
Object.const_get(class_name)
end
end
Testing Upgrades
Upgrade Testing Strategy
# Upgrade testing configuration
# spec/upgrade/upgrade_spec.cr
require "../spec_helper"
describe "Upgrade Compatibility" do
describe "v0.4.14 -> v0.5.0" do
it "maintains API compatibility" do
# Test that existing endpoints still work
endpoints = [
UserEndpoint,
PostEndpoint,
CommentEndpoint
]
endpoints.each do |endpoint_class|
endpoint = endpoint_class.new
endpoint.should respond_to(:call)
end
end
it "handles new features correctly" do
# Test new features introduced in upgrade
new_handler = NewHandler.new
new_handler.should be_a(Azu::Handler)
end
it "maintains performance characteristics" do
# Benchmark before and after upgrade
before_performance = benchmark_endpoint(UserEndpoint)
after_performance = benchmark_endpoint(UserEndpoint)
# Performance should not degrade significantly
performance_ratio = after_performance / before_performance
performance_ratio.should be >= 0.95
end
end
end
Regression Testing
# Regression testing script
# scripts/regression_test.cr
class RegressionTest
def self.run
puts "🧪 Running regression tests..."
# Run all existing tests
system("crystal spec")
# Run specific upgrade tests
run_upgrade_specific_tests
# Run performance tests
run_performance_tests
# Run integration tests
run_integration_tests
puts "✅ Regression tests completed"
end
private def self.run_upgrade_specific_tests
# Test specific features that might be affected by upgrade
test_endpoints
test_handlers
test_websockets
test_templates
end
private def self.run_performance_tests
# Benchmark critical paths
benchmark_user_creation
benchmark_post_retrieval
benchmark_websocket_connections
end
end
Rollback Procedures
Automatic Rollback
# Rollback configuration
# config/rollback.cr
CONFIG.rollback = {
enabled: true,
automatic: true,
triggers: [
"test_failure",
"performance_degradation",
"critical_error"
],
backup_strategy: "git_tag",
rollback_timeout: 5.minutes
}
# Rollback handler
class RollbackHandler
def self.trigger_rollback(reason : String)
puts "🔄 Triggering rollback: #{reason}"
# Create backup
create_backup
# Revert to previous version
revert_version
# Restart application
restart_application
puts "✅ Rollback completed"
end
private def self.create_backup
timestamp = Time.utc.to_s("%Y%m%d_%H%M%S")
system("git tag backup_#{timestamp}")
end
private def self.revert_version
system("git checkout #{CONFIG.rollback.previous_version}")
system("shards install")
end
end
Manual Rollback
# Manual rollback script
# scripts/manual_rollback.cr
class ManualRollback
def self.rollback_to(version : String)
puts "🔄 Manual rollback to version #{version}..."
# Step 1: Stop application
stop_application
# Step 2: Revert code
revert_code(version)
# Step 3: Update dependencies
update_dependencies(version)
# Step 4: Restart application
restart_application
# Step 5: Verify rollback
verify_rollback
puts "✅ Manual rollback completed"
end
private def self.stop_application
system("pkill -f azu_app")
end
private def self.revert_code(version)
system("git checkout #{version}")
end
private def self.update_dependencies(version)
# Update shard.yml to target version
shard_content = File.read("shard.yml")
updated_content = shard_content.gsub(
/azu:\s*[\d\.]+/,
"azu: #{version}"
)
File.write("shard.yml", updated_content)
system("shards install")
end
end
Version Compatibility Matrix
Compatibility Table
# Version compatibility matrix
COMPATIBILITY_MATRIX = {
"0.5.0" => {
"crystal": ">= 1.16.0",
"dependencies": {
"radix": ">= 0.4.0",
"schema": ">= 0.1.0",
"crinja": ">= 0.2.0"
},
"breaking_changes": [
"Handler interface changes",
"Endpoint pattern updates",
"Configuration structure changes"
]
},
"0.4.14" => {
"crystal": ">= 1.15.0",
"dependencies": {
"radix": ">= 0.3.0",
"schema": ">= 0.1.0",
"crinja": ">= 0.1.0"
},
"breaking_changes": [
"WebSocket API changes",
"Error handling updates"
]
}
}
Best Practices
1. Incremental Upgrades
# Incremental upgrade strategy
class IncrementalUpgrade
def self.upgrade_to(target_version : String)
current_version = Azu::VERSION
versions = get_upgrade_path(current_version, target_version)
versions.each do |version|
puts "🔄 Upgrading to #{version}..."
# Run migration for this version
run_migration(version)
# Run tests
run_tests(version)
# Verify functionality
verify_functionality(version)
puts "✅ Successfully upgraded to #{version}"
end
end
end
2. Backup Strategy
# Backup strategy
class BackupStrategy
def self.create_backup
timestamp = Time.utc.to_s("%Y%m%d_%H%M%S")
# Git backup
system("git tag backup_#{timestamp}")
# Database backup
create_database_backup(timestamp)
# Configuration backup
create_config_backup(timestamp)
puts "💾 Backup created: backup_#{timestamp}"
end
private def self.create_database_backup(timestamp)
# Create database dump
system("pg_dump myapp > backup_db_#{timestamp}.sql")
end
end
3. Monitoring During Upgrade
# Upgrade monitoring
class UpgradeMonitor
def self.monitor_upgrade
puts "📊 Monitoring upgrade process..."
# Monitor application health
monitor_health
# Monitor performance
monitor_performance
# Monitor errors
monitor_errors
# Monitor user experience
monitor_user_experience
end
private def self.monitor_health
# Check application status
health_check = HTTP::Client.get("http://localhost:3000/health")
if health_check.status_code != 200
puts "⚠️ Health check failed"
RollbackHandler.trigger_rollback("health_check_failed")
end
end
end
Troubleshooting
Common Upgrade Issues
# Common upgrade issues and solutions
UPGRADE_ISSUES = {
"handler_interface_error" => {
"symptom": "Handler interface not found",
"solution": "Add 'include Azu::Handler' to handler classes",
"code_fix": "class MyHandler\n include Azu::Handler\n # ..."
},
"endpoint_pattern_error" => {
"symptom": "Endpoint pattern not recognized",
"solution": "Update endpoint include pattern",
"code_fix": "include Endpoint(MyRequest, MyResponse)"
},
"dependency_conflict" => {
"symptom": "Shard dependency conflicts",
"solution": "Update shard.yml and run 'shards update'",
"code_fix": "shards update"
}
}
Debug Tools
# Upgrade debug tools
class UpgradeDebug
def self.diagnose_issues
puts "🔍 Diagnosing upgrade issues..."
# Check for common issues
check_handler_issues
check_endpoint_issues
check_dependency_issues
check_configuration_issues
# Generate report
generate_diagnostic_report
end
private def self.check_handler_issues
Dir.glob("src/**/*_handler.cr").each do |file|
content = File.read(file)
unless content.includes?("include Azu::Handler")
puts "⚠️ Handler missing include: #{file}"
end
end
end
end
Next Steps
Breaking Changes - Detailed breaking changes documentation
Migration Best Practices - General migration guidelines
Version Compatibility - Compatibility information
Always test upgrades in a staging environment before applying to production.
Last updated
Was this helpful?