The task of this tutorial is to implement a series of resolvers that can tell the current temperature, based on some IP address.
In this demo, I'll be using Clojure Deps to manage the dependencies:
We are going to use
cheshire to parse
JSON from the API responses.
Command Line Application
To run our application, we will use the
:exec-fn feature from
deps.edn, I'll start
setting up the main entry point and show how to trigger it from the command line:
To test this, run the following:
To make this command shorter, add an alias to
Now we can run:
Scaffolding is done. Time to start writing some application logic.
Start from the tail
First, let's understand the data scenario for this task. If we start assuming that we know nothing about the services involved, we still know what we have (the IP) and what we want (the temperature), we can start with this graph representation:
By looking at the documentation on the Meta Weather API,
I see that the temperature information is present in the
https://www.metaweather.com/api/location/$WOEID$ endpoint (
WOEID stands for Where On Earth ID)
the_temp. This means that to fetch the temperature, we need some
Meta Weather provides search endpoints to figure out the
To make this example more interesting, we are going to use the one with latitude and
longitude. In these systems, this means that now
WOEID depends on
Finally, using the GeoJS API, we can use the endpoint
to figure the latitude and longitude, given some IP:
Now we have the complete path from the
IP to the
I found those API's using the Public API's service, I find it a great source to look for open API's to play.
In Pathom, resolvers are the main building blocks express attribute relationships.
To implement the resolvers, I'll start with the one to fetch the latitude and longitude from the IP:
A resolver is like a function, with some constraints:
- The resolver input must be a map, so the input information is labeled.
- A resolver must return a map, so the output information is labeled.
- A resolver may also receive another map containing the environment information.
To test the resolver in the REPL, call it like a function:
A resolver is a custom type, here is what's inside:
Note that in the configuration map of the resolver, we have the same
we wrote in the resolver, while the
::pco/input was inferred from the destructuring used in
the resolver attribute vector.
You can learn more about the details at resolvers documentation page.
Now that we have the latitude and longitude, the next resolver will find a
latlong->woeid resolver, we let Pathom infer the output automatically. To use this
feature, remember that the last expression must be a map. Otherwise, Pathom will
not try to infer the output.
Testing the resolver in the REPL:
We are getting close, the final step is to find out the temperature, given the
To keep our REPL testing in check:
The whole process chains nicely, starting from
temperature, like this:
In the previous example, we were able to find the temperature starting from the IP. I like to point all the names involved in the operation when we finished it, let's look at it again:
We have the
:ip attribute in a map, and then we have
3 function names,
which dictates the step. Now we will replace all the resolver names with a single
attribute: our data demand, the
It's time to leverage the attribute relations established from the resolvers.
To do this, Pathom needs some indexes that combine the attribute relations described by a list of resolvers. This is demonstrated in the highlighted fragments of the following snippet:
Using the indexes we generated at the name of
env, we can do the same processing
without mentioning any resolver name, using a Smart Map:
Note the previous snippet doesn't include the name of any resolver!
We can move that code to our
main function and make our program work:
Then we can run from the command line:
Magic? No, it's the power of graphs!
To help understand how this works, have a look inside that
env variable we defined (the
map on the right side):
For now, let's focus on the
index-oir, which is the main index used to traverse
dependencies. Check the indexes page to learn more about the other
When we request the
:temperature, Pathom looks in the index for a path to that attribute.
It depends on
:woeid, which we don't have, but the index says you can
get it if you provide
:longitude. We don't have those either, then
Pathom has to look again in the index for them, and those are available through
which is in the data context. It's the same path we described before with the table
The previous paragraph described the planning process. The output of that process
is an execution graph that describes what it will take to fulfill the demand. Then it
starts running it, first figure
:longitude available, now the process can fetch the
:woeid to get the
Because our code only talks about context and demand, we can also use this command line tool in a few other forms:
As long as you use some data that has a path to the temperature, it works.
This concludes this tutorial. A quick review:
- Map the available data you have and the data you want in terms of attributes.
- Write resolvers connecting the attribute names, adding more attributes as needed.
- Prepare an environment with the indexes.
- Use EQL to make the information request.
I designed this demo to illustrate the basic concepts of attribute modeling and Pathom.
Here are a few exercise suggestions you can do to extend this demo:
- Add a resolver to tell if the temperature is cold or not, based on some cold threshold
- Add a resolver to use the Meta Weather API to search based on a search query.
- Add a resolver to use the current user public IP when nothing else is provided.