Skip to content

Use kotlin coroutine return types in query extensions instead of CompletableFuture & Optional #139

@matthewadams

Description

@matthewadams

Enhancement Description

The current query extension methods return values using Java's CompletableFuture and Optional types. The usage of these types seems awkward to me in modern Kotlin that uses coroutines. This enhancement request is to encapsulate the use of Java types in favor of pure Kotlin idioms.

Current Behaviour

Note the use of Java types here, for example:

inline fun <reified R, reified Q> QueryGateway.queryOptional(query: Q): CompletableFuture<Optional<R>> {
    return this.query(query, ResponseTypes.optionalInstanceOf(R::class.java))
}

This requires the developer to adapt CompletableFuture to Kotlin's Deferred or Flow types (depending on single vs multiple response types), as well as adapting Optional to Kotlin's nullable type system.

Wanted Behaviour

I've been trying to create new extension methods that only expose Kotlin types.

Consider the following:

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.future.asDeferred
import java.util.Optional
import java.util.concurrent.CompletableFuture

fun <T> Optional<T>.orNull(): T? = orElse(null)

fun <T> CompletableFuture<Optional<T>>.asDeferredOfNullable(): Deferred<T?> = thenApply { it.orNull() }.asDeferred()

Now, notice how I use them in the extension method queryNullableAsDeferred below, where Schedule is a Spring Data MongoDB persistent entity on the read side:

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.future.asDeferred
import org.axonframework.commandhandling.gateway.CommandGateway
import org.axonframework.extensions.kotlin.queryOptional
import org.axonframework.queryhandling.QueryGateway
import org.springframework.stereotype.Component

// note use of Kotlin async types here

inline fun <reified R, reified Q> QueryGateway.queryNullableAsDeferred(q: Q): Deferred<R?> =
    queryOptional<R, Q>(q).asDeferredOfNullable()

@Component
class CqrsSchedulingService(val cgw: CommandGateway, val qgw: QueryGateway) {
    fun createSchedule(cmd: CreateScheduleCommand): Deferred<Unit> {
        return cgw.send<Unit>(cmd).asDeferred() // note conversion to Deferred here
    }

    fun updateSchedule(cmd: UpdateScheduleCommand): Deferred<Unit> {
        return cgw.send<Nothing>(cmd).asDeferred() // note conversion to Deferred here
    }

    fun findScheduleById(q: FindScheduleByIdQuery): Deferred<Schedule?> { // note use of Deferred and Kotlin's ? nullable type operator
        return qgw.queryNullableAsDeferred(q) // note use of extension method here
    }
}

I'm no Kotlin expert yet, so maybe this needs some fine-tuning, but this seems more natural to me. I think this issue could be expanded to other parts of the codebase as well (like CommandGateway.send(..), as they also return CompletableFuture.

Possible Workarounds

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions