Writing Good Object Oriented Code in AnyEvent

We are creating a large application with complex logic, consisting of modules. I used simpler methods to create larger methods, for example,

# 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, of course, is simplified). Results are returned, exceptions are thrown, everyone is happy.

Now we move on to AnyEvent. My module is NOT the highest level, so I can not do only

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

Most of the AE modules I've seen so far work as follows:

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

So, I finished rewriting the lower level methods and tried to continue working 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 quite pretty. I call it the "Endless Staircase."

Now the next thing I could think of was an ad-hoc state machine, where highlevel () is broken down into _highlevel_stage1 (), _highlevel_stage2 (), etc. But this does not satisfy me (this is unacceptable, and thinking of good names instead of stageXX gives me a headache).

We are already looking at a full-sized state machine to manage the entire application, but adding a transition for each interaction seems too generous for me.

So, the question arises: what are the best methods for writing modules that implement business logic (Fig. 1) for working in the AnyEvent application (Figure 3)?

+6
source share
3 answers

Summary: You either want to invert the control (streams with a Coro block), or a state machine.

You can use Coro, which can convert an infinite ladder to linear code (by inverting control), for example. using Coro :: rouse_cb / rouse_wait or some of the Coro :: AnyEvent functions:

  do_sth cb => sub { ... 

becomes (if the callback is called only once):

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

The only other option is to use a state machine, for example. 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 the first_step file, you register a callback for $ self-> next_state_done, etc.

You can also see some cpan modules, such as AnyEvent :: Blackboard or AnyEvent :: Tools - I have not used them myself, but maybe they can help you.

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

+6
source

Well, one thing I can think of is to use the slightly modified Chain of Responsibility template:

 my $params = { user => $user, event => $event, session => undef }; my @chain = ('get_session', 'do_stuff', 'save_session', 'emit'); for my $index (0..$#chain) { my $current = $chain[$index]; my $next = $chain[$index + 1] || undef; $self->{$current}($params, on_error => sub { $self->error($params) }, on_success => sub { $self->{$next}($params) }, ); } 

This is a little rude, but I hope this points to the point. )

+3
source

You might want to encapsulate it in a Future object using the Future module. It adds syntactic sugar to make it cleaner.

+1
source

Source: https://habr.com/ru/post/918153/


All Articles