Ruby Pub/Sub API Specification Draft
Purpose
The pub/sub design is idiomatic to WebSocket and EventSource approaches as well as other reactive programming techniques.
The purpose of this specification is to offer a recommendation for pub/sub design that will allow applications to be implementation agnostic (not care which pub/sub extension is used)*.
Simply put, applications will not have to worry about the chosen pub/sub implementation or about inter-process communication.
This should simplify the idiomatic subscribe / publish approach to real-time data pushing.
* The pub/sub extension could be implemented by any external library as long as the API conforms to the specification. The extension will have to manage the fact that some servers fork and manage inter-process communication for pub/sub propagation (or limit it's support to specific servers). Also, servers that opt to implement the pub/sub layer, could perform optimizations related to connection handling and pub/sub lifetimes.
Pub/Sub handling
Conforming Pub/Sub implementations MUST implement the following pub/sub related methods within the WebSocket/SSE client object (as defined in the Rack WebSockets / EventSource specification draft):
subscribe(to, opt = {}) { |from, message| optional_block }wheretois a named channel andoptis a Hash object that SHOULD support the following possible keys (unsupported keys MUST be ignored)::matchindicates a matching algorithm should be applied to thetovariable (tois a pattern).Possible (suggested) values should include
:redis,:natsor:rabbitmq. Pub/Sub implementations MAY support none, some or all of these common pattern resolution schemes.:handleris an alternative to the optional block. It should accept Proc like objects (objects that answer to.call(from, msg)).If an optional
block(or:handler) is provided, if will be called when a publication was received. Otherwise, the message alone (not the channel data) MUST be sent directly to the WebSocket / EventSource client.:asaccepts either:textor:binarySymbol objects.This option is only valid if the optional
blockis missing and the connection is a WebSocket connection. Note that SSE connections are limited to text data by design.This will dictate the encoding for outgoing WebSocket message when publications are directly sent to the client (as a text message or a binary blob).
:textwill be the default value for a missing:asoption.Servers MAY ignore this value if they set the message type (text/binary) based on UTF-8 validation.
If a subscription to
toalready exists, it should be replaced by the new subscription (the old subscription should be canceled / unsubscribed).When the
subscribemethod is called within a WebSocket / SSE Callback object, the subscription must be closed automatically when the connection is closed.The
subscribemethod MUST returnnilon a known failure (i.e., when the connection is already closed), or any truthful value on success.A global variation for this method (allowing global subscriptions to be created) SHOULD be made available as
Rack::PubSub.subscribe.When the
subscribemethod isn't called from within a connection, it should be considered a global (non connection related) subscription and an exception should be raised if ablockor:handlerisn't provided by the user.unsubscribe(from)should cancel a subscription to thefromnamed channel / pattern.publish(to, message, engine = nil)(preferably supporting named arguments) where:toa String that identifies the channel / stream / subject for the publication ("channel" is the semantic used by Redis, it is similar to "subject" or "stream" in other pub/sub systems).messagea String with containing the data to be published.engineroutes the publish method to the specified Pub/Sub Engine (see later on). If none is specified, the default engine should be used. Iffalseis specified, the message should be forwarded to all subscribed clients.
The
publishmethod must returntrueif a publication was scheduled (not necessarily performed). If it's already known that the publication would fail, the method should returnfalse.An implementation MUST call the relevant PubSubEngine's
publishmethod after performing any internal book keeping logic. Ifengineisnil, the default PubSubEngine should be called. Ifengineisfalse, the implementation MUST forward the published message to the actual clients (if any).A global alias for this method (allowing it to be accessed from outside active connections) MAY be defined as
Rack::PubSub.publish.
Implementations MUST implement the following methods in one of their public classes / modules (iodine implements these under Iodine::PubSub):
attach(engine)whereengineis aPubSubEngineobject, as described in this specification.When a pub/sub engine is attached, the implementation MUST inform the engine of any existing or future subscriptions.
The implementation MUST call the engine's
subscribecallback for each existing (and future) subscription.The implementation MUST allow multiple "engines" to be attached when multiple calls to
attachare made.detach(engine)whereengineis a PubSubEngine object as described in this specification.Removes an engine from the pub/sub system. The opposite of
attach.default = enginesets a default pub/sub engine, whereengineis a PubSubEngine object as described in this specification.Implementations MUST forward any
publishmethod calls to the default pub/sub engine, unless anengineis specified in arguments passes to thepublishmethod.defaultreturns the current default pub/sub engine, where the engine is a PubSubEngine object as described in this specification.reset(engine)whereengineis a PubSubEngine object as described in this specification.Implementations MUST behave as if the engine was newly registered and (re)inform the engine of any existing subscriptions by calling engine's
subscribecallback for each existing subscription.
Implementations MAY implement pub/sub internally (in which case the default engine is the server itself or a server's module).
However, servers MUST support external pub/sub "engines" as described above, using PubSubEngine objects.
PubSubEngine objects MUST implement the following methods:
subscribe(channel, match=nil)this method performs the subscription to the specified channel.If
matchis a Symbol that the engine recognizes (i.e.,:redis,:nats, etc'), the engine should behave accordingly. i.e., the value:redison a Redis engine will invoke the PSUBSCRIBE Redis command.The method must return
trueif a subscription was scheduled (or performed) orfalseif the subscription is known to fail.This method will be called by the server (for each registered engine). The engine may assume that the method would never be called directly by an application.
unsubscribe(channel, match=nil)this method performs closes the subscription to the specified channel.The method's semantics are similar to
subscribe.This method will be called by the server (for each registered engine). The engine may assume that the method would never be called directly by an application.
publish(channel, message)where bothchannelandmessageare String objects.This method will be called by the server when a message is published using the engine.
The engine MUST assume that the method might get called directly by an application.
When a PubSubEngine object receives a published message, it should call:
Foo::PubSub.publish channel, , false