Application Structure 應用結構

Application Structure 應用結構

Introduction 介紹

Where does this class belong? This question is extremely common when building applications on a framework. Many developers ask this question because they have been told that “Model” means “Database”. So, developers have their controllers that interact with HTTP, models which do something with the database, and views which contain their HTML. But, what about classes that send e-mail? What about classes that validate data? What about classes that call an API to gather information? In this chapter, we’ll cover good application structure in the Laravel framework and break down some of the common mental roadblocks that hold developers back from good design.

這個類要寫到哪兒?這是一個在用框架寫應用程序時十分常見的問題。大量的開發人員都有這個疑問。他們被灌輸“Model”就是“Database”,在控制器裏面處理HTTP請求,在模型裏操作數據庫,視圖裏包含了要顯示的HTML。不過,發送電子郵件的類要寫到哪兒?數據驗證的類要寫到哪兒?調用外部API的類要寫到哪兒?在這一章節,我們將學習如何寫結構優美的Laravel應用,打破長久以來掣肘開發人員的普遍思維慣性這個攔路虎,最終做出好的設計。

MVC Is Killing You MVC是慢性謀殺

The biggest roadblock towards developers achieving good design is a simple acronym: M-V-C. Models, views, and controllers have dominated web framework thinking for years, in part because of the popularity of Ruby on Rails. However, ask a developer to define “model”. Usually, you’ll hear a few mutters and the word “database”. Supposedly, the model is the database. It’s where all your database stuff goes, whatever that means. But, as you quickly learn, your application needs a lot more logic than just a simple database access class. It needs to do validation, call external services, send e-mails, and more.

爲了做出好的程序設計,最大的攔路虎就是一個簡單的縮寫詞:M-V-C。模型、視圖、控制器主宰了Web框架的思想已經好多年了。這種思想的流行某種程度上是託了Ruby on Rails愈加流行的福。然而,如果你問一個開發人員“模型”的定義是什麼。通常你會聽到他嘟噥着什麼“數據庫”之類的東西。這麼說,模型就是數據庫了。不管這意味着什麼,模型裏包含了關於數據庫的一切。但是,你很快就會知道,你的應用程序需要的不僅僅是一個簡單的數據庫訪問類。他需要更多的邏輯如:數據驗證、調用外部服務、發送電子郵件,等等更多。

What Is A Model? 模型是啥?

The word “model” has become so ambiguous that it has no meaning. By developing with a more specific vocabulary, it will be easier to separate our application into smaller, cleaner classes with a clearly defined responsiblity.

單詞”model”的含義太模糊了,很難說明白準確的含義。更具體來講,模型是用來將我們的應用劃分成更小、更清晰的類,使得各代碼部分有着明確的權責。

So, what is the solution to this dilemma? Many developers start packing logic into their controllers. Once the controllers get large enough, they need to re-use business logic that is in other controllers. Instead of extracting the logic into another class, most developers mistakenly assume they need to call controllers from within other controllers. This pattern is typically called “HMVC”. Unfortunately, this pattern often indicates poor application design, and controllers that are much too complicated.

所以怎麼解決這個問題(譯者注:上文中“更多的業務邏輯”)呢?很多開發者開始將業務邏輯包裝到控制器裏面。當控制器龐大到一定規模,他們將會需要重用業務邏輯。大部分開發人員沒有將這些業務邏輯提取到別的類裏面,而是錯誤的臆想他們需要在控制器裏面調用別的控制器。這種模式通常被稱爲“HMVC”。不幸的是,這種模式通常也預示着糟糕的程序設計,並且控制器已經太複雜了。

HMVC (Usually) Indicates Poor Design HMVC(通常)預示着糟糕的設計。

Feel the need to call controllers from other controllers? This is often indicative of poor application design and too much business logic in your controllers. Extract the logic into a third class that can be injected into any controller.

你覺得需要在控制器裏面調用其他的控制器?這通常預示着糟糕的程序設計並且你的控制器裏面業務邏輯太多了。把業務邏輯抽出來放到一個新的類裏面,這樣你就可以在其他任何控制器裏面調用了。

There is a better way to structure applications. We need to wash our minds clean of all we have been taught about models. In fact, let’s just delete the model directory and start fresh!

有一種更好的程序結構。但首先我們要忘掉以往我們被灌輸的關於“模型”的一切。乾脆點,讓我們直接刪掉model目錄,重新開始吧!

Bye, Bye Models 再見,模型

Is your models directory deleted yet? If not, get it out of there! Let’s create a folder within our app directory that is simply named after our application. For this discussion, let’s call our application QuickBill, and we’ll continue to use some of the interfaces and classes we’ve discussed before.

刪掉你的models目錄了麼?還沒刪就趕緊刪了!我們將要在app目錄下創建個新的目錄,目錄名就以我們這個應用的名字來命名,這次我們就叫QuickBill吧。在後續的討論中,我們在前面寫的那些接口和類都會出現。

Remember The Context 注意使用場景

Remember, if you are building a very small Laravel application, throwing a few Eloquent models in the modelsdirectory perfectly fine. In this chapter, we’re primarily concerned with discovering more “layered” architecture suitable to large and complex projects.

記住,如果你在寫一個很小的Laravel應用,那在models目錄下寫幾個Eloquent模型其實挺合適的。但在本章節,我們主要關注如何開發更有合適“層次”架構的大型複雜項目。

So, we should have an app/QuickBill directory, which is at the same level in the application directory structure as controllers and views. We can create several more directories within QuickBill. Let’s create a Repositoriesdirectory and a Billing directory. Once these directories are established, remember to register them for PSR-0 auto-loading in your composer.json file:

這樣我們現在有了個app/QuickBill目錄,它和應用目錄下的其他目錄如controllers還有views都是平級的。在QuickBill目錄下我們還可以創建幾個其他的目錄。我們來在裏面創建個Repositories和Billing目錄。目錄都創建好以後,別忘了在composer.json文件里加入PSR-0的自動載入機制:

<!-- lang:javascript -->
"autoload": {
    "psr-0":    {
        "QuickBill":    "app/"
    }
}

譯者注:psr-0也可以改成psr-4, “psr-4”: { “QuickBill\”: “app/QuickBill” } psr-4是比較新的建議標準,和psr-0具體有什麼區別請自行檢索。

For now, let’s put our Eloquent classes at the root of the QuickBill directory. This will allow us to conveniently access them as QuickBill\User, QuickBill\Payment, etc. In the Repositories folder would belongs classes such as PaymentRepository and UserRepository, which would contain all of our data access functions such as getRecentPayments, and getRichestUser. The Billing directory would contain the classes and interfaces that work with third-party billing services like Stripe and Balanced. The folder structure would look something like this:

現在我們把繼承自Eloquent的模型類都放到QuickBill目錄下面。這樣我們就能很方便的以QuickBill\User, QuickBill\Payment的方式來使用它們。Repositories目錄屬於PaymentRepository 和UserRepository這種類,裏面包含了所有對數據的訪問功能比如getRecentPayments和getRichestUser。Billing目錄應當包含調用第三方支付服務(如Stripe和Balanced)的類。整個目錄結構應該類似這樣:

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

What About Validation 數據驗證怎麼辦?

Where to perform validation often stumps developers. Consider placing validation methods on your “entity” classes (like User.php and Payment.php). Possible method name might be: validForCreation or hasValidDomain. Alternatively, you could create a UserValidator class within a Validation namespace and inject that validator into your repository. Experiment with both approaches and see what you like best!

在哪兒進行數據驗證常常困擾着開發人員。可以考慮將數據驗證方法寫進你的“實體”類裏面(好比User.php和Payment.php)。方法名可以設爲validForCreation或hasValidDomain。或者你也可以專門創建個驗證器類UserValidator,放到Validation命名空間下,然後將這個驗證器類注入到你的repository類裏面。兩種方式你都可以試試,看哪個你更喜歡!

By just getting rid of the models directory, you can often break down mental roadblocks to good design, allowing you to create a directory structure that is more suitable for your application. Of course, each application you build will have some similarities, since each complex application will need a data access (repository) layer, several external service layers, etc.

擺脫了models目錄後,你通常就能克服心理障礙,實現好的設計。使得你能創建一個更合適的目錄結構來爲你的應用服務。當然,你建立的每一個應用程序都會有一定的相似之處,因爲每個複雜的應用程序都需要一個數據訪問(repository)層,一些外部服務層等等。

Don’t Fear Directories 別害怕目錄

Don’t be afraid to create more directories to organize your application. Always break your application into small components, each having a very focused responsibility. Thinking outside of the “model” box will help. For example, as we previously discussed, you could create a Repositories directory to hold all of your data access classes.

不要懼怕建立目錄來管理應用。要常常將你的應用切割成小組件,每一個組件都要有十分專注的職責。跳出“模型”的框框來思考。比如我們之前就說過,你可以創建個Repositories目錄來存放你所有的數據訪問類。

It’s All About The Layers 核心思想就是分層

As you may have noticed, a key to solid application design is simply separating responsibilities, or creating layers of responsibility. Controllers are responsible for receiving an HTTP request and calling the proper business layer classes. Your business / domain layer is your application. It contains the classes that retrieve data, validate data, process payments, send e-mail, and any other function of your application. In fact, your domain layer doesn’t need to know about “the web” at all! The web is simply a transport mechanism to access your application, and knowledge of the web and HTTP need not go beyond the routing and controller layers. Good architecture can be challenging, but will yield large profits of sustainable, clear code.

你可能注意到,優化應用的設計結構的關鍵就是責任劃分,或者說是創建不同的責任層次。控制器只負責接收和響應HTTP請求然後調用合適的業務邏輯層的類。你的業務邏輯/領域邏輯層纔是你真正的程序。你的程序包含了讀取數據,驗證數據,執行支付,發送電子郵件,還有你程序裏任何其他的功能。事實上你的領域邏輯層不需要知道任何關於“網絡”的事情!網絡僅僅是個訪問你程序的傳輸機制,關於網絡和HTTP請求的一切不應該超出路由和控制器層。做出好的設計的確很有挑戰性,但好的設計也會帶來可持續發展的清晰的好代碼。

For example, instead of accessing the web request instance in a class, you could simply pass the web input from the controller. This simple change alone decouples your class from “the web”, and the class can easily be tested without worrying about mocking a web request:

舉個例子。與其在你業務邏輯類裏面直接獲取網絡請求,不如你直接把網絡請求從控制器傳給你的業務邏輯類。這個簡單的改動將你的業務邏輯類和“網絡”分離開了,並且不必擔心怎麼去模擬網絡請求,你的業務邏輯類就可以簡單的測試了:

<!-- lang:php -->
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');
    }
}

Our chargeAccount method is now much easier to test, since we no longer have to use the Request or Input class inside of our BillingInterface implementation as we are simply passing the charged amount into the method as an integer.

現在chargeAccount 方法更容易測試了。 我們把Request和Input從BillingInterface裏提出來,然後在控制器裏把方法需要的支付金額直接傳過去。

Separation of responsibilities is one of the keys to writing maintainable applications. Always be asking if a given class knows more than it should. You should frequently ask yourself: “Should this class care about X?” If answer is “no”, extract the logic into another class that can be injected as a dependency.

編寫擁有高可維護性應用程序的關鍵之一,就是責任分割。要時常檢查一個類是否管得太寬。你要常常問自己“這個類需不需要關心XXX呢?”如果答案是否定的,那麼把這塊邏輯抽出來放到另一個類裏面,然後用依賴注入的方式進行處理。(譯者注:依賴注入的不同方式還記得麼?調用方法傳參、構造函數傳參、從IoC容器獲取等等。)

Single Reason To Change

A helpful method of determining whether a class has too many responsibilities is to examine your reason for changing code within that class. For example, should we need to change code within a Biller implementation when tweaking our notification logic? Of course not. The Biller implementations are concerned with billing, and should only work with notification logic via a contract. Maintaining this mindset as you are working on your code will help you quickly identify areas of an application that can be improved.

如何判斷一個類是否管得太寬,有一個有用的方法就是檢查你爲什麼要改這塊兒代碼。舉個例子:當我們想調整通知邏輯的時候,我們需要修改Biller的實現代碼麼?當然不需要,Biller的實現僅僅需要考慮支付,它與通知邏輯應當僅通過約定來進行交互。使用這種思路過一遍代碼,會讓你很快找出應用中需要改進的地方。

Where To Put “Stuff” 東西都放哪兒?

When developing applications with Laravel, you may sometimes wonder where to put “stuff”. For example, where should you put “helper” functions? Where should you put event listeners? Where should you put view composers? It may be surprising for you to know that the answer is: “wherever you want!” Laravel does not have many conventions regarding where things belong on the file system. However, as this answer is not often satisfactory, we’ll explore some possible locations for such things before moving on.

當用Laravel開發應用時,你可能迷惑於應該把各種“東西”都放在哪兒。比如,輔助函數要放在哪裏?事件監聽器要放在哪裏?視圖組件要放在哪裏?答案可能出乎你的意料——“想放哪兒都行!”Laravel並沒有很多在文件系統上的約定。不過這個答案的確不能讓人滿意,所以下面我們就這個問題展開探索,探索這些“東西”究竟可以放在哪兒。

Helper Functions 輔助函數

Laravel ships with a file full of helpers functions (support/helpers.php). You may wish to create a similar file containing helper functions relevant to your own application and conding style. A great place to include these functions are the “start” files. Within your start/global.php file, which is included on every request to the application, you may simply require your own helpers.php file:

Laravel 有一個文件(support/helpers.php)裏面都是輔助函數。你或許希望創建一個類似的文件來存儲你自己的輔助函數。“start”文件是個不錯的入口,該文件會在應用的每一次請求時被訪問。在start/global.php裏,你可以引入你自己寫的helpers.php文件,就像這樣:

<!-- lang:php -->
// Within app/start/global.php

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

//譯者注: 該helpers.php文件位於app目錄下,需要你自己創建。你想放到別的地方也可以。

Event Listeners 事件監聽器

Since event listeners obviously do not belongs in the routes.php file, and can begin to clutter the “start” files, we need another location to place this code. A great option is a service provider. As we’ve learned, service providers are not strictly for registering bindings in the IoC container. They can be used to do all sorts of work. By grouping related event registrations inside of a service provider, the code stays neatly tucked away behind the scenes of your main application code. View composers, which are a type of event, may also be neatly grouped within service providers.

事件監聽器當然不該放到routes.php文件裏面,若直接放到“start”目錄下的文件裏會比較亂,所以我們要找另外的地方來存放。服務提供者是個好地方。我們之前瞭解到,服務提供者可不僅僅是用來做依賴注入綁定,還可以幹其他事兒。可以將事件監聽器用服務提供者來管理起來,讓代碼更整潔,不至於影響到你應用的主要邏輯代碼。視圖組件其實和事件差不多,也可以類似的放到服務提供者裏面。

For example, a service provider that registers events might look something like this:

例如使用服務提供者進行事件註冊可以這樣:

<!-- lang:php -->
<?php namespace QuickBill\Providers;
    use Illuminate\Support\ServiceProvider;
    class BillingEventsProvider extends ServiceProvider{
        public function boot()
        {
            Event::listen('billing.failed', function($bill)
            {
                // Handle failed billing event...
            });
        }
    }

After creating the provider, we would simply add it our providers array within the app/config/app.php configuration file.

創建好服務提供者後,就可以將它加入到app/config/app.php 配置文件的providers數組裏。

Wear The Boot注意啓動流程

Remember, in the example above, we are using the boot method for a reason. The register method in a service provider is only intended for binding classes into container.

記住在上面的例子裏面,我們在boot方法裏進行編寫是有原因的。register方法只能用來進行依賴注入綁定。

Error Handlers 錯誤處理

If your application has many customer error handlers, they may start to take over your “start” files. Again, like event handlers, these are best moved into a service provider. The service provider might be named something like QuickBillErrorProvider. Within the boot method of this provider you may register all of your custom error handlers. Again, keeps boilerplate code out of the main files of your application. An error handler provider would look something like this:

如果你的應用裏面有很多自定義的錯誤處理方法,那你的“啓動”文件可能會很臃腫。和剛纔的事件監聽器一樣,錯誤處理方法也最好放到服務提供者裏面。這種服務提供者可以命名爲像QuickBillErrorProvider這種。然後你在boot方法裏想註冊多少錯誤處理方法都可以了。重申一下精神:讓呆板的代碼離你應用的業務邏輯越遠越好。下方展示了這種服務提供者的一種可能的書寫方法:

<!-- lang:php -->
<?php namespace QuickBill\Providers;
use App, Illuminate\Support\ServiceProvider;
class QuickBillErrorProvider extends ServiceProvider {
    public function register()
    {    
        //
    }

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

The Small Solution 簡便做法

Of course, if you only have one or two simple error handlers, keeping them in the “start” files is a reasonable and quick solution.

當然如果你只有一兩條簡單的錯誤處理方法,那麼都寫在“啓動”文件裏面也是一種又快又好的簡便做法。

The Rest 其他

In general, classes may be neatly organized using a PSR-0 structure within a directory of your application. Imperative code such as event listeners, error handlers, and other “registeration” type operations may be placed inside of a service provider. With what we have learned so far, you should be able to make an educated decision on where to place a piece of code. But, don’t be hesitate to experiment. The beauty of Laravel is that you can make conventions that work best for you. Discover a structure that works best for your applications, and make sure to share your insights with others!

通常只要遵循PSR-0(譯者注:或PSR-4)就可以保持類的整潔。命令式的代碼比如事件監聽器、錯誤處理器還有其他“註冊”性質的操作都可以放在服務提供者裏面。對於什麼代碼要放在什麼地方這個問題,結合你目前爲止學到的知識,應當可以給出一個有理有據的答案了。但永遠不要害怕試驗。Laravel最美妙之處就是你可以做出最適合你自己的風格。去探索和發現最適合你自己應用的結構吧,別忘了和他人分享你的見解!

For example, as you probably noticed above, you might create a Providers namespace for all of your application’s custom service providers, creating a directory structure like so:

例如你可能注意到我們上面的例子,你可以創建個Providers的命名空間來存放你自己寫的服務提供者,目錄就類似於這樣:

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

Notice that in the example we have a Providers and an Extensions namespace. All of your application’s service providers could be stored in the Providers directory in namespace, and the Extensions namespace is a convenient place to store extensions made to core framework classes.

看上面的例子我們有Providers和Extensions兩個命名空間(譯者注:分別對應兩個同名目錄)。你自己寫的服務提供者可以放到Providers命名空間下。那個Extensions命名空間可以用來存放你對框架核心進行擴展的類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章