Wednesday, January 4, 2012

The simplest EventBus

I've been playing a lot with event buses, domain events, messaging, or whatever you want to call it. I've seen some open source event buses but they looked pretty complicated and often had a lot of extra baggage that clouded what I was interested in: exploring different ways to decouple collaborating classes. I had enough of that and set out to find the simplest thing that would get the job done and here's what I came up with:
public static class EventBus
{
  private static List<Action<object>> handlers = new List<Action<object>>();
  
  public static void Subscribe(Action<object> handler)
  {
    handlers.Add(handler);
  }
  
  public static void Publish(object e)
  {
    handlers.ForEach(handler => handler(e));
  }
}

That's it. No unsubscribing, multithreading, xml configuration, flexible dispatching strategies, subscription tokens, base classes, interfaces, or attributes. You don't have to use any specific interfaces to subscribe or publish. You don't even have to instantiate it - it's always ready. Anything can subscribe, anything can publish, from anywhere in your project. Anything can be published and every subscriber will get it. Want to publish an AccountClosed domain event? Go for it. Want to publish a naked string or int? Go ahead. How about publishing your entire Windows Form? I'm not sure why you would, but this won't stop you.

Just one line to wire-up a subscription (often called from Main):
public static void AddSubscriptions(ILogger logger, PopulationReport pop, MainApp app)
{
  EventBus.Subscribe(e => logger.Log(e.GetType().Name + ":" + e.ToString()));

  EventBus.Subscribe(app.HandleEvent);

  EventBus.Subscribe(e => {
    if (e is CreatureCreated)
      pop.CreatureCreated(((CreatureCreated)e).Creature);
    else if (e is CreatureDied)
      pop.CreatureDied(((CreatureCreated)e).Creature);
  });
}

And one line to publish (often called from domain objects):
public void Die()
{
  this.health = 0;
  EventBus.Publish(new CreatureDied(this));
}

It can be a bit on the slutty side since it's globally accessible and subscribers will get everything that's published, but that comes with benefits too. This is a good starting point and I haven't had any problems with it: the type checking is minimal for subscribers, I've never tried to subscribe a null, and I don't mind having my domain objects explicitly depend on it. You could add a ClearSubscribers method or a SetInstance method for unit testing, but I just use a single subscriber that dumps all events into a list when a unit test needs to ensure that specific events were created. In the last few at home projects I've worked on this has been the only static class I've used since this is one of the very few times that I'm in favor of a static class with behavior and state.

I find it much easier to learn about something when it's striped of all the unnecessary fluff. Event busses, domain events, messaging, whatever you want to call it is, in my mind at least, really just easy, ubiquitous, and loosely coupled communication.

2 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. your post was very inspiring! I mentioned it in my post, too: http://itados.blogspot.co.at/2014/05/super-simple-event-bus-c.html

    ReplyDelete