/kediatR

Mediator implementation in Kotlin with native coroutine support

Primary LanguageKotlinMIT LicenseMIT

kediatR Release kediatR-core Release kediatR-spring-starter codecov

Humus! The kediatr mascot

Mediator implementation in kotlin with native coroutine support.

Supports synchronous and async (using kotlin coroutines ) command and query handling, native kotlin implementation, spring-boot, quarkus and koin configurations.

After kediatr-core version 1.0.17 you can use any dependency injection framework by implementing DependencyProvider interface.

kediatR has multiple implementations: kediatR-core, kediatR-spring-starter, kediatR-koin-starter and kediatR-quarkus-starter.

kediatR-core

<dependency>
  <groupId>com.trendyol</groupId>
  <artifactId>kediatr-core</artifactId>
  <version>1.1.2</version>
</dependency>

kediatR-spring-starter

<dependency>
  <groupId>com.trendyol</groupId>
  <artifactId>kediatr-spring-starter</artifactId>
  <version>1.1.2</version>
</dependency>

kediatR-koin-starter

<dependency>
  <groupId>com.trendyol</groupId>
  <artifactId>kediatr-koin-starter</artifactId>
  <version>1.1.2</version>
</dependency>

kediatR-quarkus-starter

<dependency>
  <groupId>com.trendyol</groupId>
  <artifactId>kediatr-quarkus-starter</artifactId>
  <version>1.1.2</version>
</dependency>

Usage

  • add kediatr-core dependency to your POM

Command dispatching

class ManualDependencyProvider(
    private val handlerMap: HashMap<Class<*>, Any>
) : DependencyProvider {
    override fun <T> getSingleInstanceOf(clazz: Class<T>): T {
        return handlerMap[clazz] as T
    }

    override fun <T> getSubTypesOf(clazz: Class<T>): Collection<Class<T>> {
        return handlerMap
            .filter { it.key.interfaces.contains(clazz) }
            .map { it.key as Class<T> }
    }
}

fun main() {
    val handler = HelloCommandHandler()
    val handlers: HashMap<Class<*>, Any> = hashMapOf(Pair(HelloCommandHandler::class.java, handler))
    val provider = ManuelDependencyProvider(handlers)
    val bus: CommandBus = CommandBusBuilder(provider).build()
    bus.executeCommand(HelloCommand("hello"))
}

class HelloCommand(val message: String) : Command

class HelloCommandHandler : CommandHandler<HelloCommand> {
    override fun handle(command: HelloCommand) {
        println(command.message)
    }
}

Query dispatching

fun main() {
    val handler = GetSomeDataQueryHandler()
    val handlers: HashMap<Class<*>, Any> = hashMapOf(Pair(GetSomeDataQuery::class.java, handler))
    val provider = ManuelDependencyProvider(handlers)
    val bus: CommandBus = CommandBusBuilder(provider).build()
    val result: String = bus.executeQuery(GetSomeDataQuery(1))
    println(result)
}

class GetSomeDataQuery(val id: Int) : Query<String>

class GetSomeDataQueryHandler : QueryHandler<GetSomeDataQuery, String> {
    override fun handle(query: GetSomeDataQuery): String {
        // you can use properties in the query object to retrieve data from somewhere
        // val result = getDataFromSomewhere(query.id)
        // return result

        return "hello"
    }
}

Pipeline Behavior

class CommandProcessingPipeline : PipelineBehavior {
    override fun <TRequest> preProcess(request: TRequest) {
        println("Starting process.")
    }
    override fun <TRequest> postProcess(request: TRequest) {
        println("Ending process.")
    }
    override fun <TRequest, TException : Exception> handleExceptionProcess(request: TRequest, exception: TException) {
        println("Some exception occurred during process. Error: $exception")
    }
}

Usage with SpringBoot

  • add kediatr-spring dependency to your POM and enjoy yourself
@Service
class UserService(private val commandBus: CommandBus) {
    fun findUser(id: Long) {
        return commandBus.executeQuery(GetUserByIdQuery(id))
    }
}

class GetUserByIdQuery(private val id: Long) : Query<UserDto>

@Component
class GetUserByIdQueryHandler(private val userRepository: UserRepository) : QueryHandler<GetUserByIdQuery, UserDto> {
    fun handle(query: GetUserByIdQuery): UserDto {
        val user = userRepository.findById(query.id)
        // do some operation on user
        return UserDto(user.id, user.name, user.surname)
    }
}

Async Usage with Kotlin Coroutine Support

class UserService(private val commandBus: CommandBus) {
    suspend fun findUser(id: Long) {
        return commandBus.executeQueryAsync(GetUserByIdQuery(id))
    }
}

class GetUserByIdQuery(private val id: Long) : Query<UserDto>

class GetUserByIdQueryHandler(private val userRepository: UserRepository) : AsyncQueryHandler<GetUserByIdQuery, UserDto> {
    suspend fun handleAsync(query: GetUserByIdQuery): UserDto {
        val user = userRepository.findByIdAsync(query.id)
        // do some operation on user
        return UserDto(user.id, user.name, user.surname)
    }
}

class AsyncCommandProcessingPipeline : AsyncPipelineBehavior {
    override suspend fun <TRequest> preProcess(request: TRequest) {
        println("Starting process.")
    }
    override suspend fun <TRequest> postProcess(request: TRequest) {
        println("Ending process.")
    }
    override suspend fun <TRequest, TException : Exception> handleException(request: TRequest, exception: TException) {
        println("Some exception occurred during process. Error: $exception")
    }
}

Usage with Koin

Simply inject kediatr as a singleton dependency with any module and inject handler instances. KediatrKoin.getCommandBus() must be in the same module with at least one Handler to get correct package name for reflection. Please note that this is an experimental release and reflection strategy with koin is a little wonky. Please open a pull request if you think there is a better implementation.

val kediatrModule = module {
    single { KediatrKoin.getCommandBus() }
    single { GetUserByIdQueryHandler(get()) }
}

class UserService(private val commandBus: CommandBus) {
    fun findUser(id: Long) {
        return commandBus.executeQuery(GetUserByIdQuery(id))
    }
}

class GetUserByIdQuery(private val id: Long) : Query<UserDto>

class GetUserByIdQueryHandler(private val userRepository: UserRepository) : QueryHandler<GetUserByIdQuery, UserDto> {
    fun handle(query: GetUserByIdQuery): UserDto {
        val user = userRepository.findById(query.id)
        // do some operation on user
        return UserDto(user.id, user.name, user.surname)
    }
}

Usage with Quarkus

  • Add kediatr-quarkus-starter dependency to your POM
  • Quarkus does not index 3rd party libraries unless you explicitly indicate. Add this configuration to application.properties file.
  quarkus:
    index-dependency:
      kediatr:
        group-id: com.trendyol
        artifact-id: kediatr-quarkus-starter
  • Add @Startup annotation for every handler so that kediatr can prepare queries and commands on beginning of the application.
class UserService(private val commandBus: CommandBus) {
    fun findUser(id: Long) {
        return commandBus.executeQuery(GetUserByIdQuery(id))
    }
}

class GetUserByIdQuery(private val id: Long) : Query<UserDto>

@ApplicationScoped
@Startup
class GetUserByIdQueryHandler(private val userRepository: UserRepository) : QueryHandler<GetUserByIdQuery, UserDto> {
    fun handle(query: GetUserByIdQuery): UserDto {
        val user = userRepository.findById(query.id)
        // do some operation on user
        return UserDto(user.id, user.name, user.surname)
    }
}

Review Our IntelliJ Plugin

https://plugins.jetbrains.com/plugin/16017-kediatr-helper

Screencast 1

Screencast 2

Source: https://github.com/bilal-kilic/kediatr-helper