অধ্যায় ৮

Driver Pattern

"One stable API, interchangeable backends."

মূল কোড না বদলে ব্যাকএন্ড বদলানো — Cache, Queue, Filesystem, Mail, Session, Notification, Logging সবখানে।

~১৪ মিনিট১২টি স্লাইড

ইন্টারফেস এক, ব্যাকএন্ড অনেক

প্রোডাকশনে ক্যাশ Redis-এ। টেস্টে array driver। স্টেজিং-এ হয়তো database। কিন্তু আপনার কোড কোথাও বদলায় না:

Cache::put('key', 'value', 3600);
Cache::put() CACHE_STORE=? redis array database file

লারাভেল কী করতে চান আর কীভাবে হবে আলাদা করে। আপনি একটি স্থির API-এর বিপরীতে কোড লেখেন; ব্যাকএন্ড শুধু একটি কনফিগারেশন ডিটেইল।

.env
CACHE_STORE=redis   # Laravel 11+ (আগে ছিল CACHE_DRIVER)

এই বদলযোগ্য ব্যাকএন্ডগুলোকে লারাভেল বলে driver (সাবসিস্টেম ভেদে store, disk, connection বা channel)। এ কারণেই লারাভেল অ্যাপে ইনফ্রাস্ট্রাকচার বদলানো সাধারণত রিরাইট নয়, শুধু কনফিগ পরিবর্তন।

চার অংশের কাঠামো

লারাভেলের বেশিরভাগ driver-ভিত্তিক সিস্টেম একই চার অংশে গড়া:

১. Public API
Facade
২. Manager
কোন driver?
৩. Driver
Contract মেনে
৪. Config
.env

১. Public API

আপনি যার সাথে কথা বলেন, সাধারণত facade:

Cache::get('key');
Storage::put('file.txt', $data);
Mail::send(...);

২. Manager

কনফিগ দেখে কোন driver ব্যবহার হবে তা রিজলভ করে। Cache::store('redis') কল করলে CacheManager Redis driver বানিয়ে ফেরত দেয়। প্রতিটি driver-ভিত্তিক সাবসিস্টেমের একটি Manager ক্লাস আছে।

৩. Driver (Concrete Implementation)

প্রতিটি driver একই contract মানে, কিন্তু আলাদা ব্যাকএন্ডে কথা বলে। Redis cache আর file cache দুটোই Illuminate\Contracts\Cache\Store ইমপ্লিমেন্ট করে — একটা Redis-এ, আরেকটা ডিস্কে লেখে।

৪. Configuration

'default' => env('CACHE_STORE', 'redis'),

এটুকুই পুরো কাঠামো। বাকি সব public API-এর পেছনে লুকানো।

কোথায় আছে (১)

বাইরের ইনফ্রাস্ট্রাকচারের সাথে কথা বলা প্রায় প্রতিটি সাবসিস্টেমে।

Cache

  • redis
  • memcached
  • file
  • database
  • array

array driver টেস্টে দারুণ — দ্রুত, অস্থায়ী, কোনো external dependency নেই।

Queue

  • redis
  • database
  • sqs
  • sync
  • null

sync রিকোয়েস্টের ভেতরেই job চালায় (ডিবাগিং-এ সুবিধা); null job নীরবে বাদ দেয়।

Filesystem

  • local
  • public
  • s3
  • ftp
  • sftp

লোকালে ডেভেলপ, S3-তে ডিপ্লয় — অ্যাপ কোড না বদলে। এখানেই driver প্যাটার্নের লাভ সবচেয়ে স্পষ্ট।

Mail

  • smtp
  • ses
  • mailgun
  • postmark
  • log

log ইমেইল লগ ফাইলে লেখে — ডেভেলপমেন্টে আসল মেইল না পাঠিয়ে যাচাই।

Cache::put('key', 'value');
dispatch(new ProcessOrder);
Storage::disk('s3')->put('file.jpg', $content);
Mail::to($user)->send(new OrderReceipt($order));

কোথায় আছে (২)

Sessions

  • file
  • cookie
  • database
  • redis
session(['key' => 'value']);

Notifications

  • mail
  • database
  • broadcast
  • slack
  • sms (vonage)
$user->notify(new InvoicePaid);

Notification-এর “channel” হলো এর driver। প্রতিটি channel একই send() contract মানে, ভিন্ন মাধ্যমে পৌঁছায়।

Logging

  • single
  • daily
  • stack
  • syslog
  • slack
Log::info('Something happened');

stack একাধিক channel একসাথে — ফাইলেও লেখো, external সার্ভিসেও পাঠাও।

Database

  • mysql
  • mariadb
  • pgsql
  • sqlite
  • sqlsrv
DB::connection('pgsql')->table('users')->get();

Query Builder আর Eloquent একই থাকে; শুধু connection বদলায়।

কেন এই প্যাটার্ন জরুরি

কোড রিরাইট ছাড়াই ইনফ্রা বদল

কোড যখন নির্দিষ্ট সার্ভিসের বদলে contract-এর সাথে কথা বলে, ব্যাকএন্ড হয়ে যায় একটি কনফিগ ভ্যালু। Local → S3, Redis → Memcached, SES → Postmark — কনফিগ বা .env-এ এক লাইন।

ফার্স্ট-ক্লাস টেস্টিং

Contract-এর কারণে লারাভেল fake ইমপ্লিমেন্টেশন দিতে পারে, যা আসল সার্ভিসে না গিয়ে কলগুলো ধরে রাখে:

Mail::fake();
Queue::fake();
Storage::fake('s3');
Notification::fake();

// ... কোড চালান ...

Mail::assertSent(OrderReceipt::class);
Queue::assertPushed(ProcessOrder::class);

টেস্ট দ্রুত, external ইনফ্রা লাগে না। Redis বা Mailgun-এ কী হলো নয়, কী কল হলো তা assert করেন।

সহজ এক্সটেনশন

কাস্টম ব্যাকএন্ড লাগলে Manager এক্সটেন্ড করুন — বাকি কোড আগের মতোই Cache::get() ডাকবে।

পরিষ্কার দায়িত্ব বিভাজন

আপনার কোড উদ্দেশ্য বলে: “এটা cache করো”, “এটা পাঠাও”, “এটা queue করো”। Driver মেকানিক্স সামলায়। ফলে business logic না ছুঁয়েই মেকানিক্স বদলানো যায়।

নাম আলাদা, কনসেপ্ট এক

সব সাবসিস্টেম “driver” শব্দ ব্যবহার করে না:

সাবসিস্টেমব্যবহৃত শব্দকনফিগ ফাইল
Cache, SessionsStoreconfig/cache.php
FilesystemDiskconfig/filesystems.php
Queue, DatabaseConnectionconfig/queue.php, config/database.php
Notifications, LoggingChannelconfig/logging.php
MailTransport / Mailerconfig/mail.php

লারাভেলের কনফিগ ফাইলে এই শব্দগুলোর যেকোনোটা দেখলে বুঝবেন — আপনি driver প্যাটার্ন দেখছেন।

লারাভেলে Custom Driver যোগ করা

extend মেথড দিয়ে নিজের driver ফ্রেমওয়ার্কে প্লাগ-ইন করা যায়। ধরুন MongoDB-তে ক্যাশ রাখতে চান:

app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Cache;

public function boot(): void
{
    Cache::extend('mongo', function ($app) {
        return Cache::repository(new MongoStore); // MongoStore implements Store
    });
}
config/cache.php
'stores' => [
    'mongo' => [
        'driver' => 'mongo',
    ],
],
.env
CACHE_STORE=mongo

এখন পুরো অ্যাপ MongoDB-তে ক্যাশ রাখবে। কোনো কন্ট্রোলার বদলাতে হবে না — সবাই আগের মতোই Cache::get() / Cache::put() ডাকবে।

একই কৌশল: Storage::extend(), Mail::extend(), Auth::extend(), Session::extend()।

নিজের কোডে: Manager বানানো

একই অপারেশনের জন্য একাধিক ব্যাকএন্ড লাগলে এই প্যাটার্ন নিজের কোডেও কাজে লাগে:

  • Payment: Stripe, PayPal, SSLCommerz, bKash
  • SMS: Twilio, Vonage, লোকাল গেটওয়ে
  • Search: Algolia, Meilisearch, Elasticsearch
  • AI: OpenAI, Anthropic, লোকাল মডেল
  • Export: CSV, Excel, PDF

১. Contract

interface PaymentGateway
{
    public function charge(int $amount, string $token): PaymentResult;
}

২. Driver

class StripeGateway implements PaymentGateway { /* ... */ }
class SslCommerzGateway implements PaymentGateway { /* ... */ }

৩. Manager — Illuminate\Support\Manager এক্সটেন্ড করে

use Illuminate\Support\Manager;

class PaymentManager extends Manager
{
    public function getDefaultDriver(): string
    {
        return $this->config->get('payment.default', 'stripe');
    }

    // 'stripe' → createStripeDriver()
    public function createStripeDriver(): PaymentGateway
    {
        return new StripeGateway($this->config->get('payment.stripe.secret'));
    }

    // 'sslcommerz' → createSslcommerzDriver()
    public function createSslcommerzDriver(): PaymentGateway
    {
        return new SslCommerzGateway($this->config->get('payment.sslcommerz'));
    }
}

Driver রিজলভ করার লজিক (create{Name}Driver খোঁজা, ক্যাশ করা, extend()) Manager নিজেই দেয় — শূন্য থেকে বানাতে হয় না।

Manager ব্যবহার ও সতর্কতা

৪. রেজিস্টার ও ব্যবহার

// AppServiceProvider::register()
$this->app->singleton(PaymentManager::class, fn ($app) => new PaymentManager($app));
$payments = app(PaymentManager::class);

$payments->charge(500, $token);                     // ডিফল্ট driver
$payments->driver('sslcommerz')->charge(500, $token); // নির্দিষ্ট driver
.env
PAYMENT_GATEWAY=sslcommerz

অ্যাপ কোড contract-এর সাথে কথা বলে, driver-এর সাথে নয়। কাল নতুন গেটওয়ে এলে শুধু একটি নতুন driver ক্লাস আর একটি create...Driver() মেথড।

Premature abstraction থেকে সাবধান! শুধু Stripe নেন, অথচ Manager + Contract + Config + তিনটি Driver বানিয়ে ফেললেন — এটা অতিরিক্ত জটিলতা।

দ্বিতীয় ব্যাকএন্ড আসলে তখনই বুঝবেন abstraction দরকার। তার আগে নয়।

পুরো লারাভেলে Driver

Driver/store প্যাটার্ন ফ্রেমওয়ার্কের সবচেয়ে দৃশ্যমান কাঠামোগত সিদ্ধান্তগুলোর একটি। Cache, queue, filesystem, mail, session, notification, logging — সবাই এটা ব্যবহার করে। আপনি যে API-তে লেখেন তা স্থির; কনফিগ ভ্যালু অনুযায়ী ব্যাকএন্ড বদলায়।

Failover-এর সাথে সংযোগ

এ কারণেই লারাভেল failover সাপোর্ট করতে পারে। Failover cache store বা failover mailer ডিফাইন করলে আপনি আসলে লারাভেলকে driver-এর একটি ক্রমানুসারে তালিকা দিচ্ছেন। Failover কাজ করে কারণ একই facade-এর পেছনের সব driver একই contract মানে।

Driver প্যাটার্ন ছাড়া failover সম্ভব হতো না।

Driver Pattern একই Contract Failover সম্ভব Fake দিয়ে টেস্ট extend() সম্ভব

সারসংক্ষেপ ও যাচাই

  • চার অংশ: Public API (Facade) → Manager → Driver (Contract) ← Config।
  • Store, Disk, Connection, Channel, Transport — সবই driver-এর ভিন্ন নাম।
  • Contract আছে বলেই Mail::fake(), Queue::fake() আর failover সম্ভব।
  • নিজের Manager বানাতে Illuminate\Support\Manager এক্সটেন্ড করুন।
  • দ্বিতীয় ব্যাকএন্ড আসার আগে abstraction বানাবেন না।

নিজেকে যাচাই করুন

Local থেকে S3-তে যেতে কন্ট্রোলারে কী বদলাতে হবে?

কিছুই না (ডিফল্ট disk ব্যবহার করলে)। শুধু FILESYSTEM_DISK=s3 আর S3 credentials।

Manager-এ driver('bkash') কল করলে কোন মেথড খুঁজবে?

createBkashDriver() — নামটা StudlyCase করে create…Driver।

Queue-র null আর sync driver-এর পার্থক্য?

sync job সাথে সাথে রিকোয়েস্টের ভেতরেই চালায়; null job নীরবে বাদ দেয়, কখনো চালায় না।