The Clean Architecture in PHP 讀書筆記(八)

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的第八篇,你的鼓勵是我繼續寫下去的動力,期待我們共同進步。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容