If there is one thing I learned when working on D-Bus is that if you build a decent API, people will use it. Right now I am working on Kamaloka-js – JavaScript bindings for building JavaScript native AMQP clients. As AMQP starts moving towards 1.0 a new high level API, based loosely on JMS, is emerging to bridge the gap between 0.10 and 1.0. Previously with AMQP you pretty much worked with patterns on the protocol level which meant when the new specification came out you would pretty much have to rewrite to the new API. The new messaging based API seeks to decouple the protocol from the API for the most common use cases.
As I implement the JavaScript API there are some decisions I must make regarding how closely I stick to this new API as implemented in other bindings. Lets look at how the new API receives messages in Python:
...
conn = Connection.open(url.host, url.port)
ssn = conn.session()
rcv = ssn.receiver('amq.topic/org.j5live.demo')
while True:
try:
msg = rcv.fetch(timeout=timeout)
process_message(msg)
ssn.acknowledge()
except Empty:
break
except ReceiveError, e:
print e
break
If you notice the API is very much mainloop based. With every loop we fetch a message off the local queue. If there are no messages, fetch works very similar to the way poll does for sockets – sleep until woken up by network traffic or a timeout. The issue is if we did this in JavaScript we would block the UI and event propagation (newer browsers do expose threading but that feature can not be relied upon in all browsers). If you have ever mistakenly created an infinite loop in JavaScript you will understand how bad this is.
There are two options I see here, keep the fetch method and require the programmer to use timers to read from the local queue or, as currently implemented in my own high level API, attach callbacks to receivers.
The timer/fetch method has a couple of advantages. First it more closely resembles the API of the other bindings. Also it allows tighter management of the local queue. Since each time the timer is triggered we can determine how many messages we wish to process we can give some performance guarantees based on how long it takes to run operations in response to the messages. In this way we can dictate when a message is processed and tweak applications to run smoother. This comes at the price of added complexity which could be even more of a detriment to performance when in the wrong hands.
The callback API has the advantage of being more in line to what JavaScript developers expect. JavaScript is highly event/callback based with onFoo handlers everywhere. We currently work this way. When a message is decoded from the socket we dispatch it immediately to any callback registered to listen for it. The issue here is an application can get hammered with messages with no way to defer processing (except by implementing their own processing queue).
Why not implement both? This is an option but that doesn’t mean it is a good option. Multiple ways of doing the same thing often confuse new users. Understanding the differences between the API’s and the nuances between usecases are often more complex than either API alone. This could be daunting to a new user.
The trick is to make the API’s build on one another. For instance I could add an onReady handler which then allows the user to use the fetch API to grab any messages inside the handler. We would then set up an internal timer if the queue was not completely drained. This would require users be familiar with the fetch API without having to set up their own timer. If they wanted to have more control, they could set up their own timers instead of using the handler.
Any JavaScript developers have any insight as to the best way forward?
[read this post in: ar de es fr it ja ko pt ru zh-CN ]
Not a JavaScript programmer, but just wanted to point out that doing polling (your timer approach) is really bad for battery. If this would ever be used in a long-running process, don’t.
Comment by Johannes Berg — January 13, 2010 @ 1:21 pm
@Johannes
Good point, however you will be disappointed that polling is how comet socket stuff currently works (WebSockets should hopefully put an end to that). GMail for instance uses this to update your messages. In any case, I think I will run with the idea of only polling if there is data to be read, and also allowing a programmer to pause a receiver.
Comment by J5 — January 13, 2010 @ 2:48 pm
[...] this post in: ar de es fr it ja ko pt ru zh-CN [...]
Pingback by John (J5) Palmieri: It’s all about the API | TuxWire : The Linux Blog — January 13, 2010 @ 3:27 pm