基于 Module 的 Laravel API 架构

file

我非常喜欢编写基于模块化设计的软件和编程方式,但我不太喜欢依赖第三方软件包和类库来处理一些琐碎的事情,因为它们不会让你的编程水平得到很好的提升。所以这两年来,我一直在用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)来提供更大的数据价值。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://hackernoon.com/simple-and-comple...

译文地址:https://learnku.com/laravel/t/21913

本帖已被设为精华帖!
本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 4

controller做错了什么?你要say bye bye

5年前 评论

“每个模块都有自己的需求,并且可以拥有自己的文件夹/实体/类/方法/属性。没有必要将所有模块标准化完全相同,因为某些模块比其他模块简单得多,并且不需要大量的结构设计” 说得非常好。当一个项目可能会发展非常多业务模块,并且也不考虑搞微服务的时候,让逻辑更加清晰。

5年前 评论

不知道作者大佬,最近有没有新的关于模块化的见解与总结。
最近我在找模块化开发的相关知识,不想采用第三方,感觉没提升空间,哈哈。

4年前 评论

这个只能查询,也不能增删改查

3年前 评论
Boxer 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!