You can save a lot of time, money, and frustration by using prewritten libraries and tools when making your full stack apps. One such tool is Redis. Redis is a server that works on most platforms for server-side caching, pub/sub, messaging, Lua scripting, and data structure storage. It is thread-safe, loves puppies and is good with small children.
Seriously, even if you just use Redis as your cache, you’ll save a lot of frustration and make your app scalable.
This tutorial gives an example of using Redis for pub/sub, also known as Publish/Subscribe. All this will be done using Clojure’s Carmine library. (Additional documentation for Carmine found here.
Prepare Redis
First you need a working copy of Redis. The preferred method of getting Redis is to build it yourself and run it locally with the command redis-server . Full instructions for downloading the current release of Redis are available at redis.io.
I won’t copy-and-paste the instructions here, because they’d be out of date in a few months.
Clojure Carmine
Now for the fun part. We will use the Clojure Carmine library to set up an example of Redis pub/sub. The Carmine library is fantastic, and worth a closer look. Check out Carmine over on Github. Even consider donating to the author for all his hard work.
Begin by starting up a copy of Redis on localhost.
1 |
redis-server |
Note the port it is running on.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
_._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 6.0.8 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 95630 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' |
In this case the port is 6379. You will need this port for your Clojure code.
Now, create a new Clojure project using Lein. I called my app ‘my-pubsub’.
1 |
lein new app my-pubsub |
Next open your project in Visual Code, or your favorite IDE.
You need to add Carmine as a dependency of your project. That will load the Carmine library from the web. The version of Carmine I am using is 3.0.1, so I modify the project.clj file to have the following dependencies.
1 |
[com.taoensso/carmine "3.0.1"] |
The entire project.clj looks like this.
1 2 3 4 5 6 7 8 9 10 11 12 |
(defproject my-pubsub "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [ [org.clojure/clojure "1.10.1"] [com.taoensso/carmine "3.0.1"]] :main ^:skip-aot my-pubsub.core :target-path "target/%s" :profiles {:uberjar {:aot :all :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}}) |
Next, lets write some Clojure.
For this example, we will put all the code in core.clj. However, in a real application, you would want to start up the Carmin connection pool to for Redis in another namespace, and use dependency injection to get access to Carmine’s connection pool. Mount would be a good choice for lifecycle management in that situation.
Skipping ahead, here is the full code for the core.clj file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
(ns my-pubsub.core (:require [taoensso.carmine :as car :refer (wcar)]) (:gen-class)) (def server1-conn { :pool {} :spec {:uri "redis://localhost:6379/"}}) (defmacro wcar* [& body] `(car/wcar server1-conn ~@body)) (def my-pubsub-listener (car/with-new-pubsub-listener (:spec server1-conn) {"some-channel" (fn [msg] (println "-->" msg))} (car/subscribe "some-channel"))) (defn -main [& args] ;; should return PONG if the Redis ;; connection is working properly (println "Checking the connection!" (wcar* (car/ping))) (wcar* (car/publish "some-channel" "Working on the weekend!"))) |
Your project should now run properly with the command, lein run . Okay. Let’s give a bit of an explanation here.
First you need to set up the connection pool for Redis connections.
1 2 3 |
(def server1-conn { :pool {} :spec {:uri "redis://localhost:6379/"}}) |
Whenever you need to specify your Redis server’s connection, you will use server1-conn . The connection could have been named anything. redcon would have worked just as well.
The :spec keyword takes a list of parameters needed to set up the connection. :uri for instance, might have specified a username and password, as in,
1 |
:spec {:uri "redis://theuser:thepass@localhost:9475/"} |
Next you need a macro to make life easier when dealing with Carmine function calls.
1 |
(defmacro wcar* [& body] `(car/wcar server1-conn ~@body)) |
wcar* is the standard, so just plan on using it everywhere.
Now we’re ready for a listener. If a tree falls on a channel, does it make a noise? Honestly, that made more sense in my head.
1 2 3 4 5 |
(def my-pubsub-listener (car/with-new-pubsub-listener (:spec server1-conn) {"some-channel" (fn [msg] (println "-->" msg))} (car/subscribe "some-channel"))) |
with-new-pubsub-listener is a function that creates a listener with a callback function attached to a named channel. In this case, we are listening to a channel named ‘some-channel’.
Finally, let’s publish a message to the channel.
1 |
(wcar* (car/publish "some-channel" "Working on the weekend!")) |
The resulting message will be returned to the subscribers in a vector. In this case the resulting vector will look like this.
1 |
[message some-channel Working on the weekend!] |
So there you have a bare bones Redis pub/sub implementation in Clojure using Carmine. There is plenty of more to learn–like unsubscribing and closing listeners.
Enjoy!