My recent dives into Elixir have been changing how I write software. More and more I find myself writing in a functional style, even in languages where its not the norm.
As my style of programming changes, I find myself re-using patterns and ideas across many different languages.
This article is about one of the patterns I use most often; interestingly, a pattern without a clear name.
As my friend Dean Radcliffe pointed out, I'm probably describing a fluent interface as described by Martin Fowler. Today I learned.
An Example
I often find myself wanting to chain together many function calls, passing in the results of previous calls into the next function in the chain.
Sometimes, the next function in the chain only needs the result of the previous function. Sometimes, it needs the result of a function further back in the chain.
To make things more real, let’s consider an example. Imagine we’re building a system that receives and responds to text messages from users. In this example, we’ll have three functions:
def get_user(phone_number) do
...
end
def get_response(message) do
...
end
def send_response(user, response) do
...
end
The get_user
function takes in the sender’s phone number and returns a corresponding user object. The get_response
function takes in the sender’s text message and returns a response. send_response
will take in a response and the user to send that response to.
One way of calling these three methods together might look like this:
user = get_user(sms.from)
response = get_response(sms.message)
send_response(user, response)
This is a fine solution. However, we’re binding user
and response
for no other reason than to pass them into send_response
.
Additionally, if our methods evolve over time to need more inputs, or more intermediary values, our function calls will get more and more tangled up.
The Pattern in Elixir
Instead of writing each function to take in exactly the arguments it needs, and returning a single value, let’s broaden our scope.
Let’s create a larger, all-encompassing state object that will contain all inputs, intermediary values, and outputs of our function chain.
In our case, we’ll need the sms
input, the user
object, and our system’s response
:
%{sms: sms, user: nil, response: nil}
We can then rewrite our get_user
, get_response
, and send_response
methods to take our state object as an argument, and return a (potentially modified) state object as a result:
def get_user(state = %{sms: %{from: from}}) do
...
%{state | user: ...}
end
def get_response(state = %{sms: %{message: message}}) do
...
%{state | response: ...}
end
def send_response(state = %{user: user, response: response}) do
...
end
Now we can cleanly chain together all of our function calls:
%{sms: sms, user: nil, response: nil}
|> get_user
|> get_response
|> send_response
Beautiful!
What’s great is that we can easily inject new functions into our chain without worrying about juggling inputs and outputs. Let’s add a function to the end of our chain that logs the entire interaction:
%{sms: sms, user: nil, response: nil}
|> get_user
|> get_response
|> send_response
|> log_interaction
In a similar vein, we can easily add debug logging (or anything else) between each step of the chain to get an in-depth look at what’s happening between each call in the chain:
%{sms: sms, user: nil, response: nil}
|> get_user
|> IO.inspect # debug logging
|> get_response
|> IO.inspect # debug logging
|> send_response
|> IO.inspect # debug logging
Now in Javascript
We can apply this same pattern in Javascript. The Lodash library gives us an excellent set of tools for chaining together function calls. Let’s put them to work.
Imagine we have a similar set of functions:
function get_user({sms: {from}}) {
...
}
function get_response({sms: {message}}) {
...
}
function send_response({user, response}) {
...
}
We can use Lodash to run through our chain, just like we did in Elixir:
_.chain({sms, user: undefined, response: undefined})
.thru(get_user)
.thru(get_response)
.thru(send_response)
.value();
Similarly, we can tap into any point of this chain to add debug logging:
_.chain({sms, user: undefined, response: undefined})
.thru(get_user)
.tap(console.debug) // debug logging
.thru(get_response)
.tap(console.debug) // debug logging
.thru(send_response)
.tap(console.debug) // debug logging
.value();
What’s in a Name
There are lots of names that we can throw at this pattern. Some of them stick better than others, but I’m not fully satisfied with any of them.
I asked in the Elixir slack channel if anyone had a name for this kind of thing:
Is there a generic name for building a large, all encompassing state object, and then passing it through a lot of different functions that might only work on a subset of that state? What Plug does, for example.
I find myself doing that kind of thing a lot, and I wonder if there’s some kind of name/pattern that describes it.
Dependency injection?
Yeah, maybe. I’d describe it as maybe DI + method chaining. DI seems like a weird thing to talk about in a purely functional setting. Of course dependencies will be injected.
I was trying to look at “popular" non-Elixir projects (like Redux or Om) which use the same concept to see if they identify the pattern by a name, but I’m not seeing anything.
Dependency injection (DI) might apply in a way, but I usually think of dependency injection in terms of passing around functionality, not data. In a pure functional context, everything could be considered dependency injection in that sense.
I mentioned “method chaining”, which seems to describe a nice side-effect of the pattern, not the pattern itself.
Maybe the best pattern name would be “Middleware”, as seen in libraries like Express, React, and Plug.
The pattern used by these libraries is exactly what I’ve been describing, but I’m not sure about the name. The term “middleware” was originally used to describe something very different than what we’ve been talking about.
On top of that, there’s some disagreement amongst software developers as to whether “middleware” is the term we should be applying to this pattern (or if this is even a pattern at all).
Either way, I’ll probably refer to this pattern as middleware when talking with other developers, and I’ll definitely continue using my favorite pattern without a name.