2

The title may be a bit misleading but I am trying to find the best solution in connecting database entries to spring beans. I hope that the example that follows will clarify things.

For starters, I have a Task entity.

@Entity
@Table( name = "tasks")
class Task {

}

I also have a TaskExecutor interface that has only one method, execute Task.

public interface TaskExecutor {
  void executeTask(Task t);
}

Now, I currently have 3 implementations of the TaskExecutor interface.

@Service("taskExecutorOne")
class TaskExecutorOne implements TaskExecutor {  
}
@Service("taskExecutorTwo")
class TaskExecutorTwo implements TaskExecutor{
 }    
@Service("taskExecutorThree")
class TaskExecutorThree implements TaskExecutor{
} 

Now, every task should be executed by one of these three task executors. What I am having trouble with, is determining the best solution for creating the connection between the task database entries and the available executor services.

1. One way of doing it is to create an ExecutorType enum with three values:

 enum ExecutorType {
    EXECUTOR_ONE,
    EXECUTOR_TWO,
    EXECUTOR_THREE
    };

We can add a column in the tasks table that will contain the value of the enum for the particular task. This way, when we need to get the executor for the task, we could do something like this:

@Service
class TaskRegisterService {
  @Autowired
  @Qualifier("taskExecutorOne")
  private TaskExecutor taskExecutorOne;

  @Autowired
  @Qualifier("taskExecutorOne")
  private TaskExecutor taskExecutorOne;

  @Autowired
  @Qualifier("taskExecutorOne")
  TaskExecutor taskExecutorOne;

  public TaskExecutor getExecutorForTask(Task t) {
    if(t.executorType == ExecutorType.EXECUTOR_ONE)
      return taskExecutorOne;
    else if(t.executorType == ExecutorType.EXECUTOR_TWO)
      return taskExecutorTwo;
    else if(t.executorType == ExecutorType.EXECUTOR_THREE)
      return taskExecutorThree;
  }
}

I don't like this solution because the creation of a new service would require adding a new entry in the enum and a new if part inside the getExecutorForTask method.

2. Another way of doing is by storing the bean name inside the tasks table. In that case, the getExecutorForTask would look like this:

@Service
class TaskRegisterService {

  @Autowired 
  private ApplicationContext applicationContex;

  public TaskExecutor getExecutorForTask(Task t) {
    return (TaskExecutor) applicationContex.getBean(t.getBeanName());
  }
}

This way, I will be able to add new executors without adding any additional code inside the getExecutorForTask method but I will have to store bean names inside the database.

Is there any better way of solving the problem that I just described? If not, which of the approaches that I described seems better? My goal is to be able to easily add new Executors, with modfying as small amount of code as possible.

Dimitar Spasovski
  • 2,023
  • 9
  • 29
  • 45

2 Answers2

0

I would recommend the last option, adding the bean name to the database based on an enum is a valid way of determining which executor needs to be used. It makes your code a lot more dynamic and modular and looks a lot cleaner (less if-else statements), so I would definitely encourage you to use the last option.

If you have any more questions feel free to ask, then I will elaborate on that :)

Sven Hakvoort
  • 3,543
  • 2
  • 17
  • 34
  • I am not sure if I explained it right but there will be no enum in my second option. My problem with enums is that the addition of new services would require addition of new entries to the enum and that might not be possible if I decide to create a library of the code above. – Dimitar Spasovski Oct 15 '18 at 14:58
  • @Dvorog, in that case it is still the best option :) I just assumed you would use the enum for it, but that is not mandatory – Sven Hakvoort Oct 15 '18 at 14:59
  • @Dvorog, is the question answered to your satisfaction? :) – Sven Hakvoort Oct 16 '18 at 09:43
  • I agree with your answer but I was wondering if there will be a more insightful solution from someone that already had some kind of experience with problems of this type. I guess I am waiting for a more lengthy explanation or a way that will allow me to solve my problem in a better way because storing bean names in a database does not seem like a good thing to do. I am thankful for your answer and If I don't get a better answer I would probably go with that solution but I don't feel like that should be the accepted answer at the moment (if that is what you are asking). – Dimitar Spasovski Oct 16 '18 at 10:55
  • @Dvorog, I was wondering if I could help you further with your issue if my answer did not completely answer the question :) Could you maybe elaborate on the way that the task executors are different from each other and what they (might) need to do? A few examples are very useful for coming up with a good solution :) – Sven Hakvoort Oct 16 '18 at 11:00
  • I am not saying that you did not answer my question, I am saying that I was hoping for a better solution that the one that we are discussing at the moment. Anyway, the executors could do different types of things (read data from db, read data from csv files, read data from email and etc). In short, they will represent multiple implementations of the Executor interface. – Dimitar Spasovski Oct 16 '18 at 21:33
0

You could create TaskExecutor decorator to choose implementation. Something like this:

@Service("taskExecutor")
public class TaskExecutorRouter implements TaskExecutor {
    private final Map<String, TaskExecutor> taskExecutorMap;

    @Autowired
    public TaskExecutorRouter(@Qualifier("taskExecutorOne") TaskExecutor taskExecutorOne,
                              @Qualifier("taskExecutorTwo") TaskExecutor taskExecutorTwo,
                              @Qualifier("taskExecutorThree") TaskExecutor taskExecutorThree) {
        this.taskExecutorMap = new HashMap<>();
        taskExecutorMap.put("taskExecutorOne", taskExecutorOne);
        taskExecutorMap.put("taskExecutorTwo", taskExecutorTwo);
        taskExecutorMap.put("taskExecutorThree", taskExecutorThree);
    }

    @Override
    public void executeTask(Task task) {
        taskExecutorMap.get(task.getExecutorType()).executeTask(task);
    }
}

Then you could inject only this implementation as TaskExecutor.

Alexander Pankin
  • 3,787
  • 1
  • 13
  • 23
  • Thank you for your answer but this solution is no different that the one I described (option 1). This one needs to keep track of all available services inside the register and because of that it is same as creating an enum. – Dimitar Spasovski Oct 16 '18 at 21:41