Laravel

Multi-Tenancy là gì? How to implement with Simple Trait in Laravel?

Multi-Tenancy là gì?

Multi-Tenancy is a single instance of software that serves multiple customers privately.

Hiểu đơn giản Multi-Tenancy là một hệ thống web application có nhiều khách hàng cùng sử dụng nhưng dữ liệu giữa các khách hàng hoàn tập độc lập, khách hàng này không thể truy cập vào dữ liệu của khách hàng kia.

Có thể kể đến vài ví dụ như:

  • Hệ thống quản lý khách sạn cho phép nhiều khách sạn có thể truy cập với những tài khoản độc lập, dữ liệu độc lập, nhưng vẫn cùng chung 1 hệ thống site.
  • Hệ thống quản lý công văn sử dụng trong tổng công ty và nhiều công ty con, cùng site nhưng dữ liệu độc lập.
  • Hệ thống quản lý dự án Jira
  • Hệ thống CRM của zoho, saleforce ....

Bài toán multi-tenancy thực tế gặp rất nhiều và tùy theo từng hệ thống sẽ triển khai theo mỗi cách khác nhau. Ở bài viết này, mình sẽ cùng bạn tìm hiểu để implement một cách đơn giản nhất vào project Laravel Framework nhé (Vì mình cũng đang tìm hiểu chưa có biết nhiều 😃))

Bài toán

Có một hệ thống quản lí khách sạn và danh sách book phòng theo từng khách sạn.

  • CEO của khách sạn A đăng kí tài khoản vào hệ thống, sau đó tạo các khách sạn để quản lí.
  • CEO của khách sạn B cũng tương tự vậy (đăng kí tài khoản vào hệ thống, sau đó tạo các khách sạn để quản lí.)

Vấn đề:

  • Nếu không Multi-Tenancy: CEO A có thể thấy được các thông tin của khách sạn ông B gồm: tên, địa chỉ của các khách hàng đã đặt phòng trên khách sạn ông B ==> Lộ thông tin rồi, ông A lúc này có thể lợi dụng những thông tin có được để thực hiện những hành vi không đúng: spam email + số điện thoại chẳng hạn... ✌️
  • Nếu có Multi-Tenancy: Tất cả dữ liệu khách sạn của ông A và ông B hoàn toàn độc lập, không liên quan và không ai có thể thấy hay truy cập vào các dữ liệu của người khác (trừ thằng admin).

Migration

Tạo thêm 1 trường created_by_user_id trong bảng hotels. Trường này dùng để xác định ông nào đã tạo ra cái khách sạn ấy trên hệ thống.

Schema::table('hotels', function (Blueprint $table) {
    $table->unsignedBigInteger('created_by_user_id');
    $table->foreign('created_by_user_id')->references('id')->on('users');
});
  • Note: Từ ver 5.8, Laravel đã thay đổi kiểu dữ liệu (data type) trường Id là big int. Vậy nên khi liên kết khóa ngoại, bạn cũng phải set data type của trường liên kết là big int nhé.

Sau đó, thêm created_by_user_id vào fillable trong Model

protected $fillable = [
    'title',
    'name',
    'address',
    'created_at',
    'updated_at',
    'created_by_user_id',
];

Bây giờ làm sao để field tự insert vào DB khi user tạo mới một Hotels? Bạn có thể sử dụng Model Observers. Tuy nhiên để mở rộng và tái sử dụng ở các Model khác nhau, mình sẽ tạo ra 1 Trait Multitenantable thay vì cứ mỗi Model mình lại tạo ra 1 Observers như vầy.

Trait Multitenantable

Tạo file app/Traits/Multitenantable.php

namespace App\Traits;

trait Multitenantable {

    protected static function bootMultitenantable()
    {
        if (auth()->check()) {
            static::creating(function ($model) {
                $model->created_by_user_id = auth()->id();
            });
        }
    }

}
  • Note: hàm bootMultitenantable() với name convention là bootXYZ(), Laravel sẽ tự chạy hàm này khi Trait được sử dụng (Có thể gọi nó là trait “constructor”)

Trait Multitenantable sẽ có nhiệm vụ khi user đó tạo một thứ gì đó thì nó tự động insert vào DB ở field created_by_user_id với value là id của user đang đang nhập.

Implement trait này vào model Hotel

use App\Traits\Multitenantable;

// ...

class Hotel extends Model
{
    use Multitenantable;
    // ...
}

Kết quả khi tạo mới 1 Hotel:

Filtering Data By User

Đây là bước quan trọng nhất để giải quyết bài toán: Tất cả dữ liệu khách sạn của ông A và ông B hoàn toàn độc lập, không liên quan và không ai có thể thấy hay truy cập vào các dữ liệu của người khác.

Trong Laravel rất đơn giản, ta dùng Global Scope để fillter tất cả các query trên Model.

Thêm vào trait Multitenantable như sau:

namespace App\Traits;

use Illuminate\Database\Eloquent\Builder;

trait Multitenantable {

    protected static function bootMultitenantable()
    {
        if (auth()->check()) {
            static::creating(function ($model) {
                $model->created_by_user_id = auth()->id();
            });

            static::addGlobalScope('created_by_user_id', function (Builder $builder) {
                $builder->where('created_by_user_id', auth()->id());
            });
        }
    }

}

Oki, lúc này nó sẽ lọc tất cả các khách sạn của user đang nhập hiện tại, còn các khách sạn của user khác hoàn toàn ko hiển thị và không access vào được.

SQL thuần nó sẽ như này:SELECT * FROM hotels WHERE created_by_user_id=1 (với 1 là id của user đang đăng nhập).

Kết quả ở Hotel list:

Thử truy cập vào thằng hotel có id=1 mà chưa set trường created_by_user_id:

404 ngay ✌️

Bây giờ bạn thử set created_by_user_id=2, back lại và xem kết quả nhé (Thêm 1 record ở list + có thể access vào rồi)

What About Administrator To See All Entries?

Ngoài ra để đảm bảo thằng Admin có thể view được hết các dữ liệu trên hệ thống thì bạn check thêm if nhé.

protected static function bootMultitenantable()
{
    if (auth()->check()) {
        static::creating(function ($model) {
            $model->created_by_user_id = auth()->id();
        });

        // if user is not administrator - role_id 1
        if (auth()->user()->role_id != 1) {
            static::addGlobalScope('created_by_user_id', function (Builder $builder) {
                $builder->where('created_by_user_id', auth()->id());
            });
        }
    }
}

Ok, vậy là coi như mình đã implement Multi-Tenancy trên model Hotels rồi.

Giờ muốn tái sử dụng Trait này, thì bạn cứ use trong Model là ngon ngay 🎮 (Nhớ model implement phải có trường created_by_user_id)

Kết luận

Trên đây mình đã giới thiệu với bạn Multi-Tenancy là gì? Cách implement Multi-Tenancy một cách đơn giản nhất vào project Laravel Framework như thế nào. Hi vọng từ bài viết này bạn có thể hiểu về Multi-Tenancy và áp dụng vào các bài toán của mình. Nếu có thắc mắc và góp ý bạn comment phía dưới nhé. Cảm ơn bạn.

Tham khảo:

Registration Login
Sign in with social account
or
Lost your Password?
Registration Login
Sign in with social account
or
A password will be send on your post
Registration Login
Registration