پرش به مطلب اصلی

مثال‌های سیستم باس

این راهنما مثال‌های عملی استفاده از سیستم باس در سناریوهای مختلف را ارائه می‌دهد. این مثال‌ها نحوه پیاده‌سازی الگوهای رایج و حل مشکلات دنیای واقعی با استفاده از معماری باس را نشان می‌دهند.

عملیات CRUD پایه

ایجاد یک منبع

تعریف Command

namespace App\Modules\Users\Commands;

use App\Core\Bus\CommandInterface;

final readonly class CreateUserCommand implements CommandInterface
{
public function __construct(
public string $name,
public string $email,
public string $password
) {
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('آدرس ایمیل نامعتبر است');
}

if (strlen($password) < 8) {
throw new \InvalidArgumentException('رمز عبور باید حداقل 8 کاراکتر باشد');
}
}
}

کنترل‌کننده Command

namespace App\Modules\Users\Handlers;

use App\Core\Bus\CommandHandlerInterface;
use App\Modules\Users\Commands\CreateUserCommand;
use App\Modules\Users\Models\User;
use Illuminate\Support\Facades\Hash;
use Psr\Log\LoggerInterface;

final readonly class CreateUserHandler implements CommandHandlerInterface
{
public function __construct(
private LoggerInterface $logger
) {}

public function handle(CreateUserCommand $command): void
{
$this->logger->info('در حال ایجاد کاربر جدید', ['email' => $command->email]);

$user = new User();
$user->name = $command->name;
$user->email = $command->email;
$user->password = Hash::make($command->password);
$user->save();

$this->logger->info('کاربر با موفقیت ایجاد شد', ['id' => $user->id]);

// ارسال رویداد دامنه
event(new UserCreatedEvent($user->id, $user->email));
}
}

پیاده‌سازی کنترلر

namespace App\Modules\Users\Controllers;

use App\Core\Bus\CommandBusInterface;
use App\Modules\Users\Commands\CreateUserCommand;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
public function __construct(
private CommandBusInterface $commandBus
) {}

public function store(Request $request): JsonResponse
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:8',
]);

try {
$command = new CreateUserCommand(
name: $request->input('name'),
email: $request->input('email'),
password: $request->input('password')
);

$this->commandBus->dispatch($command);

return response()->json(['message' => 'کاربر با موفقیت ایجاد شد'], 201);
} catch (\InvalidArgumentException $e) {
return response()->json(['error' => $e->getMessage()], 422);
} catch (\Exception $e) {
return response()->json(['error' => 'ایجاد کاربر با شکست مواجه شد'], 500);
}
}
}

بازیابی یک منبع

تعریف Query

namespace App\Modules\Users\Queries;

use App\Core\Bus\QueryInterface;

final readonly class GetUserByIdQuery implements QueryInterface
{
public function __construct(
public int $userId
) {
if ($userId <= 0) {
throw new \InvalidArgumentException('شناسه کاربر باید یک عدد صحیح مثبت باشد');
}
}
}

کنترل‌کننده Query

namespace App\Modules\Users\Handlers;

use App\Core\Bus\QueryHandlerInterface;
use App\Modules\Users\Exceptions\UserNotFoundException;
use App\Modules\Users\Models\User;
use App\Modules\Users\Queries\GetUserByIdQuery;
use Psr\Log\LoggerInterface;

final readonly class GetUserByIdHandler implements QueryHandlerInterface
{
public function __construct(
private LoggerInterface $logger
) {}

public function handle(GetUserByIdQuery $query): array
{
$this->logger->info('در حال دریافت کاربر با شناسه', ['userId' => $query->userId]);

$user = User::find($query->userId);

if (!$user) {
$this->logger->warning('کاربر یافت نشد', ['userId' => $query->userId]);
throw new UserNotFoundException("کاربر با شناسه {$query->userId} یافت نشد");
}

// حذف داده‌های حساس
$userData = $user->toArray();
unset($userData['password']);

return $userData;
}
}

پیاده‌سازی کنترلر

namespace App\Modules\Users\Controllers;

use App\Core\Bus\QueryBusInterface;
use App\Modules\Users\Exceptions\UserNotFoundException;
use App\Modules\Users\Queries\GetUserByIdQuery;
use Illuminate\Http\JsonResponse;

class UserController extends Controller
{
public function __construct(
private QueryBusInterface $queryBus
) {}

public function show(int $id): JsonResponse
{
try {
$query = new GetUserByIdQuery($id);
$user = $this->queryBus->dispatch($query);

return response()->json(['data' => $user]);
} catch (UserNotFoundException $e) {
return response()->json(['error' => $e->getMessage()], 404);
} catch (\Exception $e) {
return response()->json(['error' => 'بازیابی کاربر با شکست مواجه شد'], 500);
}
}
}

منطق کسب‌وکار پیچیده

سیستم پردازش سفارش

تعریف Command

namespace App\Modules\Orders\Commands;

use App\Core\Bus\CommandInterface;

final readonly class CreateOrderCommand implements CommandInterface
{
/**
* @param int $userId
* @param array $items آرایه‌ای از آیتم‌ها با فرمت [['product_id' => 1, 'quantity' => 2], ...]
* @param string $shippingAddress
*/
public function __construct(
public int $userId,
public array $items,
public string $shippingAddress
) {
if (empty($items)) {
throw new \InvalidArgumentException('سفارش باید حداقل شامل یک آیتم باشد');
}

foreach ($items as $item) {
if (!isset($item['product_id']) || !isset($item['quantity'])) {
throw new \InvalidArgumentException('هر آیتم باید دارای product_id و quantity باشد');
}

if ($item['quantity'] <= 0) {
throw new \InvalidArgumentException('تعداد آیتم باید مثبت باشد');
}
}

if (empty($shippingAddress)) {
throw new \InvalidArgumentException('آدرس ارسال الزامی است');
}
}
}

کنترل‌کننده Command

namespace App\Modules\Orders\Handlers;

use App\Core\Bus\CommandBusInterface;
use App\Core\Bus\CommandHandlerInterface;
use App\Core\Bus\QueryBusInterface;
use App\Modules\Orders\Commands\CreateOrderCommand;
use App\Modules\Orders\Exceptions\InsufficientInventoryException;
use App\Modules\Orders\Models\Order;
use App\Modules\Orders\Models\OrderItem;
use App\Modules\Products\Queries\GetProductByIdQuery;
use App\Modules\Users\Queries\GetUserByIdQuery;
use Illuminate\Support\Facades\DB;
use Psr\Log\LoggerInterface;

final readonly class CreateOrderHandler implements CommandHandlerInterface
{
public function __construct(
private QueryBusInterface $queryBus,
private CommandBusInterface $commandBus,
private LoggerInterface $logger
) {}

public function handle(CreateOrderCommand $command): void
{
$this->logger->info('در حال ایجاد سفارش جدید', [
'userId' => $command->userId,
'itemCount' => count($command->items)
]);

// بررسی وجود کاربر
$user = $this->queryBus->dispatch(new GetUserByIdQuery($command->userId));

// محاسبه مجموع و بررسی موجودی
$orderTotal = 0;
$orderItems = [];

foreach ($command->items as $item) {
$product = $this->queryBus->dispatch(
new GetProductByIdQuery($item['product_id'])
);

// بررسی موجودی
if ($product['stock'] < $item['quantity']) {
throw new InsufficientInventoryException(
"موجودی ناکافی برای محصول {$product['name']}"
);
}

$itemTotal = $product['price'] * $item['quantity'];
$orderTotal += $itemTotal;

$orderItems[] = [
'product_id' => $item['product_id'],
'quantity' => $item['quantity'],
'unit_price' => $product['price'],
'total' => $itemTotal
];
}

// ایجاد سفارش در تراکنش
DB::transaction(function () use ($command, $orderTotal, $orderItems) {
// ایجاد سفارش
$order = new Order();
$order->user_id = $command->userId;
$order->total = $orderTotal;
$order->shipping_address = $command->shippingAddress;
$order->status = 'pending';
$order->save();

// ایجاد آیتم‌های سفارش
foreach ($orderItems as $item) {
$orderItem = new OrderItem();
$orderItem->order_id = $order->id;
$orderItem->product_id = $item['product_id'];
$orderItem->quantity = $item['quantity'];
$orderItem->unit_price = $item['unit_price'];
$orderItem->total = $item['total'];
$orderItem->save();

// به‌روزرسانی موجودی
$this->commandBus->dispatch(
new DecrementProductStockCommand(
$item['product_id'],
$item['quantity']
)
);
}

// ایجاد رکورد پرداخت
$this->commandBus->dispatch(
new CreatePaymentCommand(
$order->id,
$orderTotal
)
);

$this->logger->info('سفارش با موفقیت ایجاد شد', ['orderId' => $order->id]);

// ارسال رویداد دامنه
event(new OrderCreatedEvent($order->id, $command->userId));
});
}
}

پیاده‌سازی کنترلر

namespace App\Modules\Orders\Controllers;

use App\Core\Bus\CommandBusInterface;
use App\Modules\Orders\Commands\CreateOrderCommand;
use App\Modules\Orders\Exceptions\InsufficientInventoryException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class OrderController extends Controller
{
public function __construct(
private CommandBusInterface $commandBus
) {}

public function store(Request $request): JsonResponse
{
$request->validate([
'items' => 'required|array|min:1',
'items.*.product_id' => 'required|integer|exists:products,id',
'items.*.quantity' => 'required|integer|min:1',
'shipping_address' => 'required|string'
]);

try {
$command = new CreateOrderCommand(
userId: auth()->id(),
items: $request->input('items'),
shippingAddress: $request->input('shipping_address')
);

$this->commandBus->dispatch($command);

return response()->json(['message' => 'سفارش با موفقیت ایجاد شد'], 201);
} catch (InsufficientInventoryException $e) {
return response()->json(['error' => $e->getMessage()], 422);
} catch (\InvalidArgumentException $e) {
return response()->json(['error' => $e->getMessage()], 422);
} catch (\Exception $e) {
return response()->json(['error' => 'ایجاد سفارش با شکست مواجه شد'], 500);
}
}
}

ارتباط بین ماژولی

ثبت‌نام کاربر با اعلان ایمیل

تعریف Command (ماژول Users)

namespace App\Modules\Users\Commands;

use App\Core\Bus\CommandInterface;

final readonly class RegisterUserCommand implements CommandInterface
{
public function __construct(
public string $name,
public string $email,
public string $password
) {
// اعتبارسنجی برای اختصار حذف شده است
}
}

کنترل‌کننده Command (ماژول Users)

namespace App\Modules\Users\Handlers;

use App\Core\Bus\CommandBusInterface;
use App\Core\Bus\CommandHandlerInterface;
use App\Modules\Users\Commands\RegisterUserCommand;
use App\Modules\Users\Models\User;
use Illuminate\Support\Facades\Hash;
use Psr\Log\LoggerInterface;

final readonly class RegisterUserHandler implements CommandHandlerInterface
{
public function __construct(
private CommandBusInterface $commandBus,
private LoggerInterface $logger
) {}

public function handle(RegisterUserCommand $command): void
{
$this->logger->info('در حال ثبت‌نام کاربر جدید', ['email' => $command->email]);

// ایجاد کاربر
$user = new User();
$user->name = $command->name;
$user->email = $command->email;
$user->password = Hash::make($command->password);
$user->save();

$this->logger->info('کاربر با موفقیت ثبت‌نام شد', ['id' => $user->id]);

// ارتباط بین ماژولی از طریق command bus
$this->commandBus->dispatch(
new \App\Modules\Notifications\Commands\SendWelcomeEmailCommand(
$user->id,
$user->email,
$user->name
)
);
}
}

تعریف Command (ماژول Notifications)

namespace App\Modules\Notifications\Commands;

use App\Core\Bus\CommandInterface;

final readonly class SendWelcomeEmailCommand implements CommandInterface
{
public function __construct(
public int $userId,
public string $email,
public string $name
) {}
}

کنترل‌کننده Command (ماژول Notifications)

namespace App\Modules\Notifications\Handlers;

use App\Core\Bus\CommandHandlerInterface;
use App\Modules\Notifications\Commands\SendWelcomeEmailCommand;
use App\Modules\Notifications\Services\EmailService;
use Psr\Log\LoggerInterface;

final readonly class SendWelcomeEmailHandler implements CommandHandlerInterface
{
public function __construct(
private EmailService $emailService,
private LoggerInterface $logger
) {}

public function handle(SendWelcomeEmailCommand $command): void
{
$this->logger->info('در حال ارسال ایمیل خوش‌آمدگویی', [
'userId' => $command->userId,
'email' => $command->email
]);

$this->emailService->sendWelcomeEmail(
$command->email,
$command->name
);

$this->logger->info('ایمیل خوش‌آمدگویی با موفقیت ارسال شد');
}
}

معماری رویداد-محور

رویداد دامنه و شنونده

رویداد دامنه

namespace App\Modules\Orders\Events;

use Illuminate\Foundation\Events\Dispatchable;

class OrderShippedEvent
{
use Dispatchable;

public function __construct(
public int $orderId,
public int $userId,
public string $trackingNumber
) {}
}

شنونده رویداد

namespace App\Modules\Notifications\Listeners;

use App\Core\Bus\CommandBusInterface;
use App\Modules\Notifications\Commands\SendOrderShippedNotificationCommand;
use App\Modules\Orders\Events\OrderShippedEvent;

class OrderShippedListener
{
public function __construct(
private CommandBusInterface $commandBus
) {}

public function handle(OrderShippedEvent $event): void
{
$this->commandBus->dispatch(
new SendOrderShippedNotificationCommand(
$event->orderId,
$event->userId,
$event->trackingNumber
)
);
}
}

ثبت رویداد

// در EventServiceProvider.php
protected $listen = [
\App\Modules\Orders\Events\OrderShippedEvent::class => [
\App\Modules\Notifications\Listeners\OrderShippedListener::class,
\App\Modules\Analytics\Listeners\TrackOrderShippedListener::class,
],
];

الگوهای پیشرفته

الگوی Decorator

Decorator لاگ‌گیری برای Command Bus

namespace App\Core\Bus\Decorators;

use App\Core\Bus\CommandBusInterface;
use App\Core\Bus\CommandInterface;
use Psr\Log\LoggerInterface;

class LoggingCommandBusDecorator implements CommandBusInterface
{
public function __construct(
private CommandBusInterface $commandBus,
private LoggerInterface $logger
) {}

public function dispatch(CommandInterface $command): void
{
$commandClass = get_class($command);

$this->logger->info("در حال ارسال command", [
'command' => $commandClass,
'data' => $this->sanitizeCommandData($command)
]);

$startTime = microtime(true);

try {
$this->commandBus->dispatch($command);

$executionTime = microtime(true) - $startTime;

$this->logger->info("Command با موفقیت اجرا شد", [
'command' => $commandClass,
'execution_time' => $executionTime
]);
} catch (\Throwable $e) {
$executionTime = microtime(true) - $startTime;

$this->logger->error("اجرای command با شکست مواجه شد", [
'command' => $commandClass,
'execution_time' => $executionTime,
'exception' => get_class($e),
'message' => $e->getMessage()
]);

throw $e;
}
}

public function register(string $commandClass, string $handlerClass): void
{
$this->commandBus->register($commandClass, $handlerClass);
}

private function sanitizeCommandData(CommandInterface $command): array
{
$data = [];
$reflection = new \ReflectionClass($command);

foreach ($reflection->getProperties() as $property) {
$property->setAccessible(true);
$propertyName = $property->getName();
$propertyValue = $property->getValue($command);

// پاک‌سازی داده‌های حساس
if (in_array($propertyName, ['password', 'token', 'secret'])) {
$data[$propertyName] = '***REDACTED***';
} else {
$data[$propertyName] = $propertyValue;
}
}

return $data;
}
}

ثبت در ارائه‌دهنده سرویس

namespace App\Providers;

use App\Core\Bus\CommandBusInterface;
use App\Core\Bus\Decorators\LoggingCommandBusDecorator;
use App\Core\Bus\Implementations\SimpleCommandBus;
use Illuminate\Support\ServiceProvider;
use Psr\Log\LoggerInterface;

class BusServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(CommandBusInterface::class, function ($app) {
$commandBus = new SimpleCommandBus($app);

// اعمال decorator
return new LoggingCommandBusDecorator(
$commandBus,
$app->make(LoggerInterface::class)
);
});
}
}

الگوی زنجیره مسئولیت

میان‌افزار اعتبارسنجی

namespace App\Core\Bus\Middleware;

use App\Core\Bus\CommandInterface;
use Closure;

class ValidationMiddleware
{
public function handle(CommandInterface $command, Closure $next)
{
// اعتبارسنجی command اگر ValidatableInterface را پیاده‌سازی کرده باشد
if ($command instanceof ValidatableInterface) {
$command->validate();
}

return $next($command);
}
}

میان‌افزار تراکنش

namespace App\Core\Bus\Middleware;

use App\Core\Bus\CommandInterface;
use Closure;
use Illuminate\Support\Facades\DB;

class TransactionMiddleware
{
public function handle(CommandInterface $command, Closure $next)
{
// فقط برای commandهایی که به آن نیاز دارند تراکنش اعمال کنید
if ($command instanceof TransactionalInterface) {
return DB::transaction(function () use ($command, $next) {
return $next($command);
});
}

return $next($command);
}
}

ثبت میان‌افزار

namespace App\Providers;

use App\Core\Bus\CommandBusInterface;
use App\Core\Bus\Implementations\MiddlewareCommandBus;
use App\Core\Bus\Middleware\ValidationMiddleware;
use App\Core\Bus\Middleware\TransactionMiddleware;
use App\Core\Bus\Middleware\LoggingMiddleware;
use Illuminate\Support\ServiceProvider;

class BusServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(CommandBusInterface::class, function ($app) {
$commandBus = new MiddlewareCommandBus($app);

// ثبت میان‌افزارها به ترتیب اجرا
$commandBus->addMiddleware(new LoggingMiddleware());
$commandBus->addMiddleware(new ValidationMiddleware());
$commandBus->addMiddleware(new TransactionMiddleware());

return $commandBus;
});
}
}