The Clean Architecture in PHP 读书

作者: 小聪明李良才 | 来源:发表于2016-11-14 08:34 被阅读171次
    Clean Architecture

    上篇简要介绍了Clean Architecture和union architecture,并给出clean architecture的一些共同点:框架无关,可测性,UI无关,数据库无关,无外部依赖,本篇会具体介绍里面的一些点。

    本文为系列文章的第八篇,完成的目录请查看Clean Architecture

    框架无关(Framework Independence)

    首先我们必须说:框架是好的!大大的提高了我们的开发速度,像市面上流行的框架如:laravel,symfony,zend framework提供了一些通用问题的解决方案,如认证,数据库交互,MVC,路由等,最重要的是这些方案一般都是一些久经考验的方案。正是由于这些方案,我们能更关注我们的业务逻辑,不必陷入一些重复的、细节的问题中。

    使用框架的另一个好处是:快速的进步,因此快去使用、学习框架吧。框架定义好了设计模式,你如果不按照框架定义好的模式去做,你就run不起来,于是你就必须去用正确的,好的模式,这样你就可以不断进步。

    但是,我们不得不承认,使用框架都是有成本的,在正式开始项目之前,你必须要去学习它,但是一旦学习过后,你就不用再去做那些恼人的重复工作了,辛苦一次,快乐一生_

    框架的一些不足

    讲了这么多框架的好处,但是必须不幸的告诉你,所有的框架都有一个共同的问题:耦合。你越是使用这个框架,你越是离不开他,你跟他的耦合也越深,一旦这个框架某一天“消失”了,你就game over了!此处的消失,可能是框架升级了,不兼容了,或者是作者不维护了,等等。

    框架无关指的是什么

    框架无关到底指的是什么?

    我们能够快速的切换框架,可能今天laravel挺火,我们用这个,明天突然symfony挺好,换换换的!

    当我们在写中大型应用的时候,我们可能会有些处理表单的代码,有些和数据库交互的代码,有些辅助函数,但是这些是我们的业务逻辑吗?NO!

    那什么才是我们的业务逻辑呢,或者说是我们的应用。答案是:domain modeldomain services

    领域模型和领域服务包括了:services,repositories,factories和entities,这些才是我们真正的应用。至于其他的,都是在领域模型和领域服务基础上构建的UI。

    为了达到框架无关,下面是一些建议。

    对于框架的使用进行抽象

    我们没多写一行使用框架的代码,我们都在增加一分对于框架的依赖。那怎么做才能减少对于框架的依赖呢?

    • 尽可能使用接口

      尽可能依赖于接口,然后通过依赖注入实现依赖反转

    • 使用适配器模式

      通过适配器模式来使用第三方库,实现定义好的接口

    • 坚持SOLID原则和clean code

      坚持SOLID和clean code原则,使得我们代码能组织的很好,并且减少依赖

    说完这么多,可能大家还是不是很懂,还是让我们上代码的。

    talk is cheap, show me the code

    路由和控制器

    路由是控制器是我们应用程序的入口,我们真的很难想象不依赖框架提供的路由和框架,怎么写我们的代码,下面是我们开发中最常见的一段代码:

    class CustomersController extends BaseController { }
    

    写下这行代码的同时,意味着我们接下去控制器中的每一行都依赖于BaseController,怎么办?

    使用适配器模式来适配控制器

    namespace MyApp\Controller;
    
    class Customers { 
      public function index() { 
        return [
          'users' => $this->customerRepository->getAll() ]; 
      } 
    }
    

    然后适配器如下:

    class CustomersController extends AbstractActionController {
        protected $controller;
        public function __construct( Customers $controller )
        {
            $this->controller = $controller;
        }
        public function indexAction()
        {
            return $this->controller->index();
        }
    }
    

    适配器做的事情就是包裹着我们自己的控制器Customers,然后进行调用。到这里,我们不禁会问自己,这么做是否值得?

    我们做的这一切工作都是为了让我们的代码不耦合于框架

    另一个解决方案是:尽可能保持控制器简单。

    就像SRP(单一职责原则)倡导的,我们要使得我们的控制器尽可能的功能单一。如果我们将控制器比喻为一个产生response的工厂,那控制器的职责只负责将输入转换为输出,至于具体的业务逻辑,都应该封装在领域模型和领域服务中。

    我们坚持的一个原则是:胖model,瘦controller。基于这个原则,我们的控制器应该是下面这样的:

    class CustomersController extends AbstractActionController {
        public function indexAction()
        {
            return [
                'users' => $this->customerRepository->getAll() ];
        }
    }
    

    上面的控制器很好的说明了我们原则:控制器尽可能简单,将所有逻辑放入领域层。

    视图层

    视图层中都是一些展示逻辑,但是我们需要注意的是:每个框架都提供了一些辅助函数来生成一些html代码,如果换框架,这会是很头痛的一部分。

    因此我们在写下每一行代码的同时,需要时刻提醒自己:尽量减少对于框架的依赖。

    表单

    表单是我们项目中最难处理一部分,同样的,我们也很难做到和框架解耦。

    在使用表单的过程中,我们应该牢记:表达只包含验证和过滤规则,和业务逻辑相关的都应该放入领域层中。

    框架服务

    大多数框架都提供一些封装好的服务,如laravel中的发送email,我们只需简单的调用:

    Mail::send( 'emails.hello', $data, function ( $message ) {
        $message->to( 'you@yoursite.com', 'You' )->subject( 'Hello, You!' );
    } );
    

    但是一旦我们换框架,我们就只能痛苦的重构了,一个解决方案是使用适配器:

    interface MailerInterface {
        public function send( $template, array $data, callable $callback );
    }
    
    class LaravelMailerAdapter implements MailerInterface {
        protected $mailer;
        public function __construct( Mailer $mailer )
        {
            $this->mailer = $mailer;
        }
        public function send( $template, array $data, callable $callback )
        {
            $this->mailer->send( $template, $data, $callback );
        }
    }
    class MailController extends BaseController {
        protected $mailer;
        public function __construct( MailerInterface $mailer )
        {
            $this->mailer = $mailer;
        }
        public function sendMail()
        {
            $this->mailer->send( 'emails.hello', $data, function ( $message ) {
                $message->to( 'you@yoursite.com', 'You' )->subject( 'Hello, You!' );
            } );
        }
    }
    
    App::bind('MailerInterface', function($app) {
      return new LaravelMailerAdapter($foo['mailer']); 
    });
    

    上面的一段代码给我们很好的示范了怎么使用适配器模式来减少对于框架的依赖。

    总结

    以上介绍的一些方法具体在实际使用时候,还需要细细斟酌,特别是要视你项目规模来酌情使用。

    如果你项目非常小,那就放开手脚,想怎么弄就怎么弄,但是如果你是做ERP这种应用,那就请好好设计的,前期良好的设计会让你后期的维护成本大大降低。

    数据库无关(Independent of Database)

    我们大多数的应用后端存储都是使用数据库,自然而然应用也是维护数据库的表结构设计的,我们的应用所有逻辑都是围绕着数据库展开,前期这没什么问题,但是随着应用继续开发,带来的问题有:

    1. 代码中到处都是和数据库的交互,我们看业务逻辑的时候,完全没办法关注于业务,只能看到数据库交互,更糟糕的是:一旦我们需要换数据库抽象层,那将是一场噩梦
    2. 由于我们使用数据库,我们基本上不可能测试我们代码,每次测试一个功能,我们都必须要保证数据库可用,然后数据库中的数据符合我们的预期,这种痛苦只有做过的才知道

    那如果数据库不是中心,那什么是我们应用的中心呢?

    前面我们讲过clean architecture,最核心的就是领域模层,我们应用的中心也应该是领域层,领域层有可以分为领域模型和领域服务。

    领域模型

    领域模型在php中就是最简单的php对象,可能是下面这个样子的:

    class Customer {
    
        protected $id;
        protected $name;
        protected $creditLimit;
        protected $status;
    
        public function getId()
        {
            return $this->id;
        }
    
        public function setId( $id )
        {
            $this->id = $id;
            return $this;
        }
    
        public function getName()
        {
            return $this->name;
        }
    
        public function setName( $name )
        {
            $this->name = $name;
            return $this;
        }
    
        // ...
    }
    

    由于是纯的php类,所以不会有什么依赖了,因此是完全解耦的,是能够方便测试的。

    但是如果只有领域模型,意义不大,要配合上领域服务,才能真正的发挥作用。

    领域服务

    领域服务内部可以细分为3层:

    • Repositories

      服务领域对象的存取,如果后端是数据库,就是负责将数据从数据库中取出,将对象存入数据库。

    • Factories

      负责对象的创建。

    • Services

      具体的业务逻辑,通过调用多个对象和其他服务来完成一个业务目标。

    具体可以参考之前的文章:The Clean Architecture in PHP 读书笔记(六)之你不知道的MVC

    讲到这,介绍clean architecture的内容就都结束了,下一篇将会以一个实际的例子来加深对clean architecture的理解,尽情期待。

    这是The Clean Architecture in PHP的第八篇,你的鼓励是我继续写下去的动力,期待我们共同进步。

    相关文章

      网友评论

      本文标题:The Clean Architecture in PHP 读书

      本文链接:https://www.haomeiwen.com/subject/hichpttx.html