"Substack for RSS feeds", built using Clojure and Datomic Ions

Jessica and I spent holiday weekend building https://rssto.email, a feed subscription service written in Clojure and hosted entirely on Datomic Ions.

The project started out as an Ion I wrote a few weeks ago, because none of the existing ways to subscribe to RSS feeds had the user experience I really wanted. The result was useful enough that we decided to productionize it and share the source (repo).

There’s not a lot of user stories about Ions out there (despite intermittent HN hype), so I’m also sharing some of my takeaways here from building on this stack.

rssto.email

Why build this?

There’s a lot of incredible blogs and writers on the internet, but at least for me, it’s far easier to keep up with the ones that have email subscriptions, either through Substack or without. Most blogs have RSS/Atom feeds but none of the existing RSS readers I’ve tried have given me a functional, ad-free, made-for-email-reading experience that I really want. This project is mostly a way to solve that problem for myself.

In general, I’m pretty excited by how the number of interesting voices and ideas on the internet is growing so rapidly, and I think there’s a lot more that you could do with this:

I don’t have time at present to build those things, but if you’re interested in contributing, I’d love to talk to you!

Why Clojure & Datomic?

Quick background: Clojure is a Lisp-like functional programming language that runs on the JVM; Datomic is a distributed database that’s queryable using Datalog and has “time-travel” built in; Datomic Ions is a Datomic offering that lets you deploy functions serverlessly as AWS Lambdas.

I’ve been interested in trying out Ions since I first heard about them—REPL-driven development is great, and the ability to write code and deploy it to production from my REPL without worrying about infra would be game-changing.

Fun tech aside, a serverless back-end that uses a horizontally scalable database seemed like the perfect architecture for a side project—easy to keep running even if I’m the only user, and trivial to scale up if not.

Takeaways

Emacs magic

The emacs ecosystem has a ton of support for working with Lisps (not surprising since emacs itself is written in a Lisp), and investing a small amount of time in setting up tooling actually goes a long way:

Clojure is powerful

Since Lisps have no syntax, you can create syntactic constructs on the fly that help you best express whatever you’re writing. While not a crazy example, a cute illustration of this is in hiccup, the library used to create html templates for emails. It lets you manipulate expressions that get compiled to html, and that ends up feeling basically like writing React:

(defn format-notify-body [feed-url new-posts manage-link]
  (html [:html
         [:body
          [:div (seq  (mapv post-html new-posts))]
          [:div
           [:a {:href manage-link :style {:color "blue"}}
            "Unsubscribe"]]]]))

I had hoped to get my hands dirty with the language’s concurrency model, but parallelizing the core polling loop was as easy as replacing map with pmap, so I guess that’s nice!

Given that it’s a functional language, I was surprised at the amount of support for mutating references, especially useful for concurrent access to shared memory—e.g. you can build memoization using atoms and transactional updates using refs. Atoms made it pretty easy to to build a mock function that records the args it was called with, that probably would have been quite difficult in a more “pure” functional language.

Pretty much the only complaint I have with the language is that JVM start-up time really hurts. It takes ~10s to do anything (run tests, start up a REPL, deploy to Datomic). I hacked together a deploy script just so I can push and deploy without having to be in the loop. (Side note: the script uses babashka, an interpreted Clojure runtime for scripting, which looks like an awesome project.)

Datomic Ions are getting there

As for Datomic Ions themselves, they do the stated job: deploying a function to an AWS Lambda is actually just 2 CLI commands. I was especially impressed that I spent no time on package or dependency bullshit, which is the norm if in Python land (which is where I’ve been recently).

However, it turns out when you actually want to build a production service, you need a lot more. I had to spend a bunch of time figuring out:

Overall, it seems like the product is at the stage where it makes for a very compelling demo, but there’s a bit more polish needed to make it truly magical, especially for first-time users. That said, I’m pretty excited by where things are headed, and that more languages and ecosystems are trying to build serverless abstractions.

The good news is that since I’ve spent the fixed cost of figuring out some of this extra boilerplate, you can pretty much copy it wholesale from the github repo for your own project. For the same reason, I think there’s a good chance I’ll be back to build more things on this stack.