1. 程式人生 > >基於 Module 的 Laravel API 架構

基於 Module 的 Laravel API 架構

file

轉自 PHP / Laravel 開發者社群 laravel-china.org/topics/2191…

我非常喜歡編寫基於模組化設計的軟體和程式設計方式,但我不太喜歡依賴第三方軟體包和類庫來處理一些瑣碎的事情,因為它們不會讓你的程式設計水平得到很好的提升。所以這兩年來,我一直在用Laravel編寫基於模組的軟體,現在我對這個結果非常滿意。

推動我走向基於模組化設計的軟體和程式設計方式的決定性因素是我想持續提升我的程式設計水平。想象一下,你構建了一個專案結構,6個月後你發現這個專案存在很多bug。在不影響6個月現有程式碼的情況下,通常不會輕易改變專案架構。在分析這個專案時,我注意到了兩個要點:你要麼在整個專案中都有一個標準,要麼堅持下去,要麼模組化並逐個模組地改進。

有些人傾向於不惜一切代價、固守標準地開發,即使這可能意味著要堅持一個你不再喜歡的標準。就我個人來言,我更喜歡持續地改進,若是第 20 個模組和第 1 個模組寫得完全不一樣也沒關係。如果某天我需要回到模組 1 修復 BUG 或重構,我可以將其改進為第 20 個模組使用的最新標準。

假設,你也像我一樣喜歡基於模組化開發 Laravel 應用、儘可能避免在專案中新增不必要的第三方依賴——本文是我的一點經驗。

1- 路由服務提供者

Laravel 路由系統可以說是整個應用的入口。首先需要修改的是預設的 RouteServiceProvider.php 檔案,它應當將現有路由模組化。

<?php
namespace App\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
    /**
     * 定義應用路由。
     *
     * @return
void */ public function map() { $this->mapModulesRoutes(); } protected function mapModulesRoutes() { // 如果你在編寫傳統 Web 應用而非 HTTP API,請使用 `web` 中介軟體。 Route::middleware('api') ->group(base_path('routes/modules.php')); } } 複製程式碼

如上,我們可以直接擺脫該檔案的整個樣板,只需設定一個模組化的路由檔案即可。

2- 模組檔案

Laravel 在 routes 資料夾中自帶了一些檔案。由於我們已經不在 RouteServiceProvider 中對映這些路由,所以可以直接刪除它們。接下來,我們建立一個 modules.php 路由檔案。

<?php
use Illuminate\Support\Facades\Route;
Route::group([], base_path('app/Modules/Books/routes.php'));
Route::group([], base_path('app/Modules/Authors/routes.php'));
複製程式碼

3- Books 模組

在 app 資料夾中,建立 Modules/Books/routes.php 檔案。在此檔案中,我們可以定義該應用 Books 模組的路由規則。

<?php
use App\Modules\Books\ListBooks;
use Illuminate\Support\Facades\Route;
Route::get('/books', ListBooks::class);
複製程式碼

你可以使用基於控制器——也就是 Laravel 中預設標準的路由方式,但我個人更喜歡 Good bye controllers, hello Request Handlers(放棄控制器,採用請求處理器) 的方式。 如下是 ListBooks 的實現。

<?php
namespace App\Modules\Books;
use App\Eloquent\Book;
use App\Modules\Books\Resources\BookResource;
class ListBooks
{
    public function __invoke(Book $book)
    {
        return BookResource::collection($book->paginate());
    }
}
複製程式碼

以上程式碼中 BookResource 是 Laravel 的資源轉換層。按照官方對於名稱空間的建議,我們可以在 app/Modules/Books/Resources 資料夾中建立它。

<?php
namespace App\Modules\Books\Resources;
use Illuminate\Http\Resources\Json\Resource;
class BookResource extends Resource
{
    public function toArray($request)
    {
        return [
            'id' => $this->resource->id,
            'title' => $this->resource->title,
        ];
    }
}
複製程式碼

4- Authors 模組

我們還可以通過 Routes 檔案來啟動 Authors 模組。

<?php
use App\Modules\Authors\ListAuthors;
use Illuminate\Support\Facades\Route;
Route::get('/authors', ListAuthors::class);
複製程式碼

注意:  app/Modules/Authors 這個名稱空間正表示我們所編寫的檔案,對於請求處理程式來說也是非常簡單的。

<?php
namespace App\Modules\Authors;
use App\Eloquent\Author;
use App\Modules\Authors\Resources\AuthorResource;
class ListAuthors
{
    public function __invoke(Author $author)
    {
        return AuthorResource::collection($author->paginate());
    }
}
複製程式碼

最後,我們將編寫的 Resource 類轉變為響應式的 JSON 格式。

<?php
namespace App\Modules\Authors\Resources;
use App\Modules\Books\Resources\BookResource;
use Illuminate\Http\Resources\Json\Resource;
class AuthorResource extends Resource
{
    public function toArray($request)
    {
        return [
            'id' => $this->resource->id,
            'name' => $this->resource->name,
            'books' => $this->whenLoaded('books', function () {
                return BookResource::collection($this->resource->books);
            })
        ];
    }
}
複製程式碼

注意資源是如何進入另一個模組以重用 BookResource 。 這通常不是一個比較好的選擇,因為模組應該是完全自給自足的,並且只能重用標準類,例如 Eloquent Models 或設計用於在任何模組上通用的通用的元件。 這個問題的解決方案通常是將 BookResource 複製到 Authors 模組中,從而可以在不使用另一個模組的情況下進行更改,反之亦然。 我決定保留這個跨模組的用法,這個例子表現出一個很好的經驗方法,就是讓模組之間彼此隔離,但是如果你認為上面的例子很簡單並且不太可能帶來任何問題。 始終確保編寫測試以涵蓋您編寫的功能,以避免其他人在不知不覺中修改您的應用程式。

5- 結語

雖然這是一個非常簡單的例子,但我希望它能夠讓人們根據自己的需要來輕鬆操作使用 Laravel 框架的結構標準。您可以非常輕鬆地更改檔案的位置,以便構建基於模組化的應用程式。我的大多數專案都附帶了 App / Components 模組,可以適用於任何模組可重用的泛型別的基礎類; App / EloquentModules 資料夾可以用於儲存 Eloquent 模型和資料庫關係模型,我們可以在其中構建任何基於模組化的功能。 這是我最近開始研究的應用程式的資料夾目錄結構:

file

我希望每個人都能從中得到這個概念,每個模組都有自己的需求,並且可以擁有自己的資料夾/實體/類/方法/屬性。沒有必要將所有模組標準化完全相同,因為某些模組比其他模組簡單得多,並且不需要大量的結構設計。此示例顯示AccountChurn模組通過 HTTP 資料夾提供 API,同時仍通過控制檯提供 Artisan 命令。另一方面,AccountOverview則僅提供 HTTP API,並且依賴倉庫、值物件(bags)以及服務類(paginators)來提供更大的資料價值。