kediatR

repository·main·Indexed 42 days ago

https://github.com/trendyol/kediatr

A coroutine-native mediator implementation for Kotlin that decouples request/response logic (Requests) and event-driven logic (Notifications) using a unified API. It features built-in support for pipeline behaviors and integrates with major DI frameworks including Spring Boot, Koin, and Quarkus.

Tokens
8.3K
Snippets
26
Records
39
Agent score
53%

What's inside kediatR

  1. Use Polymorphic Handler Inheritance

    main

    Handlers resolve polymorphically.

    • Notifications: A handler registered for a base type (e.g., Notification) will receive all derived notification types.
    • Requests: A more specific handler takes priority over a base-type handler if both are registered.
    sealed class DomainEvent : Notification
    data class OrderPlaced(val orderId: Long) : DomainEvent()
    data class OrderShipped(val orderId: Long) : DomainEvent()
    
    // Receives every DomainEvent subtype
    class AuditLogger : NotificationHandler<DomainEvent> {
      override suspend fun handle(notification: DomainEvent) {
        audit.record(notification)
      }
    }
  2. How kediatR messages and handlers work

    main

    kediatR uses a Mediator to dispatch three types of messages. All handlers are suspend functions and are coroutine-native.

    MessageHandlersReturnsDispatched with
    Request<T>exactly oneTmediator.send()
    Request.Unitexactly onenothingmediator.send()
    Notificationzero or morenothingmediator.publish()
    • Requests: Unify commands and queries. A Request<T> returns a value of type T. Use Request.Unit when no return value is needed. Each request has exactly one handler.
    • Notifications: Fire-and-forget messages that can be handled by zero or more handlers.
    • PipelineBehavior: Wraps every send and publish call, allowing for cross-cutting concerns like logging or validation.
  3. Update Mediator usage and creation

    main

    The Mediator interface is simplified. All requests (Commands and Queries) use the same send() method. MediatorBuilder has been removed in favor of a companion object factory method Mediator.build(dependencyProvider).

    Additionally, publish() for notifications now accepts an optional PublishStrategy which defaults to PublishStrategy.DEFAULT.

    // Mediator Creation
    val mediator = Mediator.build(dependencyProvider)
    
    // Sending requests
    mediator.send(CreateUserCommand())
    val user = mediator.send(GetUserCommand("123"))
    
    // Publishing notifications
    mediator.publish(UserCreatedNotification(user.id)) // Uses default
    mediator.publish(UserCreatedNotification(user.id), PublishStrategy.CONTINUE_ON_EXCEPTION)
  4. Migrate Unit Commands and Commands with Results

    main

    In v4.x, commands are no longer distinct types but implementations of the Request interface.

    1. Unit Commands (commands that return nothing):

      • Implement Request.Unit instead of Command.
      • Implement RequestHandler.Unit<TCommand> instead of CommandHandler<TCommand>.
    2. Commands with Results:

      • Implement Request<TResult> instead of CommandWithResult<TResult>.
      • Implement RequestHandler<TCommand, TResult> instead of CommandWithResultHandler<TCommand, TResult>.

    Note: When updating handler methods, change the parameter name from command/query to request to align with the new unified pattern.

  5. Migrate from v3.x to v4.x

    main
    Version 4.x introduces a complete architecture overhaul. The primary change is the unification of Commands and Queries into a single Request<TResponse> hierarchy, and the replacement of specific handler interfaces with a unified RequestHandler<TRequest, TResult> interface. All messages (Requests and Notifications) now implement the Message sealed interface.
  6. Use type aliases for gradual v4.x migration

    main

    To avoid breaking your entire codebase at once, you can temporarily define type aliases in a common file. This allows you to use v3-style names while actually using v4 implementations. Once the migration is complete, these aliases can be removed.

    Recommended Aliases:

    • Command $\rightarrow$ Request.Unit
    • CommandWithResult<T> $\rightarrow$ Request<T>
    • CommandHandler<TCommand> $\rightarrow$ RequestHandler.Unit<TCommand>
    • CommandWithResultHandler<TCommand, TResult> $\rightarrow$ RequestHandler<TCommand, TResult>
    • Query<T> $\rightarrow$ Request<T>
    • QueryHandler<TQuery, TResult> $\rightarrow$ RequestHandler<TQuery, TResult>
    // Add these type aliases to ease migration
    typealias Command = Request.Unit
    typealias CommandWithResult<T> = Request<T>
    typealias CommandHandler<TCommand> = RequestHandler.Unit<TCommand>
    typealias CommandWithResultHandler<TCommand, TResult> = RequestHandler<TCommand, TResult>
    typealias Query<T> = Request<T>
    typealias QueryHandler<TQuery, TResult> = RequestHandler<TQuery, TResult>
  7. Update Unit Commands to Request.Unit

    main

    In v4.x, commands that do not return a result should implement Request.Unit instead of the old Command interface. Handlers should implement RequestHandler.Unit<TRequest>.

    // After v4.x
    class CreateUserCommand : Request.Unit {
      // command properties
    }
    
    class CreateUserCommandHandler : RequestHandler.Unit<CreateUserCommand> {
      override suspend fun handle(request: CreateUserCommand) {
        // handle command
      }
    }
  8. Configure Spring Boot Integration

    main

    Add the kediatr-spring-boot-{2x|3x|4x}-starter. The starter auto-configures a Mediator bean. Simply annotate your handlers and behaviors as Spring components (e.g., @Component, @Service).

    @Component
    class GetUserByIdHandler(
      private val users: UserRepository
    ) : RequestHandler<GetUserById, User> {
      override suspend fun handle(request: GetUserById): User =
        users.findById(request.id)
    }
    
    @Service
    class UserService(private val mediator: Mediator) {
      suspend fun find(id: Long): User = mediator.send(GetUserById(id))
    }
  9. Migrate from v3.x to v4.x: Breaking Changes Overview

    main

    The transition from v3.x to v4.x involves a complete architecture overhaul. The primary change is the unification of all request types (Commands and Queries) under a single Request<TResult> interface. This simplifies the API to three core interfaces: Request, RequestHandler, and Notification.

    Key Mapping Summary

    Old v3.x InterfaceNew v4.x Interface
    CommandRequest.Unit
    CommandWithResult<TResult>Request<TResult>
    Query<TResult>Request<TResult>
    CommandHandler<TCommand>RequestHandler.Unit<TCommand>
    CommandWithResultHandler<TCommand, TResult>RequestHandler<TCommand, TResult>
    QueryHandler<TQuery, TResult>RequestHandler<TQuery, TResult>
  10. Update Dependency Injection registration for Handlers

    main

    When registering handlers in your DI container, ensure you use the new unified interfaces (RequestHandler.Unit or RequestHandler).

    // Spring Boot
    @Component
    class CreateUserCommandHandler : RequestHandler.Unit<CreateUserCommand> {
      // implementation
    }
    
    // Koin
    module {
      single { CreateUserCommandHandler() } bind RequestHandler::class
    }
    
    // Manual
    val mediator = HandlerRegistryProvider.createMediator(
      handlers = listOf(
        CreateUserCommandHandler(),
        GetUserCommandHandler()
      )
    )
  11. Install kediatR

    main

    To use kediatR, add the kediatr-core module and the starter corresponding to your dependency injection framework. For plain Kotlin applications without a DI framework, only kediatr-core is required.

    val version = "{CURRENT_VERSION}"
    
    // Core — always required
    implementation("com.trendyol:kediatr-core:$version")
    
    // One starter, matching your framework (each pulls in core transitively)
    implementation("com.trendyol:kediatr-spring-boot-2x-starter:$version")
    implementation("com.trendyol:kediatr-spring-boot-3x-starter:$version")
    implementation("com.trendyol:kediatr-spring-boot-4x-starter:$version")
    implementation("com.trendyol:kediatr-koin-starter:$version")
    implementation("com.trendyol:kediatr-quarkus-starter:$version")