Migration Guide
This guide helps you migrate from traditional architecture to the Wow framework, as well as upgrade between different versions.
Version Upgrade Guide
Upgrade Steps
- Backup Data: Backup event store and snapshot data before upgrading
- Read Changelog: Check Release Notes
- Update Dependency Version: Modify build.gradle.kts or pom.xml
- Run Tests: Ensure all tests pass
- Gradual Rollout: Gradually upgrade production environment
Dependency Version Update
// Update wow version
implementation("me.ahoo.wow:wow-spring-boot-starter:new-version")<dependency>
<groupId>me.ahoo.wow</groupId>
<artifactId>wow-spring-boot-starter</artifactId>
<version>new-version</version>
</dependency>Breaking Changes Check
Before upgrading, check the following:
- API Changes: Check for interface signature changes
- Configuration Changes: Check for configuration property changes
- Metadata Changes: Regenerate metadata files
Migrating from Traditional Architecture
Migration Strategy
Gradual Migration
We recommend a gradual migration strategy, progressively migrating functional modules to event sourcing architecture:
Migration Steps
- Identify Bounded Contexts: Determine business modules to migrate
- Design Domain Model: Define aggregate roots, commands, and events
- Implement Dual Writing: Write to both old and new systems
- Verify Consistency: Ensure data consistency
- Switch Read/Write: Gradually switch to new system
Data Migration
Historical Data Import
For scenarios requiring historical data preservation, it is recommended to define migration commands:
// 1. Define Migration Command
@CreateAggregate
data class MigrateOrder(
val orderId: String,
val customerId: String,
val items: List<OrderItem>,
val createdAt: Long
)
// 2. Handle Migration Command in Aggregate
@AggregateRoot
class Order(private val state: OrderState) {
@OnCommand
fun onMigrate(command: MigrateOrder): OrderCreated {
return OrderCreated(
orderId = command.orderId,
customerId = command.customerId,
items = command.items,
createdAt = command.createdAt
)
}
}
// 3. Send Migration Command
fun migrateHistoricalData(legacyOrders: List<LegacyOrder>) {
legacyOrders.forEach { order ->
val command = MigrateOrder(
orderId = order.id,
customerId = order.customerId,
items = order.items.map { /* convert */ },
createdAt = order.createdAt
)
commandGateway.send(command).block()
}
}Code Migration
From CRUD to Command Pattern
Traditional CRUD Code:
// Traditional service
@Service
class OrderService(private val orderRepository: OrderRepository) {
fun createOrder(request: CreateOrderRequest): Order {
val order = Order(
id = UUID.randomUUID().toString(),
customerId = request.customerId,
items = request.items,
status = OrderStatus.CREATED
)
return orderRepository.save(order)
}
fun updateOrderStatus(orderId: String, status: OrderStatus) {
val order = orderRepository.findById(orderId)
order.status = status
orderRepository.save(order)
}
}Migrated Wow Code:
// Command definitions
@CreateAggregate
data class CreateOrder(
val customerId: String,
val items: List<OrderItem>
)
@CommandRoute
data class UpdateOrderStatus(
@AggregateId val id: String,
val status: OrderStatus
)
// Aggregate root
@AggregateRoot
class Order(private val state: OrderState) {
@OnCommand
fun onCreate(command: CreateOrder): OrderCreated {
return OrderCreated(
customerId = command.customerId,
items = command.items
)
}
@OnCommand
fun onUpdateStatus(command: UpdateOrderStatus): OrderStatusUpdated {
return OrderStatusUpdated(command.status)
}
}
// State aggregate root
class OrderState : Identifier {
lateinit var id: String
lateinit var customerId: String
var items: List<OrderItem> = emptyList()
var status: OrderStatus = OrderStatus.CREATED
fun onSourcing(event: OrderCreated) {
this.customerId = event.customerId
this.items = event.items
}
fun onSourcing(event: OrderStatusUpdated) {
this.status = event.status
}
}From Direct Queries to Query Snapshots
Traditional Query Code:
@Repository
interface OrderRepository : JpaRepository<Order, String> {
fun findByCustomerId(customerId: String): List<Order>
fun findByStatus(status: OrderStatus): List<Order>
}Migrated Query Code:
Refer to Query Service
class OrderService(
private val queryService: SnapshotQueryService<OrderState>
) {
fun getById(id: String): Mono<OrderState> {
return singleQuery {
condition {
id(id)
}
}.query(queryService).toState().throwNotFoundIfEmpty()
}
}Compatibility Notes
Data Format Compatibility
The Wow framework uses JSON serialization for events and snapshot data, ensuring good forward compatibility:
- Adding Fields: New fields will be ignored (backward compatible)
- Removing Fields: Uses default values (needs handling)
- Changing Field Types: Requires event upgrader
Event Upgrades
Use the revision attribute of the @Event annotation for event version control:
@Event(revision = "1.0")
data class OrderCreatedV1(
val orderId: String,
val items: List<OrderItem>
)
@Event(revision = "2.0")
data class OrderCreated(
val orderId: String,
val items: List<OrderItem>,
val customerId: String // New field
)Message Format Compatibility
Ensure message format compatibility:
- Adding Fields: Safe, uses default values
- Removing Fields: Need to ensure consumers can handle
- Renaming Fields: Not compatible, requires version control
Known Issues
Version-Specific Issues
Please check GitHub Issues for the latest known issues list.
Common Migration Issues
- Event Replay Order: Ensure events are appended in version order
- Timestamp Handling: Preserve original timestamps
- ID Generation: Maintain consistent ID format
Migration Checklist
- [ ] Backup existing data
- [ ] Update dependency version
- [ ] Check breaking changes
- [ ] Update configuration files
- [ ] Regenerate metadata
- [ ] Run unit tests
- [ ] Run integration tests
- [ ] Gradual rollout verification
- [ ] Full rollout
- [ ] Monitoring verification
Rollback Plan
If migration fails, follow these rollback steps:
- Stop new service
- Restore old service
- Verify data consistency
- Analyze failure cause
- Fix issues and retry