当前位置: 首页 > news >正文

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

声明:本文并非博主原创,而是来自对《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();
});

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

该谁负责?

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

相关文章:

  • Spring Boot基础教程——web应用开发-模板引擎
  • APPium-python实例(记录)
  • SpringBoot(Security)
  • 初学Sockets编程(二) 关于名称和地址族
  • HDU - 1166 敌兵布阵
  • Flask+腾讯云windows主机快速搭建微信公众号接口
  • 一、简单工厂模式
  • 微软将所有的Windows代码库迁移到Git
  • magento megatron主题加入中文
  • 对象不支持“abigimage”属性或方法
  • Hyper-v创建检查点(VM的快照功能)
  • dede程序打开install安装时出现dir
  • 解答《编程之美》1.18问题1:给所有未标识方块标注有地雷概率
  • 【EMC】基本概念
  • Netty断线重连
  • [译]Python中的类属性与实例属性的区别
  • 【mysql】环境安装、服务启动、密码设置
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • 08.Android之View事件问题
  • 10个确保微服务与容器安全的最佳实践
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • Java知识点总结(JavaIO-打印流)
  • spring-boot List转Page
  • vue-router 实现分析
  • vue学习系列(二)vue-cli
  • 初识 beanstalkd
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 回顾2016
  • 马上搞懂 GeoJSON
  • 面试总结JavaScript篇
  • 前端js -- this指向总结。
  • 想写好前端,先练好内功
  • 一文看透浏览器架构
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • #include到底该写在哪
  • %check_box% in rails :coditions={:has_many , :through}
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (70min)字节暑假实习二面(已挂)
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (力扣)1314.矩阵区域和
  • (三)mysql_MYSQL(三)
  • (转载)(官方)UE4--图像编程----着色器开发
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • .bat批处理(一):@echo off
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .NET C#版本和.NET版本以及VS版本的对应关系
  • .net core使用ef 6
  • .NET Micro Framework 4.2 beta 源码探析
  • .net 调用php,php 调用.net com组件 --
  • .NET 中让 Task 支持带超时的异步等待
  • .NET实现之(自动更新)
  • .vue文件怎么使用_vue调试工具vue-devtools的安装
  • [ 常用工具篇 ] POC-bomber 漏洞检测工具安装及使用详解