Search code examples
javadesign-patternsinterfacefactory-patternabstract-factory

How to load factory pattern dynamically from the map?


I have various types of events as shown below. In general I have more events.

EventA
EventB
EventC
EventD
EventE

Below is the code where I will get each event types:

  public void run() {
    try {
      while (!closed.get()) {
        ConsumerRecords records = consumer.poll(10000);
        // Handle new records
        for (ConsumerRecord<String, String> record : records) {
          GenericRecord payload = decoder.decode(record.value());
          String eventType = String.valueOf(payload.get("eventType"));
          String eventMapHolder = String.valueOf(payload.get("eventMapHolder"));
          Map<String, String> eventHolder = parseStringToMap(eventHolder);
          // here eventType will "EventA", "EventB" and so on
          // pass eventType to get the individual factory for that event type
          // and then  pass eventHolder to a particular method of that factory class which calls appropriate Query method.
        }
      }
    } catch (Exception e) {
        // log error
    }
  }

For each event, I need to perform different database queries but for most of them atleast, I need to perform one database query which is common for all of them. For example:

EventA  -----> QueryA, QueryB
EventB  -----> QueryA
EventC  -----> QueryA, QueryC
EventD  -----> QueryA, QueryD
EventE  ----->

Below will be the flow:

                    EventA  ----->  QueryA, QueryB
                    EventB  ----->  QueryA
eventHolder         EventC  ----->  QueryA, QueryC
                    EventD  ----->  QueryA, QueryD
                    EventE  ----->  
  • So let's say I get "EventA" as eventType in the code I have in my question, then I will pass eventHolder Map to a method (let's call that as QueryA) and within that method, I will do some operation on this map and return a new Map. And then this new Map will be passed to another method then (let's call that QueryB) and then again we will do some operation on that and then return another Map and then that map will go back to caller.
  • Now let's say "EventB" comes as eventType in the code I have in my question, then I will pass eventHolder Map to a method (let's call that as QueryA) and within that method, I will do some operation on this map and return a new Map. And then this map will be passed back to the caller.
  • Now let's say "EventE" comes as eventType in the code I have in my question, then I will pass eventHolder Map to EventE class and and since there is nothing we have to do for this case, then I will pass this map as it is back to the caller.

Question:

Which design pattern I should use for this kind of problem? I want to avoid using if/else or switch here bcoz in general I have lot of event types so thinking of making an individual factory for them which we can load it at runtime dynamically by passing eventType value.

I know all the eventType beforehand so I can statically initialize all the factories by putting it in a map and then once the eventType comes while we are running the code, I can load individual factories from the map.

How can I use abstract factory pattern here to accomplish this if this is the right design pattern for this problem? I want to pass "eventType" as the key to a factory which will return instance of that event type. So let's say I will pass "EventA" to the factory and then it will return factory class for "EventA" and then we will call a particular method on that class which internally calls QueryA method and then we call QueryB method and at the end it will return Map which I will print it out in the above code. And Similarly for other event types.

I was thinking something on this ground: I am not sure whether this is possible at all with the factory pattern or may be some other design pattern. (My naming conventions might be messed up, I just came up with those below names)

EventFactory eventFactory = EventTypeFactory.getInstance().getFactory(eventType);
Map<String, String> holder = eventFactory.execute(eventHolder);

Solution

  • Well, there are 2 main things to achieve in this question.

    • Introduce & re-use custom query logics.
    • Dynamically map eventType String to the related query action(s).

    For the first thing I prefer you adopt a variation of Interpreter pattern. So it would look like this.

    public interface Query{
      public Map<String, String> execute(Map<String, String> map);
    } 
    
    @Singleton
    public class QueryA implements Query{
      @Override
      public Map<String, String> execute(Map<String, String> map){
        //queryA logic.
      }
    }     
    
    public class CustomQuery implements Query{
      List<Query> queryList;
    
      public void CustomQuery(List<Query> queries){
        this.queryList = queries;
      }
    
      public void CustomQuery(Query query1, Query query2){
        this.queryList = new ArrayList();
        queryList.add(query1);
        queryList.add(query2);
      }
    
      @Override
      public Map<String, String> execute(Map<String, String> map){
        if(queryList == null){
             return map;
        }
    
        Map<String, String> eventMap = map;
        for(Query query: queryList){
            eventMap = query.execute(eventMap);
        }
        return eventMap;
      }
    }
    

    Note that each child query class (ex: QueryA) should be Singleton to avoid memory foot-print. Using above we can have custom query actions.

    ex: QueryAB (needs for eventA)

    @Inject
    QueryA queryA;
    @Inject
    QueryB queryB;
    Query queryAB = new CustomQuery(queryA, queryB);
    

    Now you can have a Map<String, Query> eventToQueryMap which dynamically maps your EventType into the necessary QueryLogic(s).

    Ex:

    Map<String, Query> eventToQueryMap = new HashMap();
    eventToQueryMap.put("EventA", queryAB);
    eventToQueryMap.put("EventB", queryA);
    //like above you can add for others too.
    

    Then whenever you receive your eventType String what you all want to do is.

    Map<String, String> eventMap = eventToQueryMap.get(eventType).execute(eventHolder);
    

    This is the outline solution for the main problem you have. You can add customization in your own. :))