Like many, I found it easy to immediately criticize the Slack Web and RealTime API design. I would have wanted a RESTful implementation and not an RPC one. I would have liked for it to use HTTP and fail with an HTTP error code instead of sending clients JSON with an ok
field to yield errors. I would have liked to use HTTP verbs for CRUD and not make everything GET
or POST
. I would be excited for calling the Web API a lot less and seeing a lot more functionality available via the RealTime API.
But as I iterated on what has become the de facto standard Slack Ruby client I learned to enjoy more and more aspects of the Slack API design. I spent a bit of time reading the source code of the Slack Node.js client, especially the 2.0 rewrite, and concluded that many of the API’s choices were remarkably well-suited to the API’s primary goal - enabling rich RealTime clients.
One of the most interesting design choices is to send a ton of data to a RealTime client on rtm.start
(one of my small teams gets 43Kb, bigger ones probably easily get a megabyte). This includes an initial set of team metadata and a message server WebSocket URL. This is a big JSON blob, with team information, all channels, groups, users, bots, recent channel messages, and more. The method supports some interesting arguments, including simple_latest: true
or no_unreads: true
, obviously aimed at reducing the size of the response.
In slack-ruby-client 0.6.0 (and in the node client), this data is being saved in a local store.
The store exposes users
, channels
, teams
, etc.
Every time data changes in Slack, an event is delivered to every connected client. For example, when a channel is renamed a channel_rename event is sent.
This is handled by a channel_rename
handler.
What’s in this for you? With the 0.6.0 release of slack-ruby-client you can access an up-to-date local store with all the information otherwise available via slow Web API calls. Instead of calling client.web_client.user_info(user: 'U1234567')
to get user information, do client.users['U1234567']
. It’s a table lookup, so this is obviously a lot faster. A store can also be disabled with specifying store_class: nil
to the Slack::RealTime::Client
initializer, as the amount of data stored in memory can become fairly onerous and isn’t always necessary.
What about the store getting out of sync? Slack maintains a queue of events server-side and seems to either guarantee or try it’s best to deliver all messages, regardless of network conditions. This means that as long as the client was able to maintain a websocket connection, all events will eventually arrive in-order.
You probably also don’t want a store backed by a database without doing additional work - you want an in-memory one for the duration of the RealTime session. There’re a few methods that can help rewind events and catch up, including channels.history
, groups.history
, im.history
and mpim.history
, which call for a more sophisticated implementation.
Give slack-ruby-client 0.6.0 a try from Github HEAD, gem 'slack-ruby-client', github: 'dblock/slack-ruby-client'
. It will ship shortly.