Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)?

like image 213
Dallaylaen Avatar asked Jun 15 '12 07:06

Dallaylaen


People also ask

What is object object oriented programming (OOP)?

Object Oriented Programming (OOP) is a software design pattern that allows you to think about problems in terms of objects and their interactions. OOP is typically done with classes or with prototypes. Most languages that implement OOP (e.g., Java, C++, Ruby, Python) use class-based inheritance. JavaScript implements OOP via Prototypal inheritance.

What is object-oriented programming?

“Object-oriented programming (OOP) is a software programming model constructed around objects. This model compartmentalizes data into objects (data fields) and describes object contents and behavior through the declaration of classes (methods).

What are the three cornerstones of object oriented programming?

The three cornerstones of OOP — Inheritance, Encapsulation, and Polymorphism — are powerful programming tools/concepts but have their shortcomings: Inheritance promotes code reuse but you are often forced to take more than what you want.

Can you do object oriented programming in a non-object oriented language?

In my view, you can also do object oriented programming in languages which are not supporting that officially (like C) and you can also work in a non object oriented way in pure OOP languages (like Java or C#). Let me show you my understanding about OOP.


2 Answers

Executive Summary: you either want inversion of control (threads with Coro that block) or a state machine.

You could use Coro, which can convert the infinite ladder into linear code (by inversion of control), e.g. using Coro::rouse_cb/rouse_wait, or some of the Coro::AnyEvent functions:

   do_sth cb => sub { ...

becomes (if the callback is only called once):

   do_sth cb => Coro::rouse_cb;
   my @res = Coro::rouse_wait;

Your only other option is to use a state machine, e.g. using an object (there are many ways to implement state machines, especially in Perl):

my $state = new MyObject;

do_something callback => sub { $state->first_step_done };

And in first_step done, you register a callback for $self->next_state_done etc.

You can also look into some cpan modules, such as AnyEvent::Blackboard or AnyEvent::Tools - I haven't used them myself, but maybe they can help you.

As for condvars, I am personally not so hot about using them as the only means for an API - I prefer callbacks (as condvars are valid callbacks this allows both), but condvars do allow you to raise exceptions in the consumer (via the croak method).

like image 71
Remember Monica Avatar answered Oct 06 '22 15:10

Remember Monica


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

like image 22
cstamas Avatar answered Oct 06 '22 13:10

cstamas