Friday, July 20, 2018

Assembly Line Pattern

Intro

Hi all!
Important: This is my first attempt at a decent blog post about programming. Please go easy on me if I miss something or write something that doesn't look good.

So to the point. Java's CompletableFuture has been available for quite some time and it is a very useful tool indeed. But most well-known patterns from the famous "Design Patterns" book were created a looong time ago when creating more than one thread was rare. The pattern that I called "Assembly Line" will use both some well-known patterns and CompletableFuture to solve one common task.

Motivation

The user pressed a button and the program must execute some actions in response. The actions are executed sequentially slowly building up the end result. Like this:
It seems like an easy task - create some functions and do it! Nothing can be easier, it is simple single-threaded programming! Unfortunately it is not so easy. What if you have several similar tasks like this and in each task the steps are a little different? One step is removed, one is added. For example for some actions you do not need to show the user dialog in others additional processing is required for the parameters. That means a lot of code must be written several times. What if one or two of those steps represent a long-running operation? A special subsystem is called for to create and process such requests.

Applicability

Use this pattern when the process of handling user requests consists of similar steps with a little variation between the requests. The steps are executed sequentially no parallel execution is necessary.

Structure

Simple CompletableFuture

To describe the structure we'll start with the simplest form possible:
This diagram clearly shows the idea but it is very unwieldy. Usually the sequential tasks are not running in isolation. They need to get some information and add some information to the final result. 

Context

We need a context:
This is much better but here is the next hurdle. We need to reuse as much code as possible and that means the code for individual stages (we'll call them stages from now on) must be separated from each other and added as necessary. That means we also need a builder for the stages.

Builder

We pass some parameters to the builder and the builder creates a chain of stages. It is not the same as the Chain of Responsibility pattern because all the stages are always executed. This is more like filters in a servlet.
Now that we have a builder we can take a look at the base class for all stages.

Stage Base Class

The idea is to use "then" methods of CompletableFuture to chain executions and return a CompletableFuture that is executed after all the stages have completed execution. It is best to use an Executor to run the stages.

Complete Picture

The complete picture looks a little more complicated than the one we started with:
Everything looks in place. The final CompletableFuture from the System Entry point returns the context object.

Participants

Assembly Line system entry point - accepts the requests and begin processing. Creates and executes the chain of stages.
Context - stores the information the stages need to execute. Stages may read information from the context and write information to the context
Stage - one piece of work that is executed as part of a chain. Could be a long-running task
StageBuilder - creates the chain of stages based on the user request

Consequences

  1. As usual - reduced coupling. More classes that know nothing about each other :-). Seriously all the code that is used to handle user requests can be divided into small, more or less  independent classes/methods. Then the builder will create the correct chain for every user request. No code duplication, loose coupling through the context object
  2. Ability to execute long-running tasks as part of the chain. The main reason to use CompletableFuture in this pattern. In fact some stages may even use their own thread pools within themselves as long as they return a CompletableFuture.
  3. This structure is easy to understand and extend. It is easy to add more stages if necessary - just add the necessary classes and modify/add the builder. In fact the ability to understand should not be underestimated here. This simple model allows us to break a lot of sequential code into simpler pieces and combine them as necessary.

Final Thoughts

The assembly line system has existed for more than 100 years. It must be very simple for humans to understand and use. Why not use it in programming as well?

No comments:

Post a Comment