2

I am trying to automate Business Process which can be characterized as a pipeline with distinct stages.

Pipeline can be perceived as A->B->C, where A,B,C are distinct Business Steps that work on some common POJO.

For example, lets consider this Business process:

Business needs to identify whether a Book can be sold in Indian Subcontinent.

Step 1. Ensure that the Book has a specific Publisher P

Step 2. Fetch all editions of Book. The book can only be sold if the edition is > 2.0

Step 3. Make sure that the Price is < 5k. If not check with the Publisher.

This is a simple Pipeline with 3 steps. These steps are different from each other and should be designed for easy re-usability. For example, if tomorrow I need to work on a similar logic for US, I should be able to use Step 1, but for a different publisher.

I have an Interface like this:-

interface Step {
  // T pojo might vary based on which business problem is being solved.
  // But the variation will be very slight. For example, different Business
  // involving Books will typically work on multiple POJO's each having attributes of Books (with slight variations) 
  <T> void process(T pojo) throws Exception;
}

For simplicity, let's say we have 2 distict Business Process in Indian Subcontinent & in US. We might have following POJOs.

class IndianBooks {
  String publisher;
  int price;
  String importer; // this one is not present next POJO
  ...
}

class USBooks {
  String publisher;
  int price;
  String printedAt; //this one is different from IndianBooks
  ...
}

Each Pipeline step would implement this Step Interface For example.

class FilterByPublisher implements Step {
//lets say FilterByPublisher  is invoked by Business 1, which deals with Indian Subcontinent
// `process` method should be able to operate on IndianBooks POJO
// if invoked by Pipeline2 which opearates on US Books this method should be 
// able to handle USBooks POJO

  void process(T pojo) {
    1. needs to access publisher attribute of T
    ...
 }
}

I have thought of the following way:

  1. using instanceof within the process() method to check the type of the POJO and then operate on it, then set the other attribute. Note that this change should be reflected in sub-sequent Stages

But somehow I feel that this is not a scalable way since everytime a new Pipeline comes into the scene, I will need to update this method.

Any inputs on how I can effectively solve this?

Update: - After looking at the comment from @Jules, I looked at Visitor Pattern & thought of following approach.

I could keep the Step Interface generic. Create an abstract FilterByPublisher class which handles filtering logic. Create 2 Concrete Filter Classes (to be used by different Business Processes) which just handle the input & pass on to the abstract FilterByPublisher class.

abstract class FilterByPublisher {
  void filter(String publisher) {
    ..
 }
}

class FilterByPublisher_IndianBusiness extends FilterByPublisher implements Step {
 @Override
 public <T> void process(T pojo) {
   String publisher = ((IndianBooks)pojo).getPublisher(); // typecast
   filter(publisher);
  }
}

Any other alternatives are welcome.

5
  • I'm not quite sure I understand your problem, but it sounds like you need to be able to add both additional processing types and data types to the same data structure. This is commonly known as the expression problem and the usual approach in an OO language is to use the visitor pattern, although there are other more exotic solutions like object algebras.
    – Jules
    Commented May 2, 2016 at 7:44
  • @Jules I am trying to implement a simple pipeline (business logic) comprising of multiple steps. What I am looking for is to make each step reusable. This way each step can be plugged in & out in different pipelines. The constraint here is that different Pipelines might be working on different POJOs so the Stage(or step) should be able to handle that. One solution is to have multiple concrete Stages doing same thing but on different POJOs. The processing can be provided by Abstract Stage & POJO handling by Concrete Stages.
    – AgentX
    Commented May 2, 2016 at 8:07
  • @Jules I have updated the Question with Problem Example.
    – AgentX
    Commented May 2, 2016 at 9:08
  • 2
    Why not just have USBooks and IndianBooks that inherit from the same Books class which has publisher/price/edition. Commented May 2, 2016 at 14:19
  • Another option is to implement Step<IndianBooks> to force the type used with this specific step during compilation Commented May 2, 2016 at 14:26

2 Answers 2

3

Your design / domain model is plain wrong, if you have to deal with instanceof. This is a clear usecase OOP was invented for, resp. Polymorphism. treating a bunch of different objects as a family of objects. There are two ways to solve your problem:

1) Define a common base for Indian Books and USBooks. This makes sense, when both are indeed Books.

2) Define an interface which provides the necessary information for filtering

As a next Step, you should refactor the filtering to a strategy pattern, so that you are able to dynamically swap filter criteria.

2
  • 1
    I would suggest also to use Map and namespace atttibutes instead of different books. If books are different in every country (Context) it may end up with hundred types of books. It does hard to scale the code. As you pointed books are books the rest are not book properties (printedAt, importer). These different properties are contextual but does not define what a book is. They are like labels.
    – Laiv
    Commented Jun 5, 2016 at 9:03
  • 1
    @Laiv You are completely right. For the sake of the example I stayed with Books. But you are right, that there is no necessity to have different book-classes for each country. "Composition (and not inheritance) is your friend" :] Commented Jun 5, 2016 at 9:37
0

It could be made rather generic using reflection and a factory that uses a Map. The map connects classes, which hold functionality for processing one or more book types, to those book types.

The various steps in the A->B->C process are given a name, and a factory is created with that given name. Using the step name and the book class passed to the factory the correct step class is fetched using the map/table.

When one wants to add functionality one creates the class that holds that functionality. Then update the map/table so as to map that functionality to one or more book classes and steps. When new functionality is added the only change to existing code is to update the map. Even then the map aught to be externalized through jpa, or a spring injection, etc. Then there are no code changes, only new class additions. If externalizing, then the class name might be used instead of the class object for indexing the mapping.

Interfaces:

public interface Book {

}

public interface Step {
    public void execute(Book book);
}

The factory (getters and setters omitted for brevity):

public class StepFactory {

    private static final Map<String, Map<Class<Book>, Class<Step>>> map = new HashMap<>();
    // add items to the map

    private String stepName;

    public StepFactory(String stepName) {
        this.stepName = stepName;
    }

    public Step getStep(Book book) {

        Map<Class<Book>, Class<Step>> stepMap = map.get(stepName);
        if (stepMap == null) {
            // do whatever else needs to be done here
            return null;
        }

        Class<Step> stepClass = stepMap.get(book.getClass());
        if (stepClass == null) {
            // do whatever else needs to be done here
            return null;
        }

        try {
            Step step = stepClass.newInstance();
            return step;

        } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
                // do whatever else needs to be done here
                return null;
        }

    }
}

Somewhere else:

StepFactory stepFactory = new StepFactory("step A");
Step step = stepFactory.getStep(book);
step.execute(book);

The classes implementing the functionality still cast the book to the particular Book implementation they require. But such is expected to succeed based on proper mapping.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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