0

Problem statement: I have to process request similar to a pipeline.
For example:
When a request comes, it has to undergo a sequence of operations, like (step1,step2,step3...).
So, in order to achieve that, I am using Template design pattern.

Please review and suggest if I am implementing this problem correctly, or there is a better solution.
I am suspecting my approach will introduce code smells, as I am changing values of objects very frequently.

Also, suggest if I & how can I use Java 8 to accomplish this? Thanks.

Code:

package com.example.demo.design;

import java.util.List;

public abstract class Template {
    @Autowired
    private Step1 step1;
    @Autowired
    private Step2 step2;
    @Autowired
    private Save save;
    List<String> stepOutput = null;
    List<String> stepOutputTwo = null;
    List<String> stepOutputThree = null;

    public void step1(String action1) {
        stepOutput = step1.method(action1);
    }

    public void step2(String action2) {
        stepOutputTwo = step2.method(stepOutput, action2);
    }

    abstract public void step3();

    public void save() {
        save.persist(stepOutputThree);
    }

    final public void run(String action1, String action2) {
        step1(action1);
        step2(action2);
        stepOutputTwo = step3();
    }
}
ernest_k
  • 44,416
  • 5
  • 53
  • 99
mayank bisht
  • 618
  • 3
  • 14
  • 43
  • Please take a look at the following links https://stackoverflow.com/questions/39947155/pipeline-design-pattern-implementation https://stackoverflow.com/questions/39947155/pipeline-design-pattern-implementation – forkdbloke Nov 30 '19 at 06:54
  • I sounds to me like a good question for https://codereview.stackexchange.com/. – Ole V.V. Nov 30 '19 at 07:05
  • Yes that the TMP, one final sequence of operations and at least one abstract operation to be implemented later. – Jean-Baptiste Yunès Nov 30 '19 at 09:24

3 Answers3

1

I had same issue! you can do something like this: and uncheckCall method is for handling exceptions.

final public void run(String action1, String action2) {
     //other stuffs 

     Stream.of(step1.method(action1))
           .map(stepOutput->uncheckCall(() ->step2.method(stepOutput,action2)))
           .forEach(stepOutputThree -> uncheckCall(()->save.persist(stepOutputThree)));

    //.....

}

for uncheckCall method:

 public static <T> T uncheckCall(Callable<T> callable) {
    try {
        return callable.call();
    } catch (RuntimeException e) {
       // throw BusinessException.wrap(e);
    } catch (Exception e) {
        //throw BusinessException.wrap(e);
    }
}
Hadi J
  • 16,989
  • 4
  • 36
  • 62
  • Thanks for the answer. But I don't want to return anything fro the stream. My Template class will be implemented by the concrete classes and they will just override the step3(). – mayank bisht Nov 30 '19 at 08:04
  • I just wrote a semi code to represent a road map. however I think you can change your method signature that return something instead of declaring data members and init them. – Hadi J Nov 30 '19 at 18:52
1

In Java 8 streams model, that could look like the following:

final public void run(String action1, String action2) {
    Stream.of(action1)                        // Stream<String>
          .map(s -> step1.method(s))          // Stream<List<String>>
          .map(l -> step2.method(l,action2)   // Stream<List<String>>
          .map(l -> step3.method(l))          // Stream<List<String>>
          .forEach(l -> save.persist(l));
}
Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
1

Well, when there are "pipelines", "sequence of operations", etc. the first design pattern that comes to mind is Chain of Responsibility, that looks like the following

Chain of Responsibility

and provides you with these benefits:

  1. allows you to add new handlers when necessary (e.g. at runtime) without modifying other handlers and processing logic (Open/Closed Principle of SOLID)
  2. allows a handler to stop processing a request if necessary
  3. allows you to decouple processing logic of the handlers from each other (Single Responsibility Principle of SOLID)
  4. allows you to define the order of the handlers to process a request outside of the handlers themselves

One example of real world usage is Servlet filters where you call doFilter(HttpRequest, HttpResponse, FilterChain) to invoke the next handler

protected void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) {
    if (haveToInvokeNextHandler) {
        chain.doFilter(req, resp);
    }
}

In case of using classical Chain of Responsibility pattern your processing pipeline may look like the following:

API

public class StepContext {
    private Map<String, Object> attributes = new HashMap<>();

    public <T> T getAttribute(String name) {
        (T) attributes.get(name);
    }

    public void setAttribute(String name, Object value) {
        attributes.put(name, value);
    }
}

public interface Step {
    void handle(StepContext ctx);
}

public abstract class AbstractStep implements Step {
    private Step next;

    public AbstractStep() {

    }

    public AbstractStep(Step next) {
        this.next = next;
    }

    protected void next(StepContext ctx) {
        if (next != null) {
            next.handle(ctx);
        }
    }
}

Implementation

public class Step1 extends AbstractStep {

    public Step1(Step next) {
        super(next);
    }

    public void handle(StepContext ctx) {
        String action1 = ctx.getAttribute("action1");
        List<String> output1 = doSomething(action1);
        ctx.setAttribute("output1", output1);

        next(ctx); // invoke next step
    }
}

public class Step2 extends AbstractStep {

    public Step2(Step next) {
        super(next);
    }

    public void handle(StepContext ctx) {
        String action2 = ctx.getAttribute("action2");
        List<String> output1 = ctx.getAttribute("output1");
        List<String> output2 = doSomething(output1, action2);
        ctx.setAttribute("output2", output2);

        next(ctx); // invoke next step
    }
}

public class Step3 extends AbstractStep {

    public Step3(Step next) {
        super(next);
    }

    public void handle(StepContext ctx) {
        String action2 = ctx.getAttribute("action2");
        List<String> output2 = ctx.getAttribute("output2");
        persist(output2);

        next(ctx); // invoke next step
    }
}

Client code

Step step3 = new Step3(null);
Step step2 = new Step2(step3);
Step step1 = new Step1(step2);

StepContext ctx = new StepContext();
ctx.setAttribute("action1", action1);
ctx.setAttribute("action2", action2);

step1.handle(ctx);

Also all this stuff can be simplified into a chain of handlers decoupled from each other by means of removing the corresponding next references in case your processing pipeline will have to always invoke all the available steps without controlling the necessity of invocation from the previous one:

API

public class StepContext {
    private Map<String, Object> attributes = new HashMap<>();

    public <T> T getAttribute(String name) {
        (T) attributes.get(name);
    }

    public void setAttribute(String name, Object value) {
        attributes.put(name, value);
    }
}

public interface Step {
    void handle(StepContext ctx);
}

Implementation

public class Step1 implements Step {
    public void handle(StepContext ctx) {
        String action1 = ctx.getAttribute("action1");
        List<String> output1 = doSomething(action1);
        ctx.setAttribute("output1", output1);
    }
}

public class Step2 implements Step {
    public void handle(StepContext ctx) {
        String action2 = ctx.getAttribute("action2");
        List<String> output1 = ctx.getAttribute("output1");
        List<String> output2 = doSomething(output1, action2);
        ctx.setAttribute("output2", output2);
    }
}

public class Step3 implements Step {
    public void handle(StepContext ctx) {
        String action2 = ctx.getAttribute("action2");
        List<String> output2 = ctx.getAttribute("output2");
        persist(output2);
    }
}

Client code

Note that in case of Spring framework (just noticed @Autowired annotation) the client code may be simplified even more as the @Autowired annotation can be used to inject all the beans of the corresponding type into a corresponding collection.

Here what the documentation states:

Autowiring Arrays, Collections, and Maps

In case of an array, Collection, or Map dependency type, the container autowires all beans matching the declared value type. For such purposes, the map keys must be declared as type String which will be resolved to the corresponding bean names. Such a container-provided collection will be ordered, taking into account Ordered and @Order values of the target components, otherwise following their registration order in the container. Alternatively, a single matching target bean may also be a generally typed Collection or Map itself, getting injected as such.

public class StepsInvoker {
    // spring will put all the steps into this collection in order they were declared
    // within the spring context (or by means of `@Order` annotation)
    @Autowired
    private List<Step> steps;

    public void invoke(String action1, String action2) {
        StepContext ctx = new StepContext();
        ctx.setAttribute("action1", action1);
        ctx.setAttribute("action2", action2);

        steps.forEach(step -> step.handle(ctx))
    }
}
szhem
  • 4,672
  • 2
  • 18
  • 30