Search code examples
javareflectiondistributed-system

Best practice to associate message and target class instance creation


The program I am working on has a distributed architecture, more precisely the Broker-Agent Pattern. The broker will send messages to its corresponding agent in order to tell the agent to execute a task. Each message sent contains the target task information(the task name, configuration properties needed for the task to perform etc.). In my code, each task in the agent side is implemente in a seperate class. Like :

public class Task1 {}
public class Task2 {}
public class Task3 {}
...

Messages are in JSON format like:

{
  "taskName": "Task1",  // put the class name here
  "config": {

  }
}

So what I need is to associate the message sent from the broker with the right task in the agent side.

I know one way is to put the target task class name in the message so that the agent is able to create an instance of that task class by the task name extracted from the message using reflections, like:

Class.forName(className).getConstructor(String.class).newInstance(arg);

I want to know what is the best practice to implement this association. The number of tasks is growing and I think to write string is easy to make mistakes and not easy to maintain.


Solution

  • If you're that specific about classnames you could even think about serializing task objects and sending them directly. That's probably simpler than your reflection approach (though even tighter coupled).

    But usually you don't want that kind of coupling between Broker and Agent. A broker needs to know which task types there are and how to describe the task in a way that everybody understands (like in JSON). It doesn't / shouldn't know how the Agent implements the task. Or even in which language the Agent is written. (That doesn't mean that it's a bad idea to define task names in a place that is common to both code bases)

    So you're left with finding a good way to construct objects (or call methods) inside your agent based on some string. And the common solution for that is some form of factory pattern like: http://alvinalexander.com/java/java-factory-pattern-example - also helpful: a Map<String, Factory> like

    interface Task {
        void doSomething();
    }
    
    interface Factory {
        Task makeTask(String taskDescription);
    }
    
    Map<String, Factory> taskMap = new HashMap<>();
    
    void init() {
        taskMap.put("sayHello", new Factory() {
            @Override
            public Task makeTask(String taskDescription) {
                return new Task() {
                    @Override
                    public void doSomething() {
                        System.out.println("Hello" + taskDescription);
                    }
                };
            }
        });
    }
    
    void onTask(String taskName, String taskDescription) {
        Factory factory = taskMap.get(taskName);
        if (factory == null) {
            System.out.println("Unknown task: " + taskName);
        }
        Task task = factory.makeTask(taskDescription);
    
        // execute task somewhere
        new Thread(task::doSomething).start();
    }
    

    http://ideone.com/We5FZk

    And if you want it fancy consider annotation based reflection magic. Depends on how many task classes there are. The more the more effort to put into an automagic solution that hides the complexity from you.

    For example above Map could be filled automatically by adding some class path scanning for classes of the right type with some annotation that holds the string(s). Or you could let some DI framework inject all the things that need to go into the map. DI in larger projects usually solves those kinds of issues really well: https://softwareengineering.stackexchange.com/questions/188030/how-to-use-dependency-injection-in-conjunction-with-the-factory-pattern

    And besides writing your own distribution system you can probably use existing ones. (And reuse rather then reinvent is a best practice). Maybe http://www.typesafe.com/activator/template/akka-distributed-workers or more general http://twitter.github.io/finagle/ work in your context. But there are way too many other open source distributed things that cover different aspects to name all the interesting ones.