+3

Series Thiết kế Kiến trúc Modules Thủ công | Bài 1: Trở về nguyên bản - Tự build Modules bằng Core Laravel

Chào anh em! Khi nhắc đến việc chia project Laravel thành các Modules (User, Order, Payment...), 90% anh em developer sẽ nghĩ ngay đến việc gõ lệnh composer require nwidart/laravel-modules. Đó là một package tuyệt vời.

Nhưng anh em đã bao giờ tự hỏi: Nếu không có package đó, làm sao để bắt Laravel hiểu một thư mục hoàn toàn xa lạ nằm ngoài app/ là một Module hợp lệ? Thực tế, ở các hệ thống enterprise lớn, các kỹ sư thường không dùng package. Không có modules_statuses.json, không có lệnh artisan module:make thần thánh. Họ tự dựng một kiến trúc module "thủ công" hoàn toàn bằng tính năng gốc của Laravel. Cách làm này nhẹ hơn, nhanh hơn và giúp anh em thực sự làm chủ framework thay vì phụ thuộc vào "phép thuật" của bên thứ ba.

Hôm nay, chúng ta sẽ mổ xẻ 3 cơ chế cốt lõi để tạo ra kiến trúc này.

1. Cơ chế 1: Khai báo không gian với PSR-4 Autoloading

Theo mặc định, Laravel chỉ quét và tự động nạp (autoload) các class nằm trong thư mục app/ (tương ứng với namespace App). Để hệ thống nhận diện được thư mục modules/ nằm ở thư mục gốc, chúng ta phải can thiệp trực tiếp vào file composer.json.

Anh em chỉ cần thêm đúng 1 dòng khai báo chuẩn PSR-4:

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Modules\\": "modules/", 
        "Database\\Factories\\": "database/factories/",
        "Database\\Seeders\\": "database/seeders/"
    }
}

Nhờ dòng cấu hình này, khi anh em gọi class Modules\Admin\Base\Providers\BaseServiceProvider, Composer sẽ tự động hiểu và map nó sang đúng đường dẫn vật lý: modules/Admin/Base/Providers/BaseServiceProvider.php.

Lưu ý: Bất cứ khi nào tạo thêm class mới ở giai đoạn setup thư mục, đừng quên chạy lệnh huyền thoại composer dump-autoload để hệ thống cập nhật lại danh sách map.

2. Cơ chế 2: Service Provider - "Trái tim" của mỗi Module

Nếu không có package nwidart tự động scan module.json, làm sao Laravel biết một Module có chứa routes, views, hay migrations gì để mà load?

Câu trả lời nằm ở Illuminate\Support\ServiceProvider – tính năng nguyên thủy và quyền lực nhất của Laravel. Mỗi module thủ công sẽ có một Service Provider riêng biệt làm nhiệm vụ "đăng ký hộ khẩu" cho tài nguyên của nó.

Ví dụ với một module Base của Admin, file Provider sẽ trông như thế này:

<?php
namespace Modules\Admin\Base\Providers;

use Illuminate\Support\ServiceProvider;

class BaseServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // 1. Nạp file Routes của module
        $this->loadRoutesFrom(__DIR__ . '/../routes.php');

        // 2. Nạp Views và gán namespace 'admin-base'
        // Khi gọi view: return view('admin-base::index');
        $this->loadViewsFrom(__DIR__ . '/../Resources/views', 'admin-base');

        // 3. Nạp file đa ngôn ngữ (Translations)
        $this->loadTranslationsFrom(__DIR__ . '/../Resources/lang', 'admin-base');

        // 4. Nạp Migrations (nếu có)
        $this->loadMigrationsFrom(__DIR__ . '/../Database/Migrations');
    }

    public function register()
    {
        // Đăng ký Middleware, Bindings hoặc các cấu hình core tại đây
        app('router')->aliasMiddleware('auth-admin', \Modules\Admin\Base\Http\Middleware\AdminAuth::class);
    }
}

Như anh em thấy, loadRoutesFrom, loadViewsFrom, aliasMiddleware... toàn bộ đều là API có sẵn của Laravel. Chúng ta tự tay gọi chúng lên, cực kỳ rõ ràng, không có một "hộp đen" nào giấu giếm logic ở đây cả!

3. Cơ chế 3: "Công tắc nguồn" tại config/app.php

Khi đã có Service Provider, chúng ta cần một bước cuối cùng: Khai báo nó với hệ thống lõi.

Thay vì dựa vào cơ chế discover tự động lằng nhằng, kiến trúc thủ công chọn cách rành mạch nhất: Liệt kê tay trong mảng providers của file config/app.php.

// config/app.php

'providers' => [
    /*
     * Laravel Framework Service Providers...
     */
    Illuminate\Auth\AuthServiceProvider::class,
    // ...

    /*
     * Application Service Providers...
     */
    App\Providers\AppServiceProvider::class,
    App\Providers\RouteServiceProvider::class,

    /*
     * --------------------------------------------------------------------------
     * Modules Service Providers
     * --------------------------------------------------------------------------
     */
    Modules\Admin\Base\Providers\BaseServiceProvider::class,
    Modules\Order\Providers\OrderServiceProvider::class,
    Modules\Payment\Providers\PaymentServiceProvider::class,
],

Cách làm này mang lại một quyền năng tối thượng: Tính tường minh. Anh em nhìn vào file app.php là biết ngay project đang có bao nhiêu module chạy. Muốn "tắt" hoàn toàn một module (ví dụ module Payment đang bị lỗi)? Rất đơn giản, chỉ cần comment dòng Modules\Payment... lại là xong. Mọi Routes, Views, Middleware của module đó lập tức bốc hơi khỏi bộ nhớ hệ thống.

Tổng kết Bài 1

So sánh với các package tạo sẵn, kiến trúc module thủ công đòi hỏi anh em phải setup mọi thứ bằng tay, không có lệnh scaffold sinh code tự động. Nhưng đổi lại, chúng ta có một hệ thống cực nhẹ (Zero Dependency), quá trình debug lỗi dễ như ăn kẹo vì luồng chạy vô cùng trong suốt, và đặc biệt là kỹ năng thao tác với Core Laravel của anh em sẽ tăng lên một đẳng cấp mới.

Ở bài tiếp theo, chúng ta sẽ bắt tay vào thực hành tạo cấu trúc thư mục chuẩn cho một module và viết script thay thế lệnh module:make để tự động hóa khâu tạo file lặp đi lặp lại.

Để chuẩn bị cho Bài 2, bạn muốn chúng ta tập trung vào việc quy hoạch chuẩn cấu trúc thư mục (như Http, Entities, Repositories bên trong module) trước, hay là đi thẳng vào việc viết một Bash script/Artisan command tùy chỉnh để sinh code tự động giống chức năng make?


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí