I had an Express server that was processing events from a payment system. For example, if a subscription was changed by the user, the payment system would send an event, which my server would process. The first step in every controller function was to take apart the request
object to extract and check input parameters. If a parameter was missing or incorrect, the middleware would print a console message and return an error HTTP code. Otherwise everything would be ok, and the server would do something.
1 | const R = require('ramda') |
The same parameter logic was everywhere - checking the request
object to have body.content.customer
object (and all other checks) was in every middleware function. Unless I called every controller path with every combination of valid and invalid parameters, I could not get close to 100% of code coverage in my middleware tests.
Usually I would extract common code like that into its own function. But in this case the parameter check also does control actions. If a parameter is invalid, the code
- sends response status to the caller using
res.sendStatus(400)
- returns from the middleware function early
I could not simply factor this code out
1 | const R = require('ramda') |
My utility function getArguments
needs to do both things: it should extract parameter, and "flag" the status. Then the caller would know - where all parameters good or not? I could implement the check in the caller using my own check, for example, by checking for null
1 | const getArguments = (req, res) => { |
Nice, but what if null
is an allowed value? We need something "standard" to avoid reinventing this wheel again and again. In functional programming encoding additional information around a value means we should return some "box" type that keeps the value and allows to use the value in a standard way. What do we need in our case?
- if parameters are good, then we need to call our
Customer.update(customer)...
code - if parameters are bad, we need to call
res.sendStatus(400)
There are only two possibilities and this type is commonly called Maybe
. I can use an implementation from folktale. My getArguments
function will signal that all parameters are good by returning Maybe.Just
object. If something is invalid, it will return Maybe.Nothing
1 | const Maybe = require('folktale/maybe') |
Super, the caller now "knows" that it should handle both cases, and there is an easy way to do this - by matching the returned type, almost like a switch
statement using Maybe.matchWith
method
1 | const onSubscriptionCreated = (req, res) => { |
Perfect, all our input checking logic is refactored into a single function, and we are using "standard" Maybe logic to continue processing based on the returned result. Our middleware is a little bit cleaner now, with each function doing only its job and not mixing input checks with sending a response for example.
Other libs
I like using folktale, but similar Maybe implementation can be found in pretty much every functional JavaScript (and other languages) library
- in Monet.js
- in Sanctuary.js
- in other implementations, see Why use a Maybe type in JavaScript?
More work
We have only split parameter extraction from acting on them. But we could do more work to make code cleaner and more reliable
- instead of
Maybe
return actual results of input argument validation (which could have multiple error messages), usually calledResult
- separate parameter checks (which is a pure operation) from printing error messages (which is a side effect)
- use Task instead of Promise in
Customer.update(customer)
to make implementation pure