Last week’s post was dedicated to Ranch and learning OTP application design. This week I will continue learning Elixir / Erlang application design by studying Cowboy. As promised, instead of looking at supervision trees we will take a step back and look at how Cowboy composes multiple OTP applications in order to implement a small, fast, modular HTTP server.
As in the past two posts, I’m using IdahoEv/cowboy-elixir-example to load cowboy and the examining the structure using the Erlang observer. If you are just starting with this series you can follow along by cloning the repo and running the commands from README.md:
Connect to http://localhost:8080 to see the example. Then, from within iex, load the observer:
As seen in observer there are several applications running:
Of course the applications “elixir”, and “mix” are part of the Elixir ecosystem and are pulled in only by the example program so I won’t consider them in this post (though I previously covered Mix design).
Start at the Top
I’ll start with mix.exs for cowboy-elixir-example
So the example depends on cowboy and ranch. Based on what I know so far, it shouldn’t be necessary to explicitly list ranch as a dependency. Searching the sources, I found that cowboy-elixir-example doesn’t reference ranch directly. So I removed ranch from the list of dependencies and the app still runs.
So, here we have a simple composition - cowboy-elixir-example depends on cowboy.
Note, if we look at the deps section we see:
The difference here is that jsex is not an application. It’s a library of functions.
Working down the application tree, I look at cowboy’s cowboy.app.src. I should note that there have been some changes to cowboy’s dependencies at different versions. So I must be carefully about versions. Thankfully, Mix copies the source for all dependencies into the deps directory of our project. So I can use these sources for my analysis.
Here we see that Cowboy depends on:
It is interesting that we don’t see cowlib or crypto in observer. Could they be loaded lazily? Or could they have been unloaded?
Looking next at ranch.app.src in the ranch application I see that ranch doesn’t include any new dependencies:
Similarly for cowlib, there are no new dependencies:
So this gives us the following dependency graph (omitting stdlib and kernel)
Cowboy Dependency Graph
From the graph we can see that Cowboy is nicely encapsulated. It depends on ranch, cowlib, and crypto but it hides these applications from its users (cowboy-elixir-example in this case).
From cowboy-elixir-example’s point of view it uses the cowboy application and the jsex library. jsex is used to build up JSON and then serve it out via HTTP using Cowboy.
It is interesting that we don’t see cowlib or crypto in observer. Could they be loaded lazily? And the observer shows the inets application loaded but I didn’t find a dependency for it. How was inets loaded? Based on the application list I was expecting the dependency graph looks more like this:
Expected Cowboy Dependency Graph
Next week I will explore application loading in more detail and will answer the questions we’ve been left with.