Version 11, last updated by joedeveloper at June 24, 2010 15:56 UTC

Introduction

From wikipedia: Event-driven architectural pattern may be applied by the design and implementation of applications and systems which transmit events among loosely coupled software components and services. An event-driven system typically consists of event emitters (or agents) and event consumers (or sinks). Sinks have the responsibility of applying a reaction as soon as an event is presented. The reaction might or might not be completely provided by the sink itself. For instance, the sink might just have the responsibility to filter, transform and forward the event to another component or it might provide a self contained reaction to such event. The first category of sinks can be based upon traditional components such as message oriented middle-ware while the second category of sinks (self contained online reaction) might require a more appropriate transactional executive framework.

We use this solution to avoid long running requests that block rails application cluster. We add events to the queue that will be processed in background. For example, in the past email alerts were processed by a 5 minutes cron job, now they are processed in real-time using the queue.

Components 

  1. Event generators – models, user actions

  2. Event channel – reliable_msg server with db or disk storage

  3. Processing engine – active messaging plugin that run application processors.

Development and production event queues

By default our application does not use a real queue. For most designers and developers we have a simple event queue that sends emails only for a limited number of event types. If you want to setup the event queue (required for creating new types of events) you should follow this instructions

Application Classes

This section will describe classes used in application for generating and processing events.

app/models/event.rb – this class contains object types, event types and filter_code method that return a symbol corresponding to event type and obj_type conbination.

lib/breakout/event_manager.rb – it contains LogMethods module that can be included in any class that wants to generate events. These methods are included in any ActiveRecord model, see config/initializers/rails_changes.rb

lib/breakout/event_object_factory.rb - Each event has a related object associated. This class creates the related object from an event. You will use this class to register a new event. If you plan to add new events for a new tool, check the Add new tools wiki.

lib/breakout/ui/ - This folder contains the classes and decorators necessary to display events for users. If your event just runs some background task, you don't need this. However,  if your event needs to be displayed to the user on the Stream Tab, sent in real time alerts or by RSS. Make sure to check the Event format specification wiki.

app/processors/ - This folder contains the processors that are used to process our main events. If you are creating a new processor for a new tool, you should NOT add it here. Instead, you should check the Add new tools wiki.

How to add new event types

To add new event types, you must add the constant in the event model class in app/models/event.rb or, if you are building a new tool, add it inside your tool extension folder.

Then, you need to register the constant in the EventObjectFactory, which you can do by calling this method: Breakout::App.instance.registry.event_object_factory

How to add event processor

If you want to add an event processor for a new tool, check Add new tools wiki.

I will illustrate the process using one example that was implemented in Breakout.

Problem: We added Git tool to the application and we had a problem synchronizing user ssh key. Each time user changed his key or new member was added to the team, owner of the space needed to click synchronize ssh key button on Trac/Git tab. (note, we have since enhanced breakout to address this).

Solution: We will use events to make this happen without any user activity required. With events, using direct remote call to control center can slow down application cluster if remote control center is slow or has other problems. We will add one new event type: when user changed his ssh key in profile – user_ssh_key_changed. When user is added/removed from the team, we save events with event_type: ADD_TO_TEAM, REMOVE_FROM_TEAM, LEAVE_TEAM

 

1) LOG THE EVENT

We need to save event when user changes his ssh key. Change user model

# after_find is disabled by default
def after_find() end
after_find :store_original_values

def store_original_values
@old_ssh_key = ssh_pub_key
end

before_save :check_ssh_key
def check_ssh_key
if @old_ssh_key != ssh_pub_key
@old_ssh_key = ssh_pub_key
# send event that user key was changed
log_custom_event :event_type => Event::USER_SSH_KEY_CHANGED
end
end

Then we add an event router to route such events to git_sync_ssh_keys processor.

2) GENERATE THE PROCESSOR

To generate new event processor:

$ ruby script/generate processor git_sync_ssh_keys
exists app/processors/
exists test/functional/
create app/processors/git_sync_ssh_keys_processor.rb
create test/functional/git_sync_ssh_keys_processor_test.rb
overwrite config/messaging.rb? (enter "h" for help) [Ynaqdh] n
skip config/messaging.rb
overwrite config/broker.yml? (enter "h" for help) [Ynaqdh] n
skip config/broker.yml
overwrite app/processors/application.rb? (enter "h" for help) [Ynaqdh] n
skip app/processors/application.rb
identical script/poller
$

 

3) CONFIGURE ACTIVE MESSAGING

Edit config/messaging.rb and add

ActiveMessaging::Gateway.define do |s|
# other definitions
# ...
s.destination :git_sync_ssh_keys, '/queue/GitSyncSshKeys'
end

 

4) WRITE THE CODE FOR YOUR PROCESSOR

In app/processors/ git_sync_ssh_keys_processor.rb we fill code for the on_message method:

def on_message(event)
if event.event_type is add/remove team member
space.git_tool.upload_pub_keys
else update user ssh key
find user with ID = event.obj_id
for space in all user spaces do
find first git_tool in space_tools
git_tool.update_user_ssh_key
end
end # if
end

 

5) If the code in your processor is too long or too complex, use a component to help you

Now, we just need to add one method to TracGitTool: update_user_ssh_key(user_id, ssh_key)

Resources