1

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?

3

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
}
4
  • What if Query and Result can have different datatypes and objects inside? Not only one string instance. Full OOP solution mean simply use immutable instance which accepts Query in constructor and have RunQuery() method, which handles and returns Result instance? Nov 5 '19 at 17:33
  • I updated my post, with different data types inside Query and Result subclasses. Nov 5 '19 at 17:44
  • 1
    If you have different properties on thr objects then you can either cast in your hander (ADM) or encapsulate them and have the method on the class (OOP) OOP works better here. ADM works better when you have multiple ways of processing the same thing
    – Ewan
    Nov 5 '19 at 18:01
  • I rejected OOP-style, because it not suitable for my needs. ADM - fits much better. Separation of logic and data works great in data-oriented and multuthreaded applications. Jan 12 '20 at 8:04
1

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.