# kediatR Migration Guide ## From v3.x to v4.x Breaking Changes: Complete Architecture Overhaul This migration guide covers the major breaking changes introduced in v4.x, which includes a complete architecture overhaul with unified request/response patterns and simplified API design. ### Summary of Changes #### Removed Interfaces and Classes - **Removed**: `CommandWithResult` interface - **Removed**: `CommandWithResultHandler` interface - **Removed**: `Command` interface (replaced with `Request`) - **Removed**: `CommandHandler` interface (replaced with `RequestHandler`) - **Removed**: `Query` interface (replaced with `Request`) - **Removed**: `QueryHandler` interface (replaced with `RequestHandler`) - **Removed**: `MediatorBuilder` class - **Removed**: `CommandProvider`, `QueryProvider` classes (replaced with `RequestProvider`) #### New Unified Architecture - **Added**: `Message` sealed interface as the base for all messages - **Added**: `Request` interface for all request types (commands and queries) - **Added**: `Request.Unit` nested interface for requests that don't return results - **Added**: `RequestHandler` interface for all request handlers - **Added**: `RequestHandler.Unit` nested interface for unit request handlers - **Modified**: `Mediator` interface now has a unified `send()` method for all requests - **Modified**: `Mediator.publish()` now requires explicit `PublishStrategy` parameter (with default) - **Modified**: `PipelineBehavior` now works with `Message` instead of separate types - **Modified**: All dependency providers now use `RequestHandler` instead of separate command/query handlers ### Before (Old API) ```kotlin // Commands (no result) interface Command { val type: Class get() = this::class.java } interface CommandHandler { suspend fun handle(command: TCommand) } // Commands with results interface CommandWithResult { val type: Class> get() = this::class.java } interface CommandWithResultHandler, TResult> { suspend fun handle(command: TCommand): TResult } // Queries interface Query { val type: Class> get() = this::class.java } interface QueryHandler, TResult> { suspend fun handle(query: TQuery): TResult } // Mediator with separate methods interface Mediator { suspend fun , TResponse> send(query: TQuery): TResponse suspend fun , TResult> send(command: TCommand): TResult suspend fun publish(notification: T) suspend fun publish(notification: T, publishStrategy: PublishStrategy) } // MediatorBuilder for configuration class MediatorBuilder(dependencyProvider: DependencyProvider) { fun withPublishStrategy(strategy: PublishStrategy): MediatorBuilder fun build(): Mediator } ``` ### After (New API) ```kotlin // Unified message hierarchy sealed interface Message // Unified request interface for commands and queries interface Request : Message { interface Unit : Request } // Unified request handler interface interface RequestHandler, TResult> { suspend fun handle(request: TRequest): TResult interface Unit : RequestHandler } // Notifications remain the same but now extend Message interface Notification : Message // Simplified Mediator interface interface Mediator { suspend fun , TResponse> send(request: TRequest): TResponse suspend fun publish(notification: T, publishStrategy: PublishStrategy = PublishStrategy.DEFAULT) companion object { fun build(dependencyProvider: DependencyProvider): Mediator } } ``` ## Migration Steps ### 0. Optional: Use Type Aliases for Gradual Migration To ease the migration process, you can temporarily add type aliases to your codebase. This allows you to migrate incrementally without breaking existing code: ```kotlin // Add these type aliases to ease migration typealias Command = Request.Unit typealias CommandWithResult = Request typealias CommandHandler = RequestHandler.Unit typealias CommandWithResultHandler = RequestHandler typealias Query = Request typealias QueryHandler = RequestHandler ``` **Benefits:** - Allows gradual migration without breaking existing code - Helps during large codebase migrations - Can be removed once migration is complete **Usage:** 1. Add the type aliases to a common file (e.g., `TypeAliases.kt`) 2. Import them where needed 3. Gradually replace usage with the new interfaces 4. Remove the type aliases once migration is complete ### 1. Update Unit Commands **Before:** ```kotlin class CreateUserCommand : Command { // command properties } class CreateUserCommandHandler : CommandHandler { override suspend fun handle(command: CreateUserCommand) { // handle command } } ``` **After:** ```kotlin class CreateUserCommand : Request.Unit { // command properties } class CreateUserCommandHandler : RequestHandler.Unit { override suspend fun handle(request: CreateUserCommand) { // handle command } } ``` ### 2. Update Commands with Results **Before:** ```kotlin class GetUserCommand(val userId: String) : CommandWithResult { // command properties } class GetUserCommandHandler : CommandWithResultHandler { override suspend fun handle(command: GetUserCommand): User { // handle command and return result return userRepository.findById(command.userId) } } ``` **After:** ```kotlin class GetUserCommand(val userId: String) : Request { // command properties } class GetUserCommandHandler : RequestHandler { override suspend fun handle(request: GetUserCommand): User { // handle command and return result return userRepository.findById(request.userId) } } ``` ### 3. Update Queries **Before:** ```kotlin class GetUserQuery(val userId: String) : Query { // query properties } class GetUserQueryHandler : QueryHandler { override suspend fun handle(query: GetUserQuery): User { // handle query and return result return userRepository.findById(query.userId) } } ``` **After:** ```kotlin class GetUserQuery(val userId: String) : Request { // query properties } class GetUserQueryHandler : RequestHandler { override suspend fun handle(request: GetUserQuery): User { // handle query and return result return userRepository.findById(request.userId) } } ``` ### 4. Update Mediator Usage The mediator usage remains mostly the same, but now all requests use the unified `send()` method: ```kotlin // Unit commands (no change) mediator.send(CreateUserCommand()) // Commands with results (no change) val user = mediator.send(GetUserCommand("123")) // Queries (no change) val user = mediator.send(GetUserQuery("123")) // Notifications now require explicit PublishStrategy (with default) mediator.publish(UserCreatedNotification(user.id)) // Uses default strategy mediator.publish(UserCreatedNotification(user.id), PublishStrategy.CONTINUE_ON_EXCEPTION) ``` ### 5. Update Mediator Creation **Before:** ```kotlin val mediator = MediatorBuilder(dependencyProvider) .withPublishStrategy(ContinueOnExceptionPublishStrategy()) .build() ``` **After:** ```kotlin val mediator = Mediator.build(dependencyProvider) // PublishStrategy is now specified per publish call ``` ### 6. Update Dependency Injection Registration Handler registration needs to be updated to use the new interfaces: #### Spring Boot ```kotlin @Component class CreateUserCommandHandler : RequestHandler.Unit { // implementation } @Component class GetUserCommandHandler : RequestHandler { // implementation } @Component class GetUserQueryHandler : RequestHandler { // implementation } ``` #### Koin ```kotlin module { single { CreateUserCommandHandler() } bind RequestHandler::class single { GetUserCommandHandler() } bind RequestHandler::class single { GetUserQueryHandler() } bind RequestHandler::class } ``` #### Manual Registration ```kotlin val mediator = HandlerRegistryProvider.createMediator( handlers = listOf( CreateUserCommandHandler(), GetUserCommandHandler(), GetUserQueryHandler() ) ) ``` ## Advanced Migration Scenarios ### 1. Parameterized Commands **Before:** ```kotlin class ParameterizedCommand(val param: T) : Command class ParameterizedCommandHandler : CommandHandler> { override suspend fun handle(command: ParameterizedCommand) { // handle } } ``` **After:** ```kotlin class ParameterizedCommand(val param: T) : Request.Unit class ParameterizedCommandHandler : RequestHandler.Unit> { override suspend fun handle(request: ParameterizedCommand) { // handle } } ``` ### 2. Parameterized Commands with Results **Before:** ```kotlin class ParameterizedCommandWithResult( val param: TParam, val retFn: suspend (TParam) -> TReturn ) : CommandWithResult class ParameterizedCommandWithResultHandler : CommandWithResultHandler, TReturn> { override suspend fun handle(command: ParameterizedCommandWithResult): TReturn { return command.retFn(command.param) } } ``` **After:** ```kotlin class ParameterizedCommandWithResult( val param: TParam, val retFn: suspend (TParam) -> TReturn ) : Request class ParameterizedCommandWithResultHandler : RequestHandler, TReturn> { override suspend fun handle(request: ParameterizedCommandWithResult): TReturn { return request.retFn(request.param) } } ``` ### 3. Inheritance Scenarios **Before:** ```kotlin sealed class BaseCommand : Command { abstract val id: String } class SpecificCommand(override val id: String) : BaseCommand() class BaseCommandHandler : CommandHandler { override suspend fun handle(command: BaseCommand) { // handle } } ``` **After:** ```kotlin sealed class BaseCommand : Request.Unit { abstract val id: String } class SpecificCommand(override val id: String) : BaseCommand() class BaseCommandHandler : RequestHandler.Unit { override suspend fun handle(request: BaseCommand) { // handle } } ``` ### 4. Pipeline Behaviors **Before:** ```kotlin class LoggingPipelineBehavior : PipelineBehavior { override suspend fun handle( request: TRequest, next: RequestHandlerDelegate ): TResponse { println("Before: $request") val response = next(request) println("After: $response") return response } } ``` **After:** ```kotlin class LoggingPipelineBehavior : PipelineBehavior { override suspend fun handle( request: TRequest, next: RequestHandlerDelegate ): TResponse { println("Before: $request") val response = next(request) println("After: $response") return response } } ``` ## Benefits of the New API 1. **Unified Architecture**: Single `Request` interface for all request types (commands and queries) 2. **Simplified API**: Fewer interfaces to understand - only `Request`, `RequestHandler`, and `Notification` 3. **Type Safety**: Better compile-time type checking with generic result types 4. **Consistent Patterns**: All requests follow the same pattern regardless of type 5. **Cleaner Dependency Injection**: Single `RequestHandler` interface for all DI frameworks 6. **Flexible Publishing**: Explicit control over notification publishing strategies 7. **Better Testability**: Simpler mocking with unified handler interface 8. **Future-Proof**: Extensible architecture that can accommodate new request types ## Checklist for Migration - [ ] Replace `Command` implementations with `Request.Unit` - [ ] Replace `CommandHandler` with `RequestHandler.Unit` - [ ] Replace `CommandWithResult` with `Request` - [ ] Replace `CommandWithResultHandler` with `RequestHandler` - [ ] Replace `Query` implementations with `Request` - [ ] Replace `QueryHandler` with `RequestHandler` - [ ] Update `MediatorBuilder` usage to use `Mediator.build()` directly - [ ] Update `Mediator.publish()` calls to include explicit `PublishStrategy` (optional, has default) - [ ] Update pipeline behaviors to use `` constraint - [ ] Update dependency injection registrations to use `RequestHandler` instead of separate handler types - [ ] Update import statements to remove references to deleted interfaces - [ ] Update handler method parameters from `command`/`query` to `request` - [ ] Test all handlers to ensure they work correctly - [ ] Update any custom extensions or utilities that referenced the old interfaces ## Troubleshooting ### Common Compilation Errors 1. **"Unresolved reference: Command"** - Replace with `Request.Unit` for unit commands 2. **"Unresolved reference: CommandWithResult"** - Replace with `Request` 3. **"Unresolved reference: CommandWithResultHandler"** - Replace with `RequestHandler` 4. **"Unresolved reference: Query"** - Replace with `Request` 5. **"Unresolved reference: QueryHandler"** - Replace with `RequestHandler` 6. **"Unresolved reference: CommandHandler"** - Replace with `RequestHandler.Unit` for unit commands - Replace with `RequestHandler` for commands with results 7. **"Unresolved reference: MediatorBuilder"** - Replace with `Mediator.build(dependencyProvider)` 8. **"Type mismatch" errors on unit commands** - Ensure unit commands implement `Request.Unit` - Ensure unit handlers implement `RequestHandler.Unit` 9. **"Wrong number of type arguments" on pipeline behaviors** - Add `Message` constraint: `` ### Runtime Issues 1. **HandlerNotFoundException** - Ensure handlers are properly registered with dependency injection - Check that command and handler types match exactly 2. **ClassCastException** - Verify generic type parameters are correctly specified - Ensure handler return types match command result types ## Support If you encounter issues during migration, please: 1. Check this migration guide thoroughly 2. Review the test examples in the `testFixtures` directory 3. Create an issue in the GitHub repository with: - Your current code - The error message - Expected behavior