Valentin Trinqué
Valentin Trinqué

Reputation: 1072

Use a class from a list of generic interface

I am trying to implement a QueryBus. Basically, I want to register a list of QueryHandlers. Each QueryHandler implements a handle method defined by an interface. Each QueryHandler is associated to a Query. I want to be able to retrieve a QueryHandler using the Query and call handle on it.

The thing is the handle has to be generic because each QueryHandler handles a Query differently. They all take a dedicated Query and may return whatever they want.

interface Query<R>

interface QueryHandler<R, Q : Query<R>> {
    fun handle(query: Q): R
    fun listenTo(): String
}

// DTOs
data class BookDto(val name: String)

// List books query
data class ListBooksQuery(val page: Int = 1): Query<List<BookDto>>

class ListBooksQueryHandler: QueryHandler<List<BookDto>, ListBooksQuery> {
    override fun handle(query: ListBooksQuery): List<BookDto> {
        return listOf(BookDto("Dune"), BookDto("Dune II"))
    }

    override fun listenTo(): String = ListBooksQuery::class.toString()
}

// Get book query
data class GetBookQuery(val name: String): Query<BookDto?>

class GetBookQueryHandler: QueryHandler<BookDto?, GetBookQuery> {
    override fun handle(query: GetBookQuery): BookDto {
        return BookDto("Dune")
    }

    override fun listenTo(): String = GetBookQuery::class.toString()
}

// Run it!

fun main(args: Array<String>) {
    // Initializing query bus
    val queryHandlers = mapOf(
        with(ListBooksQueryHandler()) {this.listenTo() to this},
        with(GetBookQueryHandler()) {this.listenTo() to this}
    )

    val command = ListBooksQuery()
    val result = queryHandlers[command::class.toString()].handle(command)

    // Should print the list of BookDto
    print(result)
}

I don't even know if its possible, to be honest.

UPDATE 1: I changed the usage example in the main to show what I am really trying to do. The List was for (bad?) demonstration purpose. I want to store the QueryHandlers and retrieve them from a map.

Additional resources:

Here is what I really want to do: https://gist.github.com/ValentinTrinque/76b7a32221884a46e657090b9ee60193

Upvotes: 2

Views: 331

Answers (1)

Paul Georg Podlech
Paul Georg Podlech

Reputation: 530

UPDATE I've read your gist and tried to come up with a solution that will provide a clean interface to the user of the QueryBusMiddleware. Note that I used objects instead of classes for the QueryHandler implementations, which felt more natural to me (since there is only one possible entry in the map for each Query implementation).

interface Query<R>

interface QueryHandler<R, Q: Query<R>> {
    fun handle(query: Q): R
    fun listenTo(): String
}

// DTOs
data class BookDto(val name: String)

// List books query
data class ListBooksQuery(val page: Int = 1): Query<List<BookDto>>

object ListBooksQueryHandler: QueryHandler<List<BookDto>, ListBooksQuery> {
    override fun handle(query: ListBooksQuery): List<BookDto> {
        return listOf(BookDto("Dune"), BookDto("Dune II"))
    }
    override fun listenTo(): String = ListBooksQuery::class.toString()
}

// Get book query
data class GetBookQuery(val name: String): Query<BookDto?>

object GetBookQueryHandler: QueryHandler<BookDto?, GetBookQuery> {
    override fun handle(query: GetBookQuery): BookDto {
        return BookDto("Dune")
    }

    override fun listenTo(): String = GetBookQuery::class.toString()
}

// Run it!
fun main(args: Array<String>) {
    // Initializing query bus

    val queryHandlers = listOf(
            ListBooksQueryHandler,
            GetBookQueryHandler
    )

    val dispatcher: QueryBusMiddleware = QueryDispatcherMiddleware(queryHandlers)

    // Calling query bus
    val query = ListBooksQuery()

    // Result should be List<BookDto>
    val result = dispatcher.dispatch(query)

    print(result)
}

interface QueryBusMiddleware {
    fun <R, Q : Query<R>> dispatch(query: Q): R
}

class QueryDispatcherMiddleware constructor(handlers: List<QueryHandler<*, *>>) : QueryBusMiddleware {
    private val handlers = HashMap<String, QueryHandler<*, *>>()

    init {
        handlers.forEach { handler -> this.handlers[handler.listenTo()] = handler }
    }

    override fun <R, Q : Query<R>> dispatch(query: Q): R {
        val queryClass = query::class.toString()
        val handler = handlers[queryClass] ?: throw Exception("No handler listen to the query: $queryClass")
        return handler::class.members.find { it.name == "handle" }!!.call(handler, query) as R
    }
}

Upvotes: 1

Related Questions