我们正在构建一个具有复杂逻辑的大型应用程序,它由模块组成。我曾经用更简单的方法构建更大规模的方法,例如,
# 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;
};
(当然这是简化的)。返回结果,抛出异常,每个人都很开心。
现在,我们正在转向AnyEvent。我的模块不是最高级别,所以我不能只做
# fig. 2
my $cv = AnyEvent->condvar;
# do stuff
return $cv->recv;
到目前为止,我见过的大多数AE模块都是这样的:
# fig. 3
$module->do_stuff( $input,
on_success => sub { ... },
on_error => sub { ... }
);
所以我完成了对低级方法的重写并尝试继续使用highlevel()和...
# 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); }
);
}
);
}
);
};
不完美。我称之为“无限阶梯”。
现在我接下来要做的是ad-hoc状态机,其中highlevel()被分解为_highlevel_stage1(),_ highlevel_stage2()等。但这也不能满足我(它是不可维护的,并且想到好名字而不是stageXX让我很头疼)。
我们已经在寻找一个完整的状态机来驱动整个应用程序,但是必须为每个交互添加转换对我来说看起来有点过于慷慨。
所以问题是:编写实现业务逻辑的模块(图1)在AnyEvent应用程序中运行的最佳实践是什么(图3)?
答案 0 :(得分:7)
执行摘要:您要么想要控制反转(Coro阻塞的线程),要么是状态机。
您可以使用Coro,它可以将无限梯形图转换为线性代码(通过控制反转),例如使用Coro :: rouse_cb / rouse_wait,或者一些Coro :: AnyEvent函数:
do_sth cb => sub { ...
成为(如果回调仅被调用一次):
do_sth cb => Coro::rouse_cb;
my @res = Coro::rouse_wait;
您唯一的其他选择是使用状态机,例如使用对象(有许多方法可以实现状态机,特别是在Perl中):
my $state = new MyObject;
do_something callback => sub { $state->first_step_done };
在first_step完成后,您为$ self-> next_state_done等注册了一个回调。
您还可以查看一些cpan模块,例如AnyEvent :: Blackboard或AnyEvent :: Tools - 我自己也没有使用它们,但也许它们可以帮助您。
至于condvars,我个人并不热衷于使用它们作为API的唯一方法 - 我更喜欢回调(因为condvars是有效的回调,这允许两者),但condvars允许你在消费者中引发异常(通过croak方法)。
答案 1 :(得分:3)
嗯,我能想到的一件事是使用略微修改的责任链模式:
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) },
);
}
这有点粗糙,但我希望它能说明问题。 )
答案 2 :(得分:1)
您可能希望使用Future
模块将其封装在Future对象中。这增加了语法糖,使这个更清洁。
答案 3 :(得分:1)
7年后访问这个问题,我似乎知道正确的答案:monad。协程,Future,Promise,异步/等待和有限状态机的(合理的实现)都是这些的子类型。
当然,the curse of the monad阻止我向他人解释。
如果我今天必须再次做一次,那我就已经有了某种promise对象。