I know, it's been a while. I thought I'd finally break the ice with a little something I learned while doing a side project using Clojure's core.async library.
First, the project. The customer support folks at my company use Zendesk for keeping track of tickets. The software team recently started using Slack as our chat software and I thought that it'd be neat to feed new Zendesk tickets into a Slack channel. Sometimes there's a bit of a disconnect between the customer support team and the software team, and this might help narrow the gap. So I looked around for an existing integration, decided there was none, and started coding. Well, I was wrong. Slack already does have a published integration. Lesson learned: the best code is the code that you don't write yourself.
Before I realized this lesson, I got about halfway to writing my own little integration in Clojure using core.async. It was a lot of fun.
I wound up with a few functions that executed
update-state-loop: int (freq) -> go channel.This function updates the program's state, represented by a Clojure
freqamount of time. Program state included: (1) the last time since we updated, and (2) how many tickets we sent that day. If I had spent more time understanding the Zendesk API, I could probably eliminate this state from the program all together by setting start and end bounds on my queries.
latest-tickets-loop: channel (in), channel (out), freq -> go channel.This function takes data from
in, and puts the newest tickets (depending on the state of the program) to
messenger-loop: ch -> go channel.This function takes data from
printlnon each one. Later, this function could be replacted with a function that would send off reformatted tickets to post on Slack.
There is also a function that fetched data from Zendesk every
freq amount of
data-ch: int (freq) -> go channel
Looking at these function signatures makes me realize two things:
It looks like I wrote functions with "-loop" at the end to indicate that they execute and return go channels. Hmm, didn't notice that before. Interesting. I'm not sure whether I should distinguish between regular channels and go channels, so I just will.
Whoops, I got the units upside down on freq. This variable should be called
period. The units of frequency are inverse of time: "1 / time," whereas I'm using the parameter to represent a time interval.
Anyway, with this hacky stuff I attempted to wire these three loops together
(defn run  (let [freq (* 30 1000) stream (data-ch freq) pipe (chan)] (update-state-loop freq) (latest-tickets-loop stream pipe freq) (messenger-loop pipe)))
But when I executed
run, nothing happenend. Mysterious. I had some luck that
day and a muse spoke to me: "wrap the
let-block in a blocking take" it said.
And I did like this:
(defn run  (<!! (let [freq (* 30 1000) stream (data-ch freq) pipe (chan)] (update-state-loop freq) (latest-tickets-loop stream pipe freq) (messenger-loop pipe))))
Voila, it worked! But why? I had to think about it. I realized that
created without a buffer, so it was a blocked channel. Attempts to write to it
block until something else tries to pull from it. So when I wrapped everything
in the blocking take, I was taking from
(messenger-loop pipe), a go channel,
which then took from
pipe which then pulled from stream and opened things up
data-ch to put data into the
The code is useless, but hey, at least I enjoyed writing it, and it gave me an
excuse to break blog silence. I'd be interested in what you think about this
sort of design: separate asynchronous go blocks executing procedures that could
easily be done synchronously in one big
(while true ...) expression. Am I
just trigger happy with my new toy, the go-routine? Probably =)