Áp dụng Builder (Manager) Pattern trong Laravel để quản lý bên thứ 3 cung cấp dịch vụ

Nội dung

Bài toán đặt ra

Trong rất nhiều ứng dụng chúng ta sẽ phải sử dụng dịch vụ của bên thứ 3. Ở đây tôi ví dụ dịch vụ gửi OTP qua SMS chẳng hạn. Để đảm bảo hệ thống chạy ngon lành và không phụ thuộc vào sự cố của nhà cung cấp, chúng ta nên sử dụng ít nhất 02 nhà cung cấp dịch vụ. Trong bài viết này, tôi giả định có 3 nhà cung cấp OTP qua SMS là Abenla, EsmsNexmo. Làm thế nào để quản lý các nhà cung cấp dịch vụ này trong Laravel?

Giải pháp

Trong Laravel vấn đề trên có thể được giải quyết thông qua Builder (Manager) Pattern. Các bạn tham khảo thêm từ bài viết Builder (Manager) Pattern.

Triển khai

Bước 1: Tạo thư mục quản lý các dịch vụ

Tạo một class OtpManager theo đường dẫn app/Managers/OtpManager.php để quản lý OTP. Nội dung file OtpManager.php

<?php

namespace App\Managers;

use App\Services\Otp\AbenlaOtpService;
use App\Services\Otp\EsmsOtpService;
use App\Services\Otp\NexmoOtpService;
use Illuminate\Support\Manager;

class OtpManager extends Manager
{

    /**
     * @inheritDoc
     */
    public function getDefaultDriver()
    {
        // TODO: Implement getDefaultDriver() method.
        return 'abenla';
    }

    /**
     * Driver Abenla
     *
     * @return AbenlaOtpService
     */
    public function createAbenlaDriver()
    {
        return new AbenlaOtpService();
    }

    /**
     * Driver esms
     *
     * @return EsmsOtpService
     */
    public function createEsmsDriver()
    {
        return new EsmsOtpService();
    }

    /**
     * Driver nexmo
     *
     * @return NexmoOtpService
     */
    public function createNexmoDriver()
    {
        return new NexmoOtpService();
    }
}

Trong đó, hàm getDefaultDriver() sẽ trả về nhà cung cấp mặc định khi ta không truyền vào. Các hàm createAbenlaDriver(), createEsmsDriver()createNexmoDriver() sẽ được tự động kích hoạt khi ta gọi nhà cung cấp tương ứng. Như vậy, trong mỗi hàm trên sẽ xử lý nghiệp vụ tương ứng với từng nhà cung cấp. Khi có nhà cung cấp mới, các bạn bổ sung thêm hàm vào để giao nhiệm vụ cho class tương ứng xử lý. Cụ thể:

  • Khi bạn muốn gửi OTP bởi nhà cung cấp Abenla thì class AbenlaOtpService sẽ đảm nhiệm
  • Khi bạn muốn gửi OTP bởi nhà cung cấp Esms thì class EsmsOtpService sẽ đảm nhiệm
  • Khi bạn muốn gửi OTP bởi nhà cung cấp Nexmo thì class NexmoOtpService sẽ đảm nhiệm

Thêm nhà cung cấp mới theo mẫu hàm create[Mã nhà cung cấp]Driver().

Bước 2: Tạo class xử lý nghiệp vụ theo từng nhà cung cấp

Các bạn có thể tổ chức thư mục để quản lý như ảnh minh họa

Trong đó, tôi có tạo 1 interface để quản lý các hàm nghiệp vụ chung. Nội dung file app/Services/Otp/OtpInterface.php tham khảo

<?php

namespace App\Services\Otp;

interface OtpInterface
{
    public function sendOtpByVoice($mobile, $otp, $options);
    public function sendOtpBySms($mobile, $otp, $options);
}

Class xử lý khi kết nối với nhà cung cấp Abenla, xem file app/Services/Otp/AbenlaOtpService.php

<?php

namespace App\Services\Otp;

class AbenlaOtpService implements OtpInterface
{

    public function sendOtpByVoice($mobile, $otp, $options)
    {
        // TODO: Implement sendOtpByVoice() method.
        return 'AbenlaOtpService:sendOtpByVoice:' . $mobile;
    }

    public function sendOtpBySms($mobile, $otp, $options)
    {
        // TODO: Implement sendOtpBySms() method.
        return 'AbenlaOtpService:sendOtpBySms:' . $mobile;
    }
}

Class xử lý khi kết nối với nhà cung cấp Esms app/Services/Otp/EsmsOtpService.php

<?php

namespace App\Services\Otp;

class EsmsOtpService implements OtpInterface
{

    public function sendOtpByVoice($mobile, $otp, $options)
    {
        // TODO: Implement sendOtpByVoice() method.
        return 'EsmsOtpService:sendOtpByVoice:'.$mobile;
    }

    public function sendOtpBySms($mobile, $otp, $options)
    {
        // TODO: Implement sendOtpBySms() method.
        return 'EsmsOtpService:sendOtpBySms:'.$mobile;
    }
}

Class xử lý khi kết nối với nhà cung cấp Nexmo app/Services/Otp/NexmoOtpService.php

<?php

namespace App\Services\Otp;

class NexmoOtpService implements OtpInterface
{

    public function sendOtpByVoice($mobile, $otp, $options)
    {
        // TODO: Implement sendOtpByVoice() method.
        return 'NexmoOtpService:sendOtpByVoice:'.$mobile;
    }

    public function sendOtpBySms($mobile, $otp, $options)
    {
        // TODO: Implement sendOtpBySms() method.
        return 'NexmoOtpService:sendOtpBySms:'.$mobile;
    }
}

Bước 3: Sử dụng

Bạn có thể tạo một artisan command để test theo đường dẫn app/Console/Commands/Dev/TestCommand.php. Trong hàm handle, ta viết như sau:

public function handle(OtpManager $otpManager)
    {
        // Manager
        //$result = $otpManager->driver('abenla')->sendOtpBySms('0912345678', '113115', []);
        //$result = $otpManager->driver('esms')->sendOtpBySms('0912345678', '113115', []);
        $result = $otpManager->driver('nexmo')->sendOtpBySms('0912345678', '113115', []);

        print_r($result);

    }

Khi dùng dịch vụ của nhà cung cấp nào Bạn chỉ cần truyền qua driver('nexmo') là xong.

Lưu ý: khi Bạn gọi driver('nexmo') thì Laravel sẽ tự động gọi đến hàm createNexmoDriver() trong file app/Managers/OtpManager.php để xử lý. Hàm này sẽ giao cho class NexmoOtpService() để xử lý nghiệp vụ cụ thể.

Hy vọng bài viết sẽ góp ích cho các Bạn!
Nguồn: Viblo

Anthony Nguyễn

Cây bút chính tại VietnamTutor

Bài viết cùng chuyên mục

Vibe coding và nợ kỹ thuật thầm lặng: kiểm soát bằng kỹ thuật ngữ cảnh

Vibe coding tăng tốc nhưng âm thầm tích lũy nợ kỹ thuật. Bài này giải thích vì sao và cách context engineering giúp bạn giữ codebase

Git stash: lưu tạm công việc dở và khôi phục đúng lúc

Git stash cho phép lưu tạm các thay đổi chưa commit để chuyển sang task khác, rồi khôi phục lại đúng lúc — tránh commit dở

Git rebase bị conflict: cách sửa hoặc abort an toàn

Hướng dẫn xử lý git rebase bị conflict: cách kiểm tra file lỗi, sửa conflict marker, tiếp tục rebase, abort an toàn và khi nào không

Bảo mật vibe coding: đừng để AI sinh lỗ hổng website

Bảo mật vibe coding không chỉ là quét lỗi sau khi AI viết code. Bài viết đưa ra checklist kiểm soát secret, prompt injection, dependency, auth

Kiểm thử code do AI tạo: danh sách kiểm tra trước khi deploy

Code do AI tạo cần được kiểm thử theo nhiều lớp trước khi lên website thật. Checklist này giúp bạn kiểm tra chức năng, bảo mật,

Lỡ push .env lên Git: cách xử lý secret bị lộ

Lỡ push .env lên Git không chỉ là lỗi xóa file. Hãy revoke hoặc rotate secret trước, rồi dọn Git history và ngăn sự cố lặp

Quy trình vibe coding 7 bước: từ ý tưởng đến prototype

Quy trình vibe coding hiệu quả bắt đầu từ giả thuyết kinh doanh, không phải prompt dài. Bài viết hướng dẫn 7 bước từ brief, dữ

Vibe coding cho doanh nghiệp: việc nào nên làm bằng AI?

Vibe coding cho doanh nghiệp hữu ích khi cần thử prototype, dashboard hoặc automation nhỏ. Bài viết giúp bạn phân loại use case theo mức rủi

AI viết code nhanh hơn review: kiểm soát thế nào?

Khi AI viết code nhanh hơn khả năng review của team, doanh nghiệp cần đổi cách kiểm soát chất lượng. Bài viết đưa ra framework thực

Git detached HEAD là gì? Cách thoát an toàn

Bài viết này giải thích detached HEAD trong Git, cách nhận biết trạng thái này và các bước thoát ra an toàn mà không mất code.

Rủi ro vibe coding: 9 lỗi khiến prototype khó vận hành

Bài viết này chỉ ra 9 rủi ro phổ biến khi vibe coding và cách kiểm soát để prototype không biến thành gánh nặng vận hành.

Vibe coding là gì? Cách thử ý tưởng phần mềm bằng AI

Vibe coding giúp chủ doanh nghiệp biến ý tưởng phần mềm thành prototype nhanh hơn bằng AI. Bài viết này chỉ ra khi nào nên thử,