perlasynchronousanyevent

Writing good object-oriented code under AnyEvent


We're building a large application with complex logic, which is composed of modules. I used to build larger scale methods of simpler methods, for instance,

# fig. 1   
package Foo;
sub highlevel {
    my ($self, $user, $event) = @_;
    my $session = $self->get_session($user);
    my $result = $self->do_stuff($session, $event);
    $self->save_session($session);
    return $result;
};

(this is simplified of course). Results are returned, exceptions are thrown, everyone's happy.

Now, we're making a move to AnyEvent. My module is NOT the uppermost level, so I can't do just

# fig. 2
my $cv = AnyEvent->condvar;
# do stuff
return $cv->recv;

Most AE modules I've seen so far work like this:

# fig. 3
$module->do_stuff( $input, 
    on_success => sub { ... }, 
    on_error => sub { ... }
);

So I'm done rewriting the lower-level methods and tried to proceed with highlevel() and...

# fig. 4
package Foo;
sub highlevel {
    my ($self, $user, $event, %callbacks) = @_;
    my $done = $callbacks{on_success};
    my $error = $callbacks{on_error};
    $self->get_session( $user,
        on_error => $error,
        on_success => sub {
             my $session = shift;
             $self->do_stuff( $session, $event,
                  on_error => $error,
                  on_success => sub { 
                       my $result = shift;
                       $self->save_session( $session,
                            or_error => $error,
                            on_success => sub { $done->($result); }
                       );
                  }
              );
          }
     );
 };

Not exactly beautiful. I call it "the Infinite ladder".

Now the next thing I could come up with was an ad-hoc state machine where highlevel() is broken up into _highlevel_stage1(), _highlevel_stage2() etc. But that doesn't satisfy me as well (it's unmaintainable, and thinking of good names instead of stageXX gives me headache).

We're already looking into a full-blown state machine to drive the whole app, but having to add a transition for every interaction looks a bit too generous to me.

So the question is: What are the best practices for writing modules that implement business logic (fig. 1) for running within an AnyEvent app (fig. 3)?


Solution

  • You may want to encapsulate it in a Future object using the Future module. That adds syntactical sugar to make this cleaner.