Sep 28, 2015
Table of contents:
The Pipeline Design Pattern is where data is passed through a sequences of tasks or stages. The pipeline acts like an assembly-line, where the data is processed and then passed on to the next stage.
Using a pipeline is advantageous because it is really easy to compose a complex process as individual tasks. It also makes it easy to add, remove, or replace stages within the pipeline without disturbing the entire process.
Larval uses the Pipeline Design Pattern in a couple of places throughout the framework. This means everything we need to implement this pattern is already part of the foundation of your application!
As we saw in How to resolve environment specific implementations from Laravel’s IoC Container, we can use Laravel’s internal components to build our own functionality on top of the framework.
In today’s tutorial we will be looking at the Pipeline Design Pattern and how we can leverage Laravel’s internal pipeline.
The Pipeline Design Pattern is where a complex process is broken down into individual tasks. Each individual task is reusable and so the tasks can be composed into complex processes.
This allows you to break up monolithic processes into smaller tasks that process data and then pass that data to the next step.
Each task within the pipeline should receive and emit the same type of data. This allows tasks to be added, removed, or replaced from the pipeline without disturbing the other tasks.
If you are familiar with Unix operating systems you are probably already familiar with piping the output from one command to be the input of another command.
For example:
cat helloworld.txt | grep "hello world" | rev | > output.txt
In this example I’m reading the contents of file, searching for the string “hello world”, reversing the string, and then adding it to a file called output.txt
.
Laravel uses the Pipeline Design Pattern in a couple of different places throughout the framework. By far the most prominent place is Laravel’s implementation of middleware.
When a request comes into the application it is passed through a series of middleware.
Each middleware has the responsibility of a single action. For example, setting the cookies, checking for authentication, or preventing CSRF attacks.
Each stage will process the request and either pass it onto the next stage, or reject the request and return the appropriate HTTP response.
This makes it really easy to add another processing step before a requests gets to your application code.
For example, if you needed to enforce a business rule you could add a middleware to the process. We looked at an example of that last week in Setting the Context in a Laravel Application.
You can also remove anything you don’t need from the process without hacking the internal request lifecycle.
The Pipeline Design Pattern has a number of advantages.
Firstly, complex processes can be broken down into individual tasks. This makes it very easy to test each task in isolation.
Tasks can be reused for different processes without duplicating logic. This is useful if you are enforcing a business rule as you won’t have to duplicate that logic.
It’s very easy to add, remove, or replace tasks of a complex process without disturbing the existing process. Applications are constantly evolving and so it’s likely that a change your code is going to change over time.
But of course, there are always disadvantages of any design pattern too.
Whilst composition makes each individual part simpler, it adds complexity when you try to combine those parts as a single whole.
You also need to ensure that process works as a whole by having tests that can run end-to-end, rather than just testing each individual task. The process is going to be unreliable if it doesn’t work as a complete process.
Finally, it can also be difficult to understand a complex process when viewing it as a set of discrete steps in isolation.
Using Laravel’s Pipeline is very easy. First you need to create a new Illuminate\Pipeline\Pipeline
object and inject it with an instance of Illuminate\Contracts\Container\Container
:
$pipeline = app("Illuminate\Pipeline\Pipeline");
Next you pass in the object that you want to send through the pipeline:
$pipeline->send($request);
Next you pass an array of tasks that should accept and process the request:
$pipeline->through($middleware);
Finally you run the pipeline with a destination callback:
$pipeline->then(function ($request) {
// Do something
});
This is basically how the middleware functionality of the framework accepts HTTP requests and then send the request through the defined middleware for that route.
The Pipeline Design Pattern can be extremely useful. Middleware is the perfect example.
Instead of having a monolithic process for accepting requests and passing it to the application, we can compose individual tasks that accept the request, do the processing and then pass the request to the next stage.
This means each task has a single responsibility, and it is very easy to add, remove or replace tasks to be processed.
Laravel makes good use of this design pattern internally in the framework. But you can use Laravel’s Pipeline functionality for your own projects. In next week’s tutorial we’re going to be looking at how you can use the Pipeline functionality to deal with complex processes.
However, if you do not want to rely upon a non-public facing piece of functionality of the framework, you could us league/pipeline instead.