Osgood::Server - Event Repository


Osgood-Server documentation Contained in the Osgood-Server distribution.

Index


Code Index:

NAME

Top

Osgood::Server - Event Repository

SYNOPSIS

Top

    create a database (mysql in our example)
    mysql -u root your_database < sql/schema.sql
    edit osgood_server.yml
    script/osgood_server_server.pl

DESCRIPTION

Top

Osgood is a passive, persistent, stateless event repository.

* Passive: Osgood doesn't seek out events, it only waits for notification of them.

* Persistent: Events are durable, they do not disappear if the server goes offline.

* Stateless: Events in Osgood are read-only. You cannot change them.

What's all that fancy talk mean? Osgood is a system wherein you record the fact that something happened. A client library (Osgood::Client that allows you to send events to Osgood and to later retrieve them.

Effectively we are talking about an implementation of the Publisher / Subscriber model. This implementation is built using HTTP as the medium and a relational database as the backing store.

An example may help to illustrate the usefulness.

Imagine a company that sells widgets. Sometimes, customers are unhappy and cancel the widgets. When a customer calls to do so, the call center dutifully cancels the order. This works fine and the world is a happy place.

One day a new marketing employee decides to try and woo these customers back. She asks you to build a system to send an email to a customer that cancels, promising a $5 coupon to come back for more widgets! This is a great idea, and it is a great chance to use Osgood.

When the cancellation happens, you add a smidge of code that uses Osgood::Client to send an event. You might use descriptions like:

  my $event = Osgood::Event->new(
      object    => 'Order',
      action    => 'canceled',
      params    => {
          id    => 12345
      }
  );
  my $list = Osgood::EventList->new(events => [ $event ]);
  $client->send($list);

Meaning that Order #12345 was canceled.

Later, you whip up a job that runs every hour that queries Osgood for all the orders that have been canceled.

  $client->query({
      object    => 'Order',
      action    => 'canceled',
  });

Now you have a list! But every call returns ALL the Order canceled events that happened, ever! The query method provides a bunch of constraints to fix that. The most useful of which is 'id'. Providing query with an id limits the returned events to all the events that have an ID GREATER THAN THE ONE PROVIDED.

There are other constraints provided in Osgood::Server::Controller::Event such as date_before and date_after.

The advantage of this implementation is that you can create subscribers to an event without adding any new code to your cancellation process. State is maintained by the jobs that do the querying, however you choose to implement it. We use ids, you might use dates.

IMPORTANT:

PERFORMANCE

Top

Note: See the accompanying section of Osgood::Client as well.

Osgood uses some DBIx::Class shortcuts to pull results faster. Depending on database hardware, small numbers of events (hundreds) should be really fast. Tests have been conducted on lists of 10_000 events and the response time still falls within ::Client's default 30 second timeout on modern hardware.

SEE ALSO

Top

Osgood::Server::Controller::Root, Catalyst, Osgood::Client

AUTHORS

Top

Lauren O'Meara

Cory 'G' Watson <gphat@cpan.org>

CONTRIBUTORS

Top

Guillermo Roditi (groditi) Mike Eldridge (diz)

COPYRIGHT AND LICENSE

Top


Osgood-Server documentation Contained in the Osgood-Server distribution.

package Osgood::Server;

use strict;
use warnings;

use Catalyst::Runtime '5.70';

# Set flags and add plugins for the application
#
#         -Debug: activates the debug mode for very useful log messages
#   ConfigLoader: will load the configuration from a YAML file in the
#                 application's home directory
# Static::Simple: will serve static files from the application's root 
#                 directory

use Catalyst qw/-Debug ConfigLoader Static::Simple Params::Nested/;

our $VERSION = '2.0.1';
our $AUTHORITY = 'cpan:GPHAT';

# Configure the application. 
#
# Note that settings in osgood_server.yml (or other external
# configuration file that you set up manually) take precedence
# over this when using ConfigLoader. Thus configuration
# details given here can function as a default configuration,
# with a external configuration file acting as an override for
# local deployment.

__PACKAGE__->config( name => 'Osgood::Server' );

# Start the application
__PACKAGE__->setup;


sub add_from_list {
    my ($self, $list) = @_;

    my $iter = $list->iterator;
    # count events
    my $count = 0;
    my $error = undef;

	my $schema = $self->model('OsgoodDB')->schema;
	$schema->txn_begin;

    while (($iter->has_next) && (!defined($error))) {
        my $event = $iter->next;

        # find or create the action
        my $action = $self->model('OsgoodDB::Action')->find_or_create({
            name => $event->action
        });
        if (!defined($action)) {
            $error = "Error: bad action " . $event->action();
            last;
        }
        # find or create the object
        my $object = $self->model('OsgoodDB::Object')->find_or_create({
            name => $event->object
        });
        if (!defined($object)) {
            $error = "Error: bad object " . $event->object;
            last;
        }
        # create event - this has to be a new thing. no find here. 
        my $db_event = $self->model('OsgoodDB::Event')->create({
            action_id => $action->id,
            object_id => $object->id,
            date_occurred => $event->date_occurred
        });
        if (!defined($db_event)) {
            $error = 'Error: bad event ' . $event->object . ' '
                . $event->action . ' ' . $event->date_occurred;
            last;
        }
        # add all params
        my $params = $event->params;
        if (defined($params)) {
            foreach my $param_name (keys %{$params}) {
                my $event_param = $self->model('OsgoodDB::EventParameter')->create({
                    event_id => $db_event->id,
                    name => $param_name,
                    value => $params->{$param_name}
                });
                if (!defined($event_param)) {
                    $error = 'Error: bad event parameter' .  $param_name .
                             ' ' .  $params->{$param_name};
                }
            }
        }

        # increment count of inserted events
        $count++;
    }

    if (defined($error)) {      # if error, rollback
        $count = 0;             # if error, count is zero. nothing inserted.
        $schema->txn_rollback;
    } else {                    # otherwise, commit
        $schema->txn_commit;
    }

    return ($count, $error);
}

1;