অধ্যায় ৮
Driver Pattern
"One stable API, interchangeable backends."
মূল কোড না বদলে ব্যাকএন্ড বদলানো — Cache, Queue, Filesystem, Mail, Session, Notification, Logging সবখানে।
ইন্টারফেস এক, ব্যাকএন্ড অনেক
প্রোডাকশনে ক্যাশ Redis-এ। টেস্টে array driver। স্টেজিং-এ হয়তো database। কিন্তু আপনার কোড কোথাও বদলায় না:
Cache::put('key', 'value', 3600);
লারাভেল কী করতে চান আর কীভাবে হবে আলাদা করে। আপনি একটি স্থির API-এর বিপরীতে কোড লেখেন; ব্যাকএন্ড শুধু একটি কনফিগারেশন ডিটেইল।
.envCACHE_STORE=redis # Laravel 11+ (আগে ছিল CACHE_DRIVER)
এই বদলযোগ্য ব্যাকএন্ডগুলোকে লারাভেল বলে driver (সাবসিস্টেম ভেদে store, disk, connection বা channel)। এ কারণেই লারাভেল অ্যাপে ইনফ্রাস্ট্রাকচার বদলানো সাধারণত রিরাইট নয়, শুধু কনফিগ পরিবর্তন।
চার অংশের কাঠামো
লারাভেলের বেশিরভাগ driver-ভিত্তিক সিস্টেম একই চার অংশে গড়া:
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 প্যাটার্নের লাভ সবচেয়ে স্পষ্ট।
- 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
- 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, Sessions | Store | config/cache.php |
| Filesystem | Disk | config/filesystems.php |
| Queue, Database | Connection | config/queue.php, config/database.php |
| Notifications, Logging | Channel | config/logging.php |
| Transport / Mailer | config/mail.php |
লারাভেলের কনফিগ ফাইলে এই শব্দগুলোর যেকোনোটা দেখলে বুঝবেন — আপনি driver প্যাটার্ন দেখছেন।
লারাভেলে Custom Driver যোগ করা
extend মেথড দিয়ে নিজের driver ফ্রেমওয়ার্কে প্লাগ-ইন করা যায়। ধরুন MongoDB-তে ক্যাশ রাখতে চান:
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 সম্ভব হতো না।
সারসংক্ষেপ ও যাচাই
- চার অংশ: 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 নীরবে বাদ দেয়, কখনো চালায় না।