Creating A Composable Web Server in Typescript
I suppose we should start where we must when discussing a Composable anything, which is “why should we care about composability?”
Have you ever heard the analogy that programming is like playing with Legos? How many times have you been in a large codebase and thought, “Wow! I can combine any of these things in any way I want to build new things!”
I think it’s fair to say there’s a disconnect between how composable we think our code is versus how composable it actually is.
So then what makes something composable?
Well the first thing I think of is the Closure property. The Closure property essentially says that given a function that accepts two arguments of the same type it will always return a value of the same type. Generically it would look something like this
A -> A -> A. (For those interested in reading more about this, the most basic algebraic structure that upholds the Closure property is called a Magma).
Writing code that adheres to the Closure property means that we can combine pieces of code in any way we would like to build more complex things of the same type.
Creating a composable type
I’ve worked with many different web frameworks, but easily the best api I’ve used was from Suave.io which is written in F# and is remarkably composable. We’ll take it as our inspiration but will make a few different decisions to make it work nicer with Typescript and Node.
The base type for everything in Suave is called a
WebPart. In fact everything in Suave essentially ties back into a
WebPart because of the Closure property. To create a similar structure in Typescript we’ll define the following.
Pretty simple right? Because of the way Typescript handles interfaces and a concept known as “row polymorphism”, a
WebPart is any object with a
run property that is a function from
A -> Promise<Maybe<A>>. As it turns out the
A is generic so this type doesn’t really have anything to do with the web and could be used for all sorts of other things by simply providing a different
There are lots of good tutorials on Maybe if you aren’t familiar with it, but basically it’s a way of defining in the type system that we may or may not have a value. It’s similar to saying
A | undefinedexcept the type is defined elsewhere rather than being inline. My Typescript version is here
Specifying for the Web
It may still be unclear how we’ll use this structure, so here’s a teaser of some functions we’ll write to create WebParts.
methodFilter function is quite simple. It returns a
WebPart<HttpContext> that either succeeds or fails depending upon whether the Request’s method matches the provided string. And so we’re not always dealing with strings everywhere, I’ve added
The key part here is that we’ve specified we want a
WebPart<HttpContext>. By fixing the type variable to
HttpContext we get to use all the properties of our
WebPart type and specialize our
run function to deal with
Ways of composing
Now that we have a type to work with, how do we want to combine them? I can think of at least 2 ways in which I’d want to do that as a developer.
- With an and relationship ( appending one
WebPartto the next )
- With an or relationship ( choosing the first matching
WebPartfrom a list )
WebPart to another
Back to our Lego analogy, we want to be able to snap any block to any other. In our case we’ll have loads of things that can create a
WebPart ( http method filters like we saw earlier, route filters, content writers, etc ) and we want to be able to compose those things in any way we see fit. The implementation for appending two
WebPart types has some complexity to it, but I’ve documented our steps with types to make it a bit easier to understand. Ultimately the implementation doesn’t matter as much as the concept of combining two things to get another of the same type.
And now armed with
append we’ll be able to write code like this:
So while that’s neat, our API is… lacking. Those nested calls to
append are going to get really tedious when we start building more complex applications. If only there was some way to take a list of things and return a single thing…
Reduce to the rescue
That’s literally what
This works OK in a pinch, but it would be nice to have this inline reduction stuff written within the library to work with any number of WebPart types without forcing us to write that same
reduce function every time. But what happens if we receive an empty list?
It turns out that in addition to Closure, our
append has another property that we can leverage called Associativity which can be demonstrated with the following code.
ap2 are the same app regardless of whether we associate the first two elements first or the second two elements first,
append is Associative. In Abstract Algebra that means that our
WebPart type with the
append function is a Semigroup.
Hmm.. that still doesn’t do anything for our empty use case though. We need another property known as Identity to handle our empty list use case.
empty simply uses our
succeed helper from earlier to provide a base case where we default to the chain of WebParts as being successful. Said another way,
empty can be appended to either side of an existing
WebPart without affecting the
WebPart. For some simple examples of the Identity property, you can think about adding 0 to any number, or multiplying 1. It’s also the same concept as concatenating an empty string to another string, or passing negative infinity to a
max function. I could go on, but I think you get the idea.
Now that we have Closure, Associativity, and Identity we have what is known as a Monoid, and it’s just the structure we need to abstract
pipe function starts with our
empty and simply calls
append pairwise as we traverse through the array. Now with this function we can clean up those examples from earlier.
That results in a much nicer api, and we’ve also seen how we can abstract the concept of reducing an array by providing a Monoid instance for a type. I think that’s enough for now. Here’s what we’ve accomplished
- We’ve introduced the
WebParttype that everything in our web server will use
- We’ve given it properties of Closure and Associativity with the
appendfunction. (Making it a Magma and a Semigroup)
- We’ve given it the Identity property with the
emptyfunction. (Making it a Monoid)
- Finally, we’ve used the fact that we have Closure, Associative, and Identity properties on the type to define what it means to combine 0 to many of them into one
We’ll build up the
choose function in the next post which will “choose” the first matching
WebPart from a list of
WebPart. Here’s a teaser: