Cook, little pot, cook!
– The Magic Porridge Pot, Brothers Grimm
I’ve just released littlepot, tiny library devoted to transform batched request into single element request.
Imagine you develop a service which return one random image and some witty text below. As an image provider, you have chosen some http://image.provider.com
, because they have a free limited API. Their endpoint http://image.provider.com/api/random
is exactly what you need.
You wrote a function to get batched data.
(def get-batch []
(-> api-endpoint
http/get
json/parse))
But your service need to return one image (get-image)
You lazy enough, so you just return random image from fresh batch.
(defn get-image []
(rand-nth (get-batch)))
Simple enough, unless you’ll encounter RateLimitException
because guys from image.provider.com
too greedy and allow only 100 requests per hour, but you just utilize 2% of all images.
Obvious solution: save images somewhere and return from there.
Congratulations, you discovered state, your life will never be the same.
Assume you introduced atom over list to save the data from the batch. Now every time you retrieve image, you need to remove it from cached batch.
(defn get-image []
(let [element (first @batch)]
(swap! batch rest)
element))
Good enough, but the cache is not infinite, you need to refill it when data is over.
(defn get-image []
(when (empty? @batch)
(fill batch))
(let [element (first @batch)]
(swap! batch rest)
element))
All clients are blocked until you cache is filled and see little latency.
What if we could send request in background, allowing clients still retrieve the data?
Meet littlepot, solutions to some of these problems.
Storage. It is backed by clojure.lang.PersistentQueue
, clojure queue implementation, so you don’t need to care about efficient storage.
Autofill. It sends request for next batch in a background, when your cached data is close to exhaustion, so the process of filling cache goes automatically and silently.
Non-blocking. You do not need to wait when data appears in cache; if something there, return it, if not, return :no-data.
Composable. Having function to retrieve single element (get-one) you can easily get fifty elements by calling (take 50 (repeatedly get-one)).
Concurrency. It encapsulates whole state in ref and uses STM, so multiple consumers allowed. Also, guaranteed that at most one batch will be in progress.
Getting Started Guide or some Advanced Usages could be found on github.
mishadoff 04 October 2015