Question

I have Handler classes which accepts Queries and returns Results. Handlers is anemic. They accept input data bag and returns output data bag. Handlers can be many so I created common generic interface for handlers and common interfaces for query bags and result bags. Additionally, I created HandlerManager class, which accepts Query instance and using pattern matching, decide, which instance of Handler need to use:

sealed class Query {
    class QueryA(val data: Int) : Query()
    class QueryB(val data: Double) : Query()
    class QueryC(val data: String) : Query()
}


sealed class Result {
    class ResultA(val data: Int) : Result()
    class ResultB(val data: Double) : Result()
    class ResultC(val data: String) : Result()
}

interface Handler<Q : Query, R : Result> {
    fun handle(query: Q): R
}

class HandlerA : Handler<Query.QueryA, Result.ResultA> {
    override fun handle(query: Query.QueryA): Result.ResultA {
        return Result.ResultA(query.data)
    }
}

class HandlerB : Handler<Query.QueryB, Result.ResultB> {
    override fun handle(query: Query.QueryB): Result.ResultB {
        return Result.ResultB(query.data)
    }
}

class HandlerC : Handler<Query.QueryC, Result.ResultC> {
    override fun handle(query: Query.QueryC): Result.ResultC {
        return Result.ResultC(query.data)
    }
}

class HandlerManager() {
    private val handlerA = HandlerA()
    private val handlerB = HandlerB()
    private val handlerC = HandlerC()

    fun handle(query: Query): Result {
        return when (query) {
            is Query.QueryA -> handlerA.handle(query)
            is Query.QueryB -> handlerB.handle(query)
            is Query.QueryC -> handlerC.handle(query)
        }
    }
}

Question is simple: is this model is suitable and valid? Or there are OOP-style/Rich domain alternatives?

Was it helpful?

Solution

I'd go with "No", I think you have a number of code smells in your example.

  1. HandlerManager inspects the type of the query

    Ideally instead of checking types you should be using inheritance

  2. Does your use of generics even work? ie, al the handlers implement Handler, but you have to know the type of Handler before you can call the handle function on it

    Since you check the query type in the manager you could simply have a non generic interface for Handler.

I think the problem with your code is that you are attempting to squeeze OOP into a ADM design. Simply go full ADM:

public class Query
{
    string QueryString
    string Type
}

public interface IHandler
{
    Result RunQuery(Query q)
}

public class HandlerManager
{
    Dictionary<string, Handler> handlers
    
    public Result RunQuery(Query q)
    {
        return this.handers[q.Type].RunQuery(q)
    }
}

Or full OOP

public class Query
{
    string QueryString
    Result RunQuery() //override in descendants
}

OTHER TIPS

Rich vs Anemic models: A model being rich or anemic is decided by the availability of the methods in the entities and aggregates. It's not about the Handler. It wouldn't make sense to have many methods other than handle in the handler.

What changes together stays together: Why make all queries (QueryA, QueryB, QueryC) as inner class of Query class. It is unusual that these queries change all change at the same time, so grouping them together is not the best idea. The similar thing for Results

HandlerManager is not open-close: When ever a query is added to the system the HandlerManager has to be modified. There is a beautiful solution using reflection by Jimmy Bogard called mediatR in C#. MediatR automatically calls the Handler using reflection. I am sure there should be good java alternatives. You could check here. It would be best to make the queries open-close as they change very frequently

Licensed under: CC-BY-SA with attribution
scroll top