资讯专栏INFORMATION COLUMN

Laravel深入学习6 - 应用体系结构:解耦事件处理器

HackerShell / 1598人阅读

摘要:别堵塞了传输层大多数事件处理器被当作传输层组件。解耦事件处理器开始本命题前,我们来使用一个示例。假想下把队列处理器用来发送消息给用户。尽量避免在事件处理器中掺杂太多的业务逻辑。

声明:本文并非博主原创,而是来自对《Laravel 4 From Apprentice to Artisan》阅读的翻译和理解,当然也不是原汁原味的翻译,能保证90%的原汁性,另外因为是理解翻译,肯定会有错误的地方,欢迎指正。

欢迎转载,转载请注明出处,谢谢!

应用体系结构:解耦事件处理器 介绍

现在我们已经介绍了很多使用Laravel 4构建健壮应用的特性,下面来深入挖掘更多的细节。本章我们将讨论诸如队列、事件这些众多事件处理器的解耦,也包括类似“类事件”结构的路由过滤。

别堵塞了传输层

大多数“事件处理器”被当作_传输层_组件。换言之,队列处理、事件触发器、或者一个外来请求都被用来调用某些调用处理。要像处理控制器一样处理这些事件处理器,并避免在其中涉及太多业务逻辑。

解耦事件处理器

开始本命题前,我们来使用一个示例。假想下把队列处理器用来发送SMS消息给用户。在发送消息之后,处理器讲发送了的消息记录成历史以便我们知道有哪些用户收到了这些消息。代码实现如下:

class SendSMS{

    public function fire($job, $data)
    {
        $twilio = new Twilio_SMS($apiKey);

        $twilio->sendTextMessage(array(
            "to"=> $data["user"]["phone_number"],
            "message"=> $data["message"],
        ));

        $user = User::find($data["user"]["id"]);

        $user->messages()->create(array(
            "to"=> $data["user"]["phone_number"],
            "message"=> $data["message"],
        ));

        $job->delete();
    }

}

仅测试这块代码,就可能遇到一些问题。首先,测试困难。Twilio_SMS类是在fire方法中实例化的,这意味着我们无法使用注入的方式模拟服务。其次,在处理器中我们直接用到了Eloquent模型,这就给测试带来了另外一个问题,我们必须在方法中进行真正的数据库访问。最后,我们在队列之外无法进行SMS消息发送。我们的SMS消息发送逻辑完全糅合在Laravel队列中了。

通过将逻辑提取到某一“服务”中的方法,我们可以将应用中的SMS消息发送逻辑从Laravel的队列服务中解耦出来。从而可以在应用中的任何地方发送消息。当我们进行了这种解耦处理,这种重构也是我们的代码变得更加具有可测性。

让我们来修改下代码:

class User extends Eloquent {

    /**
     * Send the User an SMS message
     *
     * @param SmsCourierInterface $courier
     * @param string $message
     * @return SmsMessage
     */
    public function sendSmsMessage(SmsCourierInterface $courier, $message)
    {
        $courier->sendMessage($this->phone_number, $message);

        return $this->sms()->create(array(
            "to"=> $this->phone_number,
            "message"=> $message,
        ));
    }

}

在这个重构的代码实例中,我们将发送消息的逻辑提取到User模型中。同时向该方法中注入SmsCourierInterface接口实现逻辑,使我们更好的测试逻辑中的方方面面。重构了短信发送逻辑之后,再对队列进行重构:

class SendSMS {

    public function __construct(UserRepository $users, SmsCourierInterface $courier)
    {
        $this->users = $users;
        $this->courier = $courier;
    }

    public function fire($job, $data)
    {
        $user = $this->users->find($data["user"]["id"]);

        $user->sendSmsMessage($this->courier, $data["message"]);

        $job->delete();
    }

}

在重构的示例中,可以看到,队列服务已经足够轻量。它在队列和我们_真正的_应用逻辑之间已经足够符合_传输层_这个概念。赞!这意味着我们可以在队列之外轻易的发送消息。最后,让我们编写一些测试代码:

class SmsTest extends PHPUnit_Framework_TestCase {

    public function testUserCanBeSentSmsMessages()
    {
        /**
         * Arrage ...
         */
        $user = Mockery::mock("User[sms]");
        $relation = Mockery::mock("StdClass");
        $courier = Mockery::mock("SmsCourierInterface");

        $user->shouldReceive("sms")->once()->andReturn($relation);

        $relation->shouldReceive("create")->once()->with(array(
            "to" => "555-555-5555",
            "message" => "Test",
        ));

        $courier->shouldReceive("sendMessage")->once()->with(
            "555-555-5555", "Test"
        );

        /**
         * Act ...
         */
        $user->sms_number = "555-555-5555";
        $user->sendMessage($courier, "Test");
    }
}
其他事件处理器

我们可以改进很多这种类型的“事件处理器”。将他们限定为简单的“传输层”来使用,能将复杂的业务逻辑很好的组织和解耦到框架之外。为了巩固下这种思想,下面我们举例一个路由过滤器,用它来验证用户是否为我们的“高级”订阅用户。

Route::filter("premium", function()
{
    return Auth::user() && Auth::user()->plan == "premium";
});

乍看像是没什么问题。这么小的代码能有啥问题呢?然而,在这么小的过滤中,也能意识到我们将应用的实现细节暴漏了出来。注意,我们在过滤中进行对plan属性进行了检测。“级别”的检测逻辑层紧紧的揉进了路由、传输层。如果我们将“高级”订阅用户的套餐存放到数据库或者用户模型中,这里又必须对我们的路由过滤器进行修改!

相应的,做些小的改编:

Route::filter("premium", function()
{
    return Auth::user() && Auth::user()->isPremium();
});

这样小的改编带来的效果是明显的,付出的代价也是小的。通过在模型中对用户是否属于高级订阅用户的判断,我们将路由中的检测逻辑解耦了出来。我们的过滤程序不在负责检测用户订阅级别的职责。相应的,它只需简单的询问用户模型即可。现在,如果订阅级别的判断存放在数据库中,路由过滤不需要更改任何代码!

该谁负责?

我们又一次讨论了_职责_的概念。牢记,一个类应有的职责是什么,和他涉及的范围是明确的。尽量避免在事件处理器中掺杂太多的业务逻辑。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/23009.html

相关文章

  • Laravel深入学习5 - 应用架构

    摘要:控制只是用来接收请求并请求逻辑处理类。事实上,业务逻辑无需感知网络,网络仅仅接入应用的传输机制,他不应超出应用中的路由和控制器的范畴。职责分离是编写健壮应用的关键。其他通常,类库应该以规范组织在我们的应用中。 声明:本文并非博主原创,而是来自对《Laravel 4 From Apprentice to Artisan》阅读的翻译和理解,当然也不是原汁原味的翻译,能保证90%的原汁性,另...

    lixiang 评论0 收藏0
  • Laravel最佳实践--事件驱动编程

    摘要:事件驱动编程是图形用户界面和其他应用程序例如应用程序中使用的主要范例,用于执行某些操作来响应用户输入。我们来看一下事件驱动编程带来的收益。现在让我们看看采用事件驱动编程方法如何实现上述相同的功能。 在这篇文章中我们将了解到什么是事件驱动编程以及在Laravel中如何开始构建一个事件驱动应用,同时我们还将看到如何通过事件驱动编程来对应用程序的逻辑进行解耦。 在开始之前,先说明一下这篇文章...

    Drummor 评论0 收藏0
  • 深入剖析 Laravel 服务容器

    摘要:划下重点,服务容器是用于管理类的依赖和执行依赖注入的工具。类的实例化及其依赖的注入,完全由服务容器自动的去完成。 本文首发于 深入剖析 Laravel 服务容器,转载请注明出处。喜欢的朋友不要吝啬你们的赞同,谢谢。 之前在 深度挖掘 Laravel 生命周期 一文中,我们有去探究 Laravel 究竟是如何接收 HTTP 请求,又是如何生成响应并最终呈现给用户的工作原理。 本章将带领大...

    abson 评论0 收藏0
  • Laravel深入学习4 - 服务提供器

    摘要:一个服务提供器必须包含至少一种方法。服务提供器一旦被注册,就可被用于程序的各个地方。注意服务提供器的变量来自类中。启动服务当所有的服务提供器注册之后,他们就变成了已启动状态。再次提示,把服务提供器作为一种组织工具来使用。 声明:本文并非博主原创,而是来自对《Laravel 4 From Apprentice to Artisan》阅读的翻译和理解,当然也不是原汁原味的翻译,能保证90%...

    daryl 评论0 收藏0
  • Laravel深入学习8 - 单一责任原则

    摘要:它是良好应用设计的大原则,包含单一责任原则开放封闭原则里氏替换原则接口分离原则依赖倒置原则让我们通过代码示例来深究下这五个原则。实探单一责任原则代表一个类有且仅有一个改变的原因,换言之,一个类的职责范畴是严谨明确的。 声明:本文并非博主原创,而是来自对《Laravel 4 From Apprentice to Artisan》阅读的翻译和理解,当然也不是原汁原味的翻译,能保证90%的原...

    ymyang 评论0 收藏0

发表评论

0条评论

HackerShell

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<