16 Apr 09

Hooking into the MindTouch Event Bus

With 9.02 out the door, I wanted to shed a bit more light on a new feature I briefly touched on before: The Deki Event Bus. This article mostly covers the purpose and benefits of events, only a brief overview of its implementation, but I promise to follow it up with some posts that walk through building real world use cases with the event bus.

What are Events all about?

MindTouch emits a number of events into the new PubSubService that is built into Dream. Every service is provided a Plug instance to the defaullt PubSubService making it simple to publish and subscribe to events. By subscribing to events, your service will be called automatically when an event occurs, allowing it to react to changes in the Wiki in real time. But events are a different extension model from the familiar model of making REST calls to interact with MindTouch, so there are a couple of things to be aware of:

Events are asynchronous and do not affect the action that caused them, which means that by the time you receive an event the cause of it has already completed. They cannot be used as hooks to intercept and alter existing behavior, although the receiver of the event could easily use the API to alter the resource referenced by the event.

Events are real time only, so if you are not subscribed when the event happens, you miss it. There is no back-log of events that is fed to subscribers. This means that if a service restarts and events happen before the restarted service re-subscribes, the events will be missed. Events should not be used as authoritative when it comes to the wiki’s state, as you may miss some  and drift out of sync. Instead they should be used as triggers to fetch the up-to-date information from the Wiki. Because of this, events generally do not carry a lot of data, trying to limit themselves to only immutable information such as canonical URIs and IDs for resources.

Events are multicast, so any number of services can subscribe to them without affecting another. This also means that you cannot assume that you are either the first or last to react to the event. Nor can you assume that just because you are acting on one event another event of the same type has not already occured.

Events provide a loose coupling where neither the sender nor the receiver has to know any implementation details of the other. Much in the same way as service features provide a loose coupling for synchronous interaction with MindTouch, events provide asynchronous notifications of actions occuring in MindTouch.

This last point is the true benefit of the event system: It allows us to change its implementation details of actions without affecting code that relies on those actions. Between the service api and the event schemas, there exist a contract for interactions. And this loose coupling benefit isn’t just something we think is beneficial to those who wish to extend MindTouch’s functionality. We are using it ourselves already in 9.02 because it allows us to break the Wiki into more independent components that are easier and safer to maintain or to swap out with alternative implementations.

Using Events

Since every dream service receives a plug to the default PubSubService, subscribing to events is as simple as creating a feature to receive events and posting a subscription against the PubSubService to register that feature.

Let’s assume that we have a service that wants to listen to all user logins. This service exists at http://myhost.com/userloginwatch/ and has a public feature for receiving the user login events (for illustrative purposes the feature is public so we don’t have to worry about authorization details):

[DreamFeature("POST:notify", "receive a login notification")]
public Yield PostLogin(
    DreamContext context,
    DreamMessage request,
    Result<DreamMessage> response) {
  ...
  response.Return(DreamMessage.Ok());
  yield break;
}

In order to hook this service into the event bus, it needs to post a subscription document, like the one below, against its pubsub Plug:

<subscription-set>
  <uri.owner>http://myhost.com/userloginwatch/</uri.owner>
  <subscription>
    <channel>event://*/deki/users/login</channel>
    <recipient authtoken="APIKEY">
      <uri>http://myhost.com/userloginwatch/notify</uri>
    </recipient>
  </subscription>
</subscription-set>

Our recipient is the PostLogin() feature which exists at http://myhost.com/userloginwatch/notify. In addition to the Uri, Deki needs to know that our service is authorized to receive this event and expects the Deki apikey as the authtoken for the recipient. Since we also want to remove our subscription when we’re done, we also need to capture the location at which the subscription is stored. This is generally done in the Start() of the service like this:

// post the subscription
XUri serverUri = Self.Uri.AsServerUri();
XDoc subscription =
  Result<DreamMessage> subscribe;
  yield return subscribe = PubSub.At("subscribers")
    .PostAsync(new XDoc("subscription-set")
      .Elem("uri.owner", serverUri.ToString())
      .Start("subscription")
        .Elem("channel", "event://*/deki/users/login")
        .Start("recipient")
          .Attr("authtoken", _apikey)
          .Elem("uri", serverUri.At("notify").ToString())
        .End()
      .End());

// retrieve the access key for our subscription,
// so we can later remove it
string accessKey = subscribe.Value.AsDocument()["access-key"].AsText;
XUri location = subscribe.Value.Headers.Location;

// add the access key as a cookie so it is automatically sent
Cookies.Update(
  DreamCookie.NewSetCookie("access-key", accessKey, location),
  null);

// and store our subscription location,
// so we can delete it in Stop()
XUri localLocation = location.AsLocalUri().WithoutQuery();
_subscriptionLocation = Plug.New();

With this done, every time a user logs in, our service will have its PostLogin() feature invoked with an event document like this:

<deki-event wikiid="{wikiid}" event-time="{datetime}">
  <channel>event://{wikiid}/deki/users/login</channel>
  <userid>{userid}</userid>
  <uri>{user-uri}</uri>
</deki-event>

This allows our service to asynchronously react to a user logging in, by tracking login stats, retrieving and examining the user, etc.

The present and future of Events

9.02 offers a rich set of events, tracking changes in MindTouch, that can be subscribed to. We see events as a significant extension facility, and are using it ourselves because of the benefits of its loose coupling, and to dogfood the technology for everyone else. We’ve shipped two services in 9.02 that use the event bus, the Lucene index service was rewritten to use events for queueing up resources that need to be indexed and the new Page Alerts system was introduced to create a mechanism for users to subscribe to Page changes.

I hope that events will prove to be a useful extension mechanism for all those who wish to add custom behavior to Deki. Currently these events are primarily based on actions that change the state of resources in MindTouch. In the future we will continue to expand the number of available events, likely adding more events based on operational actions. If there are any specific events you would like to see, please let me know in the comments.

2 Responses to “Hooking into the MindTouch Event Bus”

  1. [...] advanced features of DekiScript extensions written in C#. This is not the tutorial on the event bus I promised last week, which I am still working on and will blog about in the near future, but it introduces a number of [...]

  2. [...] promised previously, this article will go over building an extension to MindTouch using the Event Bus. This article is [...]

Leave a Reply

Copyright © 2011 MindTouch, Inc. Powered by