معماری سیستم باس
معماری سیستم باس بر اساس اصل 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
- عدم تراکنشهای بین ماژولی: هر کنترلکننده تراکنشهای خود را مدیریت میکند
این محدودیتها میتوانند در نسخههای آینده سیستم باس برطرف شوند.