| Object-Recorder documentation | Contained in the Object-Recorder distribution. |
Object::Recorder - Records method calls into a serializable data structure
This module makes it possible to record method calls issued to a set of objects inti a serializable container which can later be replayed, perfoming the actual method calls.
use Object::Recorder;
# start recording method calls to an instance of My::Object
# will build the object by calling My::Object->new( @params )
my @params = ('constructor', 'args');
my $obj = Object::Recorder->record(
'My::Object',
new => @params
);
my @args = (1, 2, 'whatever');
$obj->some_method_call(@args);
# this object will be blessed directly without calling any constructors
my $another_obj = Object::Recorder->record('Another::Object');
# $another_object will only be created when $obj is replayed
$obj->another_method_call($another_object);
# it's ok to have return values (currently, only 1 is supported)
my $return = $another_object->return_something();
# this will DWIM
$return->call_method_on_returned_value();
And then, somewhere else:
$obj->replay();
In this case, replaying will perform these steps:
my $obj1 = My::Object->new('constructor', 'args');
$obj1->some_method_call(1, 2, 'whatever');
my $obj2 = bless {}, 'Another::Object';
$obj1->another_method_call($obj2);
my $ret = $obj2->return_something();
$ret->call_method_on_returned_value();
This can be useful for several reasons. For instance, it could be used in the creation of task objects which would then be processed by a cluster of worker servers, without the need to update the worker code for each new type of task.
It seems that this feature could also be useful in some sort of caching scheme besides being useful in distributed systems in general.
This method starts the recording process and returns an object which can be
used as if it were an instance of $class_name. If $constructor is not
given, the object will be build by directly blessing it into the given
$class_name. If $constructor is given, this method will be used as the
constructor method name, which will be called with the given @args (if any).
As a debug helper, if $Object::Recorder::BYPASS_STORAGE is set, this method
will skip the recording process and call the constructor directly. This can be
helpful is something's going wrong.
Replays the storage object, performing the stored method calls.
Should return the storage object's class name (used by create_storage).
Should return a suitable storage object.
Currently, this module currently only supports methods calls which have up to a single return value and it also doesn't play well with context-sensitive return values. This limitation could be worked around but would involve some trickery. I'll probably try implementing it if someone asks me to, but it works good enough for my own needs.
It won't record direct accesses to the object, only method calls. So if you
need to fiddle with the object's internal attributes, this module won't work
for you. Implementing this would require tie()ing the storage object and
recording that, too.
In other words:
$obj->{some_field} = 'some_value';
won't be recorded.
Patches are welcome.
Please report any bugs or feature requests to
bug-object-recorder at rt.cpan.org, or through the web interface at
http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Object-Recorder.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.
Nilson Santos Figueiredo Junior, <nilsonsfj at cpan.org>
Copyright (C) 2007 Nilson Santos Figueiredo Junior. Copyright (C) 2007 Picturetrail, Inc.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Object-Recorder documentation | Contained in the Object-Recorder distribution. |
package Object::Recorder; use warnings; use strict; use Memoize; use Object::Recorder::Storage;
our $VERSION = '0.01'; # this is a debug switch # if true will call the underlying objects directly instead of recording our $BYPASS_STORAGE = 0;
sub record { my $class = shift; return $class->create_storage( @_ ) unless $BYPASS_STORAGE; # debug mode: don't record, execute directly my ($rec_class, $constructor, @args) = @_; return $constructor ? $rec_class->$constructor(@args) : bless {}, $rec_class; }
sub replay { my $class = shift; my ($store, $obj) = @_; my $retval = $class->_replay($store, $obj); # cleanup the internal cache after replay calls Memoize::flush_cache('_evaluate_arg'); Memoize::flush_cache('_get_obj'); return $retval; }
sub storage_class { 'Object::Recorder::Storage' }
sub create_storage { shift->storage_class->new(@_) } # private methods sub _evaluate_arg { my $class = shift; my ($obj) = @_; return $obj unless ref $obj eq $class->storage_class; return $class->_replay($obj); } # memoizes the appropriate methods memoize('_evaluate_arg'); memoize('_get_obj'); sub _get_obj { my $class = shift; my ($store, $obj) = @_; unless (defined $obj) { my $object_class = $store->{object_class}; # make sure the module is loaded eval "require $object_class;"; if (my $constructor = $store->{constructor}) { my @args = map { $class->_evaluate_arg($_) } @{$store->{args}}; # we should call a constructor method $obj = $object_class->$constructor( @args ); } else { # otherwise, create a standard object $obj = bless {}, $object_class; } } $store->{expansion_pending} = 1; return $obj; } sub _replay { my $class = shift; my ($store, $obj) = @_; # return if we're not calling an specific constructor and also don't have # any recorded calls return if @{$store->{calls}} == 0 and not $store->{constructor}; die "no object nor object class" if not defined $obj and not $store->{object_class}; # only create the object if we don't have one yet $obj = $class->_get_obj($store, $obj); # only expand the object if it's not already being expanded if ($store->{expansion_pending}) { $store->{expansion_pending} = 0; for my $call (@{$store->{calls}}) { my $method = $call->{method}; # expand stored args my @args = map { $class->_evaluate_arg($_) } @{$call->{args}}; # recursively replay objects $class->_replay($call->{retval}, $obj->$method(@args)); } } return $obj; }
1;