Refactoring code is (most of the time) a fun activity until you hit an obstacle or run out of ways to simplify the code. You’ll find yourself looking for new design patterns from books or even digging inside the source code of your favorite open source projects to find some inspiration. Having worked with Django for 3 years, I find its design philosophy and decisions ideal. As a result, I’ve decided to dedicate a blog series to cover this topic: Django inspired design patterns and how you can use them to refactor your code.
Middleware is a simple yet very robust component of Django. Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or output. This should look familiar to you
1 2 3 4 5 6
The core concept of middleware is to dynamically load middleware classes using the provided dotted paths.
Where shall we consider this design pattern?
Let’s consider a simple example of an email validator that all of us can relate to. The email validator:
1. Has input and output
2. Consists of validation conditions (validate input) - validators
3. Can optionally mutate the output - mutators
A simple version can be written like this
1 2 3 4 5 6 7 8 9 10
How do we go about refactoring?
Let’s break down the logic into small units of validators and mutators. We need to define a
PIPELINE constant which holds the dotted paths that can be loaded.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Let’s create a EmailValidator class that dynamically loads validators and mutators.
load_pipelines imports functions from dotted paths and add them into a list.
This example uses
__import__ but it’s recommended that you use
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Why do it this way? What are the benefits?
- Pluggable: this design makes it easy for other developers to extend the logic of your code, it’s maintainable. Let’s say if there’s a new requirement to validate that the email is actually valid and will not bounce, we can easily add a new function to the validators file and add it into the
- Loose coupling: each pipe (function) doesn’t know/care what the other does.
- Easy to unit test: by breaking down the logic into small units, it’s now easier to write tests. We can now write tests for each of those functions in the pipeline.