Laravel- 應(yīng)用架構(gòu)

聲明:本文并非博主原創(chuàng),而是來(lái)自對(duì)《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當(dāng)然也不是原汁原味的翻譯,能保證90%的原汁性,另外因?yàn)槭抢斫夥g,肯定會(huì)有錯(cuò)誤的地方,歡迎指正。

歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明出處,謝謝!
轉(zhuǎn)載自 https://segmentfault.com/a/1190000009438428#articleHeader0

應(yīng)用架構(gòu)

簡(jiǎn)介

這一章是哪出戲?對(duì)于使用框架創(chuàng)建應(yīng)用這是非常普遍的。很多開(kāi)發(fā)者會(huì)提出這樣的問(wèn)題,因?yàn)樵谒麄兡X仁里已經(jīng)存在這樣的觀念,“模型”就是“數(shù)據(jù)庫(kù)”。所以通常,控制器被用來(lái)和HTTP交互,模型就是和數(shù)據(jù)庫(kù)打交道,視圖就是還有HTML代碼的那部分。但是,對(duì)于那些比如發(fā)送郵件的類、驗(yàn)證數(shù)據(jù)類、訪問(wèn)接口的類該怎么區(qū)分呢?本章我們就使用Laravel構(gòu)建好的架構(gòu)進(jìn)行探討,打破那些固話在你心中的概念,讓開(kāi)發(fā)回歸本質(zhì)。

MVC會(huì)弄死你的

阻礙我們的一種設(shè)計(jì)即:M-V-C。模型,視圖,控制器,這種框架思維已經(jīng)控制開(kāi)發(fā)人員很多年了。這種思維來(lái)源于Ruby On Rails。如果,讓一個(gè)程序員去解釋什么是“模型”,通常都會(huì)聽(tīng)到將其和“數(shù)據(jù)庫(kù)”關(guān)聯(lián)的答案。據(jù)說(shuō),模型就是數(shù)據(jù)庫(kù)。模型包含了數(shù)據(jù)庫(kù)的一切。但是,很快你就會(huì)發(fā)現(xiàn),在簡(jiǎn)單的數(shù)據(jù)庫(kù)訪問(wèn)類之上還有很多額外的邏輯。他需要我們進(jìn)行數(shù)據(jù)驗(yàn)證,調(diào)取額外的服務(wù),發(fā)送郵件,等等。

什么是模型?

模型現(xiàn)在已經(jīng)變的模棱兩可,很難具體指代什么。根據(jù)開(kāi)發(fā)中遇到的那么多詞匯,我們可以理解認(rèn)為,他就是為了將應(yīng)用切分成小而清晰,具有特定職責(zé)的類。

那么,這種困境中的解決方案是什么?很多開(kāi)發(fā)人員會(huì)在控制器之上添加更多的邏輯。當(dāng)控制器變的很大的時(shí)候,需要復(fù)用其他控制器中的一些邏輯層。很多人會(huì)錯(cuò)誤的認(rèn)為需要在當(dāng)前控制器調(diào)用其他控制器,而不是講邏輯抽象成單獨(dú)的類。這種模式通常稱為“HMVC”。不幸的是,這也是糟糕的設(shè)計(jì),通常控制器會(huì)很復(fù)雜。

HMVC(通常)預(yù)示著糟糕的設(shè)計(jì)

當(dāng)覺(jué)得須要在控制器中調(diào)用其他控制器?這意味著當(dāng)前設(shè)計(jì)是糟糕的,控制器里面的業(yè)務(wù)邏輯太負(fù)責(zé)。我們可以講邏輯抽象成通用類,以便在其他控制器中進(jìn)行調(diào)用。

總會(huì)有更好的程序設(shè)計(jì)。我們需要忘記以前在腦海中殘留的那種“模型”的設(shè)計(jì)理念,干脆讓我們刪除模型目錄,并重新開(kāi)始。

再見(jiàn),模型

是否已經(jīng)把你的models目錄刪除?如果沒(méi)有,沒(méi)關(guān)系,別再去理他。我們來(lái)在app下建立一個(gè)新文件夾,簡(jiǎn)單的起個(gè)應(yīng)用名字即可QuickBill,在后續(xù)的討論中,我們之前舉例的那些例子都會(huì)出現(xiàn)。

注意使用場(chǎng)景

記住,如果你創(chuàng)建的是小型的Laravel應(yīng)用,在models下創(chuàng)建幾個(gè)Eloquent模型還是很合適的。而在本章中,我們關(guān)注的是擁有更多“層次”架構(gòu)的復(fù)雜應(yīng)用。

我們已經(jīng)有了app/QuickBill目錄,他和controllers和views目錄同級(jí)。我們可以在QuickBill下創(chuàng)建一些其他目錄,比如Repositories和Billing目錄。建好目錄之后,記得在composer.json注冊(cè)PSR-0自動(dòng)加載。

"autoload": {
          "psr-0":    {
        "QuickBill":    "app/"
    }
}

現(xiàn)在,我們把Eloquent類放到QuickBill根目錄下,我們就能輕松的訪問(wèn)到QuickBillUser,以及QuickBillPayment等。在Respositories目錄中創(chuàng)建PaymentRepository和UserRepository類,并編碼數(shù)據(jù)訪問(wèn)的方法getRecentPayments以及getRichestUser。在Billing目錄則包含使用第三方服務(wù),諸如“Stripe”、“Balanced”的類和接口。上述目錄結(jié)構(gòu)如下:

// app
    // QuickBill
        // Repositories
            -> UserRepository.php
            -> PaymentRepository.php
        // Billing
            -> BillerInterface.php
            -> StripeBiller.php
        // Notifications
            -> BillingNotifierInterface.php
            -> SmsBillingNotifier.php
        User.php
        Payment.php

數(shù)據(jù)驗(yàn)證放哪

這個(gè)問(wèn)題通常讓我們頭大??梢钥紤]把他們放到“實(shí)體”類中,比如User.php或者Payment.php中,方法名可以叫:validForCreation和hasValidDomain。或者也可以創(chuàng)建一個(gè)命名空間為Validation的UserValidator類,并注入到repository類中。兩種方法看個(gè)人喜好。

擺脫models目錄,我們就能沖破枷鎖,實(shí)現(xiàn)好的設(shè)計(jì)。當(dāng)然,我們創(chuàng)建的很多項(xiàng)目都會(huì)有相似之處,無(wú)論多么復(fù)雜的項(xiàng)目也都會(huì)有數(shù)據(jù)接入(存儲(chǔ))層,以及其他服務(wù)層等等。

不要害怕文件夾

不要因?yàn)槎嘟ㄎ募A而害怕,他是組織應(yīng)用程序的很好方式。通常我們希望以此講應(yīng)用分割成很小的組件,每個(gè)組件都有自己特定的職責(zé)。別被“模型”束縛了思維。就像上面舉的例子,我們可以創(chuàng)建一個(gè)Repository來(lái)存放所有數(shù)據(jù)接入的相關(guān)類。

處處皆分層

你應(yīng)該已經(jīng)注意到,好的應(yīng)用設(shè)計(jì)應(yīng)擁有明確的職責(zé)劃分,有明確的邏輯分成。控制只是用來(lái)接收HTTP請(qǐng)求并請(qǐng)求邏輯處理類。業(yè)務(wù)處理層才是整個(gè)應(yīng)用的中心。它包含了像數(shù)據(jù)獲取,數(shù)據(jù)驗(yàn)證,支付處理,郵件發(fā)送類庫(kù),以及應(yīng)用中的各種函數(shù)等。事實(shí)上,業(yè)務(wù)邏輯無(wú)需感知“網(wǎng)絡(luò)”,網(wǎng)絡(luò)僅僅接入應(yīng)用的傳輸機(jī)制,他不應(yīng)超出應(yīng)用中的路由和控制器的范疇。好的架構(gòu)是經(jīng)得起考研的,是由清晰代碼組成的可持續(xù)發(fā)展的架構(gòu)。

例如,我們使用向控制器中傳入網(wǎng)絡(luò)請(qǐng)求輸入來(lái)替代在類中直接訪問(wèn)網(wǎng)絡(luò)請(qǐng)求實(shí)例的方法。簡(jiǎn)單的改動(dòng)即將類從“網(wǎng)絡(luò)”中解耦出來(lái),這種方式也不用擔(dān)心在測(cè)試時(shí)對(duì)請(qǐng)求的再次模擬了:

class BillingController extends BaseController{
    public function __construct(BillerInterface $biller)
    {
        $this->biller = $biller;
    }
    public function postCharge()
    {
        $this->biller->chargeAccount(Auth::user(), Input::get('amount'));
        return View::make('charge.success');
    }
}

chargeAccount方法可以很容易進(jìn)行測(cè)試,只需傳入測(cè)試數(shù)據(jù)用到的整型數(shù)據(jù),而不是使用一個(gè)含有Request和Input類的BillerInterface接口的實(shí)現(xiàn)類庫(kù)來(lái)作為參數(shù)傳入該方法。

職責(zé)分離是編寫(xiě)健壯應(yīng)用的關(guān)鍵。這種關(guān)鍵就是一個(gè)類是否管的太多。你應(yīng)該時(shí)常問(wèn)自己:“是不是這個(gè)類還要關(guān)心X?”,如果答案是“不”,就將邏輯抽象出來(lái),并用依賴注入的方式處理。

改變的原因很簡(jiǎn)單

決定類庫(kù)是否足夠職責(zé)分離的一個(gè)非常有用的方法就是檢驗(yàn)自己為什么要更改這些代碼。比如,在調(diào)整通知邏輯的時(shí)候,是否Biller接口的實(shí)現(xiàn)也要修改?當(dāng)然不,Biller的實(shí)現(xiàn)只和支付有關(guān),只需按照約定和通知邏輯交互。保持這樣的思維觀念,就能幫你快速改進(jìn)應(yīng)用中的各個(gè)部分,使之變得健壯起來(lái)。

“瓶瓶罐罐”都放哪

當(dāng)使用Laravel開(kāi)發(fā)應(yīng)用的時(shí)候,會(huì)經(jīng)常有這樣的疑問(wèn),很多“東西”不知道放哪。比如,“helper”函數(shù)放哪?事件堅(jiān)挺程序放哪?視圖組件又該在哪?答案可能會(huì)讓你凌亂:“哪都可以”!Laravel沒(méi)有文件該歸屬哪里的概念。然而這個(gè)答案并不是讓人滿意的,在繼續(xù)深入之前,讓我們先就上面的問(wèn)題探討下。

輔助函數(shù)
Laravel的輔助函數(shù)放在support/helpers.php文件中?;蛘吣阋蚕雱?chuàng)建這樣一個(gè)自己的輔助函數(shù)文件,“start”目錄就是個(gè)不錯(cuò)的地方。在請(qǐng)求應(yīng)用時(shí),start/global.php都會(huì)被引用到,我們可以在這添加上加載自己的helpers.php文件:

// Within app/start/global.php

require_once __DIR__.'/../helpers.php';

事件監(jiān)聽(tīng)器
事件監(jiān)聽(tīng)器當(dāng)然不能屬于routes.php文件,放在start文件也不合適,我們要另?yè)竦胤桨卜潘7?wù)提供器的目錄就不錯(cuò),之前我們知道,服務(wù)提供不僅是容器注冊(cè)綁定的服務(wù),還可以做很多其他事情。它可以講很多監(jiān)聽(tīng)器組織起來(lái),這種方式是代碼清理整潔,也不影響應(yīng)用邏輯。視圖組件也可以放到這個(gè)位置,他和監(jiān)聽(tīng)器其實(shí)是類似的,都能收納在服務(wù)提供器中。

比如,用服務(wù)提供器組織監(jiān)聽(tīng)器:

<?php namespace QuickBillProviders;

use IlluminateSupportServiceProvider;

class BillingEventsProvider extends ServiceProvider{

    public function boot()
    {
        Event::listen('billing.failed', function($bill)
        {
            // Handle failed billing event...
        });
    }
}

創(chuàng)建完提供器后,只需要簡(jiǎn)單的在app/config/app.php配置中providers數(shù)組添加上它就好。

注意boot方法

記住,上例中我們使用boot的原因,register方法僅僅是用來(lái)將服務(wù)注冊(cè)到容器的方法。

錯(cuò)誤處理
如果應(yīng)用中我們自定義了錯(cuò)誤處理,別接管在“start”文件中,同樣,像事件監(jiān)聽(tīng)器一樣,最好還放在服務(wù)提供器中進(jìn)行組織。提供器可以像這樣命名QuickBillErrorProvider,并在boot方法中講所有自定義的錯(cuò)誤處理注冊(cè)進(jìn)來(lái),重申一下:我們要將這些代碼和我們的邏輯分離開(kāi)來(lái)。最終,自定義的錯(cuò)誤處理程序如下:


<?php namespace QuickBillProviders;

use App, IlluminateSupportServiceProvider;

class QuickBillErrorProvider extends ServiceProvider {

    public function register()
    {    
        //
    }

    public function boot()
    {
        App::error(function(BillingFailedException $e)
        {
            // Handle failed billing exceptions ...
        });
    }
}

簡(jiǎn)潔的方案

當(dāng)然在只有一兩個(gè)錯(cuò)誤處理方式的情況下,把他放到“start”文件也是一種簡(jiǎn)潔的方式。

其他
通常,類庫(kù)應(yīng)該以PSR-0規(guī)范組織在我們的應(yīng)用中。命令式代碼如事件監(jiān)聽(tīng)、錯(cuò)誤處理、以及其他“注冊(cè)”類型的服務(wù)最好組織在服務(wù)提供器中。基于如上原則,我們就能決策出代碼的組織規(guī)律。有一點(diǎn)不要太猶豫,Laravel是為了讓工作方便于我們的業(yè)務(wù),這也是Laravel的宗旨。尋找適合自己應(yīng)用的結(jié)構(gòu),并分享給其他人。

如上,我們可以為所有自定義的服務(wù)提供器添加一個(gè)命名空間Providers并創(chuàng)建目錄組織起來(lái):

// app
    // QuickBill
        // Billing
        // Extensions
            //Pagination
                -> Environment.php
        // Providers
            -> EventPusherServiceProvider.php
        // Repositories
        User.php
        Payment.php

上例中,有兩個(gè)命名空間Extensions和Providers,自定義的服務(wù)放到Providers目錄下,對(duì)框架擴(kuò)展的組件以Extensions命名空間的方式組織到同名目錄下。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容