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

معماری سیستم باس

معماری سیستم باس بر اساس اصل Command Query Separation (CQS) طراحی شده و از اصول طراحی SOLID پیروی می‌کند تا قابلیت نگهداری، قابلیت آزمایش و مقیاس‌پذیری را تضمین کند.

اصول معماری

جداسازی Command و Query (CQS)

سیستم باس به طور دقیق اصل جداسازی Command و Query را اجرا می‌کند:

  • Commandها: عملیات را انجام می‌دهند و وضعیت را تغییر می‌دهند، اما هرگز داده‌ای برنمی‌گردانند
  • Queryها: داده برمی‌گردانند، اما هرگز وضعیت را تغییر نمی‌دهند

این جداسازی چندین مزیت ارائه می‌دهد:

  • مرزهای مسئولیت واضح
  • بهبود قابلیت آزمایش
  • فرصت‌های بهتر برای بهینه‌سازی عملکرد
  • استدلال ساده‌تر درباره رفتار کد

اصول SOLID

سیستم باس از تمام اصول SOLID پیروی می‌کند:

  • مسئولیت واحد: هر جزء تنها یک دلیل برای تغییر دارد
  • باز/بسته: سیستم برای توسعه باز است اما برای تغییر بسته است
  • جایگزینی لیسکوف: پیاده‌سازی‌ها می‌توانند جایگزین رابط‌های خود شوند
  • جداسازی رابط: کلاینت‌ها فقط به رابط‌هایی که استفاده می‌کنند وابسته هستند
  • وارونگی وابستگی: ماژول‌های سطح بالا به انتزاع‌ها وابسته هستند

اتصال سست

ماژول‌ها وابستگی مستقیم به یکدیگر ندارند. آن‌ها منحصراً از طریق سیستم باس که به عنوان یک واسطه عمل می‌کند، ارتباط برقرار می‌کنند.

ثبت صریح

کنترل‌کننده‌ها به صورت صریح ثبت می‌شوند بدون کشف خودکار، که دید واضحی از تمام روابط پیام-کنترل‌کننده را تضمین می‌کند.

اجزای سیستم

رابط‌های اصلی

اطلاع

تمام رابط‌ها در فضای نام App\Core\Bus\Contracts تعریف شده‌اند.

BusMessage

رابط پایه برای تمام پیام‌ها (Commandها و Queryها):

interface BusMessage
{
// رابط نشانگر
}

CommandInterface

رابط نشانگر برای Commandها:

interface CommandInterface extends BusMessage
{
// رابط نشانگر
}

QueryInterface

رابط نشانگر برای Queryها:

interface QueryInterface extends BusMessage
{
// رابط نشانگر
}

BusResponseInterface

ساختار پاسخ استاندارد:

interface BusResponseInterface
{
public function isSuccess(): bool;
public function getData(): mixed;
public function getErrorMessage(): ?string;
public function getException(): ?Throwable;
}

CommandBusInterface

قرارداد برای CommandBus:

interface CommandBusInterface
{
public function register(string $commandClass, string $handlerClass): void;
public function dispatch(CommandInterface $command): void;
}

QueryBusInterface

قرارداد برای QueryBus:

interface QueryBusInterface
{
public function register(string $queryClass, string $handlerClass): void;
public function dispatch(QueryInterface $query): BusResponseInterface;
}

پیاده‌سازی‌ها

اطلاع

تمام پیاده‌سازی‌ها در فضای نام‌های App\Core\Bus\Services و App\Core\Bus\Responses تعریف شده‌اند.

InMemoryCommandBus

پیاده‌سازی همزمان CommandBus:

final class InMemoryCommandBus implements CommandBusInterface
{
private array $handlers = [];
private Container $container;

public function register(string $commandClass, string $handlerClass): void
{
$this->handlers[$commandClass] = $handlerClass;
}

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

if (!isset($this->handlers[$commandClass])) {
throw new HandlerNotFoundException($commandClass);
}

$handlerClass = $this->handlers[$commandClass];
$handler = $this->container->make($handlerClass);

$handler->handle($command);
}
}

InMemoryQueryBus

پیاده‌سازی همزمان QueryBus:

final class InMemoryQueryBus implements QueryBusInterface
{
private array $handlers = [];
private Container $container;

public function register(string $queryClass, string $handlerClass): void
{
$this->handlers[$queryClass] = $handlerClass;
}

public function dispatch(QueryInterface $query): BusResponseInterface
{
$queryClass = get_class($query);

if (!isset($this->handlers[$queryClass])) {
return new BusErrorResponse(
new HandlerNotFoundException($queryClass)
);
}

try {
$handlerClass = $this->handlers[$queryClass];
$handler = $this->container->make($handlerClass);

$result = $handler->handle($query);

return new BusSuccessResponse($result);
} catch (Throwable $e) {
return new BusErrorResponse($e);
}
}
}

BusSuccessResponse

پاسخ موفقیت با الگوی شیء مقدار تغییرناپذیر:

final readonly class BusSuccessResponse implements BusResponseInterface
{
public function __construct(
private mixed $data
) {}

public function isSuccess(): bool
{
return true;
}

public function getData(): mixed
{
return $this->data;
}

public function getErrorMessage(): ?string
{
return null;
}

public function getException(): ?Throwable
{
return null;
}
}

BusErrorResponse

پاسخ خطا با اعتبارسنجی:

final readonly class BusErrorResponse implements BusResponseInterface
{
public function __construct(
private Throwable $exception
) {}

public function isSuccess(): bool
{
return false;
}

public function getData(): mixed
{
return null;
}

public function getErrorMessage(): string
{
return $this->exception->getMessage();
}

public function getException(): Throwable
{
return $this->exception;
}
}

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

جریان Command

جریان Query

جریان خطا

الگوهای طراحی استفاده شده

سیستم باس چندین الگوی طراحی را پیاده‌سازی می‌کند:

الگوی Command

الگوی Command برای کپسوله کردن یک درخواست به عنوان یک شیء استفاده می‌شود، که امکان پارامتری کردن کلاینت‌ها با درخواست‌های مختلف، صف‌بندی درخواست‌ها و ثبت عملیات‌ها را فراهم می‌کند.

الگوی Query Object

الگوی Query Object برای کپسوله کردن یک پرس‌وجوی پایگاه داده به عنوان یک شیء استفاده می‌شود، که امکان پارامتری کردن و ترکیب پرس‌وجوها را فراهم می‌کند.

الگوی Result Object

الگوی Result Object برای کپسوله کردن نتیجه یک عملیات استفاده می‌شود، شامل وضعیت موفقیت/شکست و اطلاعات داده یا خطای مرتبط.

تزریق وابستگی

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

الگوی Factory

الگوی Factory برای ایجاد استثناها و سایر اشیاء استفاده می‌شود.

الگوی Value Object

الگوی Value Object برای DTOها (اشیاء انتقال داده) استفاده می‌شود تا تغییرناپذیری و کپسوله‌سازی را تضمین کند.

محدودیت‌های فعلی

پیاده‌سازی فعلی دارای محدودیت‌های زیر است:

  • فقط پردازش همزمان: تمام عملیات‌ها به صورت همزمان اجرا می‌شوند
  • فقط ذخیره‌سازی در حافظه: عدم پایداری پیام‌ها
  • عدم پشتیبانی از Middleware: نمی‌توان پیام‌ها را رهگیری یا اصلاح کرد
  • عدم Event Sourcing: عدم پشتیبانی داخلی از event sourcing
  • عدم تراکنش‌های بین ماژولی: هر کنترل‌کننده تراکنش‌های خود را مدیریت می‌کند

این محدودیت‌ها می‌توانند در نسخه‌های آینده سیستم باس برطرف شوند.