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

زیرساخت پرس‌وجو: راهنمای پیاده‌سازی

نمای کلی

زیرساخت پرس‌وجو یک روش استاندارد برای ماژول‌ها فراهم می‌کند تا داده را بخوانند از ماژول‌های دیگر بدون ایجاد وابستگی شدید. این راهنما فرآیند کامل پیاده‌سازی از ایجاد اولین رابط پرس‌وجو تا الگوهای استفاده پیشرفته را پوشش می‌دهد.

اصل اصلی

وارونگی وابستگی: ماژول‌های مصرف‌کننده به انتزاع‌ها (رابط‌ها) تعریف‌شده در یک هسته مشترک وابسته‌اند، در حالی که ماژول‌های ارائه‌دهنده این رابط‌ها را در دامنه خود پیاده‌سازی می‌کنند.


اجزای معماری

1. قراردادهای پایه (پایه مشترک)

واقع در app/Contracts/:

app/Contracts/QueryInterface.php
<?php

declare(strict_types=1);

namespace App\Contracts;

interface QueryInterface
{
// رابط نشانگر - هیچ متدی مورد نیاز نیست
// همه رابط‌های پرس‌وجو باید این را extend کنند
}

2. لایه دامنه (قراردادها)

واقع در app/Domains/{Context}/:

این لایه شامل:

  • رابط‌های پرس‌وجو
  • DTOها (اشیاء انتقال داده)
  • IDهای دامنه (Value Objects)
  • مجموعه‌های DTO
ساختار نمونه
app/Domains/User/
├── UserQuery.php # رابط
├── UserDTO.php # شیء انتقال داده
├── UserDTOCollection.php # مجموعه DTO
├── UserId.php # شناسه دامنه (Value Object)
└── UserIdCollection.php # مجموعه شناسه

3. لایه پیاده‌سازی

واقع در app/Core/{Module}/Services/Query/ یا app/Modules/{Module}/Services/Query/:

این لایه شامل:

  • پیاده‌سازی‌های مشخص رابط‌های پرس‌وجو
  • استفاده از ریپوزیتوری
  • منطق نگاشت Entity به DTO

4. Service Locator

واقع در app/Services/QueryServiceLocator.php:

مدیریت ثبت و حل سرویس‌های پرس‌وجو.

5. Service Provider

واقع در app/Providers/QueryServiceProvider.php:

ثبت همه رابط‌های پرس‌وجو و پیاده‌سازی‌های آن‌ها با کانتینر سرویس Laravel.


پیاده‌سازی گام به گام

گام 1: ایجاد شناسه دامنه (Value Object)

app/Domains/Order/OrderId.php
<?php

declare(strict_types=1);

namespace App\Domains\Order;

use App\Contracts\DomainId;

final class OrderId extends DomainId
{
// همه قابلیت‌ها را از DomainId به ارث می‌برد
// در صورت نیاز می‌توان متدهای خاص دامنه اضافه کرد
}
نکات کلیدی
  • کلاس پایه DomainId را extend می‌کند
  • از کلیدواژه final برای جلوگیری از وراثت استفاده کنید
  • ایمنی نوع برای شناسه‌های سفارش فراهم می‌کند
  • اعتبارسنجی می‌کند که شناسه عدد صحیح مثبت باشد

گام 2: ایجاد مجموعه شناسه (اختیاری اما توصیه می‌شود)

app/Domains/Order/OrderIdCollection.php
<?php

declare(strict_types=1);

namespace App\Domains\Order;

use App\Contracts\BaseCustomCollection;

final class OrderIdCollection extends BaseCustomCollection
{
public function __construct(array $items = [])
{
// اعتبارسنجی همه آیتم‌ها به عنوان نمونه OrderId
foreach ($items as $item) {
if (!$item instanceof OrderId) {
throw new \InvalidArgumentException(
'All items must be instances of OrderId'
);
}
}

parent::__construct($items);
}

/**
* تبدیل مجموعه به آرایه مقادیر شناسه
*/
public function toValues(): array
{
return array_map(
fn(OrderId $id) => $id->value,
$this->items
);
}
}

گام 3: ایجاد شیء انتقال داده (DTO)

app/Domains/Order/OrderDTO.php
<?php

declare(strict_types=1);

namespace App\Domains\Order;

use App\Contracts\DataTransferObject;
use App\Domains\User\UserId;
use DateTimeInterface;

final class OrderDTO implements DataTransferObject
{
public function __construct(
public readonly OrderId $id,
public readonly string $orderNumber,
public readonly UserId $userId,
public readonly int $totalAmount,
public readonly string $status,
public readonly DateTimeInterface $createdAt,
public readonly ?DateTimeInterface $completedAt = null,
) {
}

public function toArray(): array
{
return [
'id' => $this->id->value,
'order_number' => $this->orderNumber,
'user_id' => $this->userId->value,
'total_amount' => $this->totalAmount,
'status' => $this->status,
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
'completed_at' => $this->completedAt?->format('Y-m-d H:i:s'),
];
}

/**
* بررسی اینکه آیا سفارش تکمیل شده است
*/
public function isCompleted(): bool
{
return $this->status === 'completed';
}

/**
* بررسی اینکه آیا سفارش در انتظار است
*/
public function isPending(): bool
{
return $this->status === 'pending';
}
}
نکات کلیدی
  • همه ویژگی‌ها public readonly (تغییرناپذیر)
  • استفاده از type hint برای همه ویژگی‌ها
  • پارامترهای الزامی اول، پارامترهای اختیاری آخر
  • می‌تواند شامل متدهای کمکی خاص دامنه باشد
  • پیاده‌سازی toArray() برای سریال‌سازی

گام 4: ایجاد مجموعه DTO

app/Domains/Order/OrderDTOCollection.php
<?php

declare(strict_types=1);

namespace App\Domains\Order;

use App\Contracts\BaseCustomCollection;

final class OrderDTOCollection extends BaseCustomCollection
{
public function __construct(array $items = [])
{
// اعتبارسنجی همه آیتم‌ها به عنوان نمونه OrderDTO
foreach ($items as $item) {
if (!$item instanceof OrderDTO) {
throw new \InvalidArgumentException(
'All items must be instances of OrderDTO'
);
}
}

parent::__construct($items);
}

/**
* فیلتر سفارشات تکمیل‌شده
*/
public function completed(): self
{
$completed = array_filter(
$this->items,
fn(OrderDTO $order) => $order->isCompleted()
);

return new self(array_values($completed));
}

/**
* فیلتر سفارشات در انتظار
*/
public function pending(): self
{
$pending = array_filter(
$this->items,
fn(OrderDTO $order) => $order->isPending()
);

return new self(array_values($pending));
}

/**
* دریافت مبلغ کل همه سفارشات
*/
public function totalAmount(): int
{
return array_reduce(
$this->items,
fn(int $carry, OrderDTO $order) => $carry + $order->totalAmount,
0
);
}
}
نکات کلیدی
  • مجموعه type-safe برای OrderDTO
  • می‌تواند شامل متدهای فیلترینگ خاص دامنه باشد
  • عملیات تغییرناپذیر (برگرداندن نمونه‌های جدید)
  • عملیات کسب‌وکار معنادار فراهم می‌کند

گام 5: ایجاد رابط پرس‌وجو

app/Domains/Order/OrderQuery.php
<?php

declare(strict_types=1);

namespace App\Domains\Order;

use App\Contracts\QueryInterface;
use App\Domains\User\UserId;

interface OrderQuery extends QueryInterface
{
/**
* دریافت سفارش با شناسه (در صورت عدم یافتن استثنا پرتاب می‌کند)
*/
public function getById(OrderId $id): OrderDTO;

/**
* یافتن سفارش با شناسه (در صورت عدم یافتن null برمی‌گرداند)
*/
public function findById(OrderId $id): ?OrderDTO;

/**
* یافتن چندین سفارش با شناسه‌ها
*/
public function findByIds(OrderIdCollection $ids): OrderDTOCollection;

/**
* بررسی وجود سفارش
*/
public function exists(OrderId $id): bool;

/**
* یافتن سفارش با شماره سفارش
*/
public function findByOrderNumber(string $orderNumber): ?OrderDTO;

/**
* دریافت همه سفارشات یک کاربر
*/
public function getOrdersByUser(UserId $userId): OrderDTOCollection;

/**
* دریافت سفارشات تکمیل‌شده یک کاربر
*/
public function getCompletedOrdersByUser(UserId $userId): OrderDTOCollection;

/**
* دریافت سفارشات در انتظار یک کاربر
*/
public function getPendingOrdersByUser(UserId $userId): OrderDTOCollection;
}
نکات کلیدی
  • QueryInterface را extend می‌کند
  • فقط متدهای فقط-خواندنی (بدون create/update/delete)
  • استفاده از Value Objects برای پارامترها (OrderId، UserId)
  • برگرداندن DTOها یا مجموعه‌های DTO
  • استفاده از get* برای متدهایی که استثنا پرتاب می‌کنند
  • استفاده از find* برای متدهایی که null برمی‌گردانند
  • مستندسازی هر متد با PHPDoc

گام 6: ایجاد پیاده‌سازی پرس‌وجو

app/Modules/Orders/Services/Query/OrderQueryImplementation.php
<?php

declare(strict_types=1);

namespace App\Modules\Orders\Services\Query;

use App\Domains\Order\OrderDTO;
use App\Domains\Order\OrderDTOCollection;
use App\Domains\Order\OrderId;
use App\Domains\Order\OrderIdCollection;
use App\Domains\Order\OrderQuery;
use App\Domains\User\UserId;
use App\Modules\Orders\Entities\Order;
use App\Modules\Orders\Repositories\OrderRepository;

final class OrderQueryImplementation implements OrderQuery
{
public function __construct(
private readonly OrderRepository $repository
) {
}

public function getById(OrderId $id): OrderDTO
{
$order = $this->repository->findOrFail($id->value);

return $this->toDTO($order);
}

public function findById(OrderId $id): ?OrderDTO
{
$order = $this->repository->find($id->value);

return $order ? $this->toDTO($order) : null;
}

public function findByIds(OrderIdCollection $ids): OrderDTOCollection
{
if ($ids->isEmpty()) {
return OrderDTOCollection::empty();
}

$orders = $this->repository
->newQuery()
->whereIn('id', $ids->toValues())
->get();

$dtos = array_map(
fn(Order $order) => $this->toDTO($order),
$orders->all()
);

return new OrderDTOCollection($dtos);
}

public function exists(OrderId $id): bool
{
return $this->repository
->newQuery()
->where('id', $id->value)
->exists();
}

public function findByOrderNumber(string $orderNumber): ?OrderDTO
{
$order = $this->repository
->newQuery()
->where('order_number', $orderNumber)
->first();

return $order ? $this->toDTO($order) : null;
}

public function getOrdersByUser(UserId $userId): OrderDTOCollection
{
$orders = $this->repository
->newQuery()
->where('user_id', $userId->value)
->orderBy('created_at', 'desc')
->get();

$dtos = array_map(
fn(Order $order) => $this->toDTO($order),
$orders->all()
);

return new OrderDTOCollection($dtos);
}

public function getCompletedOrdersByUser(UserId $userId): OrderDTOCollection
{
$orders = $this->repository
->newQuery()
->where('user_id', $userId->value)
->where('status', 'completed')
->orderBy('completed_at', 'desc')
->get();

$dtos = array_map(
fn(Order $order) => $this->toDTO($order),
$orders->all()
);

return new OrderDTOCollection($dtos);
}

public function getPendingOrdersByUser(UserId $userId): OrderDTOCollection
{
$orders = $this->repository
->newQuery()
->where('user_id', $userId->value)
->where('status', 'pending')
->orderBy('created_at', 'desc')
->get();

$dtos = array_map(
fn(Order $order) => $this->toDTO($order),
$orders->all()
);

return new OrderDTOCollection($dtos);
}

/**
* تبدیل موجودیت Order به OrderDTO
*/
private function toDTO(Order $order): OrderDTO
{
return new OrderDTO(
id: new OrderId($order->id),
orderNumber: $order->order_number,
userId: new UserId($order->user_id),
totalAmount: $order->total_amount,
status: $order->status,
createdAt: $order->created_at,
completedAt: $order->completed_at,
);
}
}
نکات کلیدی
  • رابط پرس‌وجو را پیاده‌سازی می‌کند
  • از ریپوزیتوری برای دسترسی به داده استفاده می‌کند
  • متد خصوصی toDTO() برای نگاشت موجودیت
  • DTOها را برمی‌گرداند، هرگز موجودیت‌ها را نه
  • از Value Objects برای شناسه‌ها استفاده می‌کند
  • مجموعه‌های خالی را به خوبی مدیریت می‌کند

گام 7: ثبت در QueryServiceProvider

app/Providers/QueryServiceProvider.php
<?php

declare(strict_types=1);

namespace App\Providers;

use App\Domains\Order\OrderQuery;
use App\Modules\Orders\Services\Query\OrderQueryImplementation;
use App\Services\QueryServiceLocator;
use Illuminate\Support\ServiceProvider;

class QueryServiceProvider extends ServiceProvider
{
private array $queryServices = [
'users' => UserQuery::class,
'carts' => CartQuery::class,
'orders' => OrderQuery::class, // ← این را اضافه کنید
// ... سایر پرس‌وجوها
];

private array $implementations = [
UserQuery::class => UserQueryImplementation::class,
CartQuery::class => CartQueryImplementation::class,
OrderQuery::class => OrderQueryImplementation::class, // ← این را اضافه کنید
// ... سایر پیاده‌سازی‌ها
];

public function register(): void
{
// ثبت QueryServiceLocator به عنوان singleton
$this->app->singleton(QueryServiceLocator::class, function () {
$locator = new QueryServiceLocator();

foreach ($this->queryServices as $context => $interface) {
$locator->register($context, $interface);
}

return $locator;
});

// اتصال رابط‌ها به پیاده‌سازی‌ها
foreach ($this->implementations as $interface => $implementation) {
$this->app->bind($interface, $implementation);
}
}

public function boot(): void
{
// منطق boot در صورت نیاز
}
}
نکات ثبت
  • نام زمینه را به آرایه $queryServices اضافه کنید
  • نگاشت رابط به پیاده‌سازی را اضافه کنید
  • نام زمینه باید توصیفی باشد (مثلاً 'orders'، 'users')
  • Laravel به صورت خودکار وابستگی‌ها را حل می‌کند

الگوهای استفاده

بهترین برای: زمانی که می‌دانید در زمان کامپایل به کدام رابط پرس‌وجو نیاز دارید.

<?php

namespace App\Modules\Accounting\Services;

use App\Domains\Order\OrderQuery;
use App\Domains\Order\OrderId;
use App\Domains\User\UserQuery;
use App\Domains\User\UserId;

final class InvoiceService
{
public function __construct(
private readonly OrderQuery $orderQuery,
private readonly UserQuery $userQuery,
) {
}

public function generateInvoice(int $orderId): InvoiceDTO
{
// دریافت داده سفارش
$order = $this->orderQuery->getById(new OrderId($orderId));

// دریافت داده کاربر
$user = $this->userQuery->getById($order->userId);

// تولید فاکتور
return new InvoiceDTO(
orderNumber: $order->orderNumber,
customerName: $user->fullName,
totalAmount: $order->totalAmount,
// ...
);
}
}
مزایا
  • ✅ Type-safe (تکمیل خودکار IDE کار می‌کند)
  • ✅ آسان برای تست (می‌توان رابط‌ها را Mock کرد)
  • ✅ وابستگی‌های واضح
  • ✅ اعتبارسنجی زمان کامپایل

بهترین شیوه‌ها

1. طراحی رابط

interface OrderQuery extends QueryInterface
{
// نام‌های متد واضح و توصیفی
public function getById(OrderId $id): OrderDTO;
public function findByOrderNumber(string $orderNumber): ?OrderDTO;
public function getCompletedOrdersByUser(UserId $userId): OrderDTOCollection;
}

2. طراحی DTO

final class OrderDTO implements DataTransferObject
{
public function __construct(
public readonly OrderId $id, // Value Objects
public readonly string $orderNumber, // انواع ابتدایی
public readonly UserId $userId, // VOهای مرتبط
public readonly DateTimeInterface $createdAt, // رابط‌ها
) {
}

// متدهای کمکی مشکلی ندارند
public function isCompleted(): bool
{
return $this->status === 'completed';
}
}

3. الگوهای پیاده‌سازی

final class OrderQueryImplementation implements OrderQuery
{
public function __construct(
private readonly OrderRepository $repository
) {
}

public function getById(OrderId $id): OrderDTO
{
$order = $this->repository->findOrFail($id->value);
return $this->toDTO($order);
}

private function toDTO(Order $order): OrderDTO
{
return new OrderDTO(
id: new OrderId($order->id),
orderNumber: $order->order_number,
// ...
);
}
}

4. قراردادهای نام‌گذاری

پیشوند متدرفتارنوع برگشتی
get*در صورت عدم یافتن استثنا پرتاب می‌کندDTO یا Collection
find*در صورت عدم یافتن null برمی‌گرداند?DTO یا Collection
exists*وجود را بررسی می‌کندbool
count*رکوردها را می‌شماردint
مثال‌ها
public function getById(OrderId $id): OrderDTO;              // در صورت عدم یافتن پرتاب می‌کند
public function findById(OrderId $id): ?OrderDTO; // در صورت عدم یافتن null برمی‌گرداند
public function exists(OrderId $id): bool; // true/false
public function countByUser(UserId $userId): int; // شمارش

5. مدیریت خطا

public function getById(OrderId $id): OrderDTO
{
// اجازه دهید ریپوزیتوری ModelNotFoundException پرتاب کند
$order = $this->repository->findOrFail($id->value);
return $this->toDTO($order);
}

public function findById(OrderId $id): ?OrderDTO
{
// برای عدم یافتن null برگردانید
$order = $this->repository->find($id->value);
return $order ? $this->toDTO($order) : null;
}

استراتژی‌های تست

تست واحد با Mock

tests/Unit/Modules/Accounting/Services/InvoiceServiceTest.php
<?php

namespace Tests\Unit\Modules\Accounting\Services;

use App\Domains\Order\OrderDTO;
use App\Domains\Order\OrderId;
use App\Domains\Order\OrderQuery;
use App\Domains\User\UserDTO;
use App\Domains\User\UserId;
use App\Domains\User\UserQuery;
use App\Modules\Accounting\Services\InvoiceService;
use Mockery;
use Tests\TestCase;

final class InvoiceServiceTest extends TestCase
{
public function test_generates_invoice_successfully(): void
{
// آماده‌سازی
$orderId = new OrderId(1);
$userId = new UserId(10);

$orderQuery = Mockery::mock(OrderQuery::class);
$orderQuery->shouldReceive('getById')
->once()
->with(Mockery::on(fn($id) => $id->equals($orderId)))
->andReturn(new OrderDTO(
id: $orderId,
orderNumber: 'ORD-001',
userId: $userId,
totalAmount: 10000,
status: 'completed',
createdAt: now(),
));

$userQuery = Mockery::mock(UserQuery::class);
$userQuery->shouldReceive('getById')
->once()
->with(Mockery::on(fn($id) => $id->equals($userId)))
->andReturn(new UserDTO(
id: $userId,
fullName: 'جان دو',
morphClass: 'App\Models\User',
registeredAt: now(),
mobile: null,
));

$service = new InvoiceService($orderQuery, $userQuery);

// اجرا
$invoice = $service->generateInvoice(1);

// بررسی
$this->assertEquals('ORD-001', $invoice->orderNumber);
$this->assertEquals('جان دو', $invoice->customerName);
$this->assertEquals(10000, $invoice->totalAmount);
}
}

تست یکپارچگی

tests/Integration/Services/Query/OrderQueryImplementationTest.php
<?php

namespace Tests\Integration\Services\Query;

use App\Domains\Order\OrderId;
use App\Domains\Order\OrderQuery;
use App\Domains\User\UserId;
use App\Modules\Orders\Entities\Order;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

final class OrderQueryImplementationTest extends TestCase
{
use RefreshDatabase;

private OrderQuery $orderQuery;

protected function setUp(): void
{
parent::setUp();
$this->orderQuery = app(OrderQuery::class);
}

public function test_gets_order_by_id(): void
{
// آماده‌سازی
$order = Order::factory()->create([
'order_number' => 'ORD-001',
'total_amount' => 10000,
]);

// اجرا
$dto = $this->orderQuery->getById(new OrderId($order->id));

// بررسی
$this->assertEquals($order->id, $dto->id->value);
$this->assertEquals('ORD-001', $dto->orderNumber);
$this->assertEquals(10000, $dto->totalAmount);
}

public function test_returns_null_when_order_not_found(): void
{
// اجرا
$dto = $this->orderQuery->findById(new OrderId(999));

// بررسی
$this->assertNull($dto);
}

public function test_gets_orders_by_user(): void
{
// آماده‌سازی
$userId = 1;
Order::factory()->count(3)->create(['user_id' => $userId]);
Order::factory()->count(2)->create(['user_id' => 2]);

// اجرا
$orders = $this->orderQuery->getOrdersByUser(new UserId($userId));

// بررسی
$this->assertCount(3, $orders);
}
}

الگوهای رایج و مثال‌ها

الگو: پشتیبانی صفحه‌بندی

مشاهده پیاده‌سازی
interface OrderQuery extends QueryInterface
{
public function paginateByUser(
UserId $userId,
int $page = 1,
int $perPage = 15
): OrderPaginationDTO;
}

final class OrderPaginationDTO implements DataTransferObject
{
public function __construct(
public readonly OrderDTOCollection $items,
public readonly int $total,
public readonly int $currentPage,
public readonly int $perPage,
public readonly int $lastPage,
) {
}

public function toArray(): array
{
return [
'items' => array_map(
fn(OrderDTO $order) => $order->toArray(),
$this->items->toArray()
),
'total' => $this->total,
'current_page' => $this->currentPage,
'per_page' => $this->perPage,
'last_page' => $this->lastPage,
];
}
}

الگو: پرس‌وجوهای پیچیده با فیلترها

مشاهده پیاده‌سازی
final class OrderFilterDTO
{
public function __construct(
public readonly ?string $status = null,
public readonly ?DateTimeInterface $fromDate = null,
public readonly ?DateTimeInterface $toDate = null,
public readonly ?int $minAmount = null,
public readonly ?int $maxAmount = null,
) {
}
}

interface OrderQuery extends QueryInterface
{
public function findByFilters(OrderFilterDTO $filters): OrderDTOCollection;
}

// پیاده‌سازی
public function findByFilters(OrderFilterDTO $filters): OrderDTOCollection
{
$query = $this->repository->newQuery();

if ($filters->status !== null) {
$query->where('status', $filters->status);
}

if ($filters->fromDate !== null) {
$query->where('created_at', '>=', $filters->fromDate);
}

if ($filters->toDate !== null) {
$query->where('created_at', '<=', $filters->toDate);
}

if ($filters->minAmount !== null) {
$query->where('total_amount', '>=', $filters->minAmount);
}

if ($filters->maxAmount !== null) {
$query->where('total_amount', '<=', $filters->maxAmount);
}

$orders = $query->get();

$dtos = array_map(
fn(Order $order) => $this->toDTO($order),
$orders->all()
);

return new OrderDTOCollection($dtos);
}

الگو: DTOهای تو در تو

مشاهده پیاده‌سازی
final class OrderWithItemsDTO implements DataTransferObject
{
public function __construct(
public readonly OrderId $id,
public readonly string $orderNumber,
public readonly OrderItemDTOCollection $items, // مجموعه تو در تو
public readonly int $totalAmount,
) {
}

public function toArray(): array
{
return [
'id' => $this->id->value,
'order_number' => $this->orderNumber,
'items' => array_map(
fn(OrderItemDTO $item) => $item->toArray(),
$this->items->toArray()
),
'total_amount' => $this->totalAmount,
];
}
}

interface OrderQuery extends QueryInterface
{
public function getWithItems(OrderId $id): OrderWithItemsDTO;
}

عیب‌یابی

مشکل: رابط یافت نشد

خطا
Interface [App\Domains\Order\OrderQuery] does not exist
راه‌حل
  1. بررسی وجود فایل رابط در مسیر صحیح
  2. بررسی تطابق namespace با ساختار دایرکتوری
  3. اجرای composer dump-autoload
  4. پاک کردن کش Laravel: php artisan cache:clear

مشکل: پیاده‌سازی حل نشد

خطا
Target [App\Domains\Order\OrderQuery] is not instantiable
راه‌حل
  1. بررسی ثبت پیاده‌سازی در QueryServiceProvider
  2. بررسی اتصال رابط به پیاده‌سازی
  3. اطمینان از وجود کلاس پیاده‌سازی
  4. اجرای php artisan config:clear

مشکل: خطای نوع با Value Objects

خطا
Argument #1 must be of type OrderId, int given
راه‌حل
$order = $orderQuery->getById(1);

مشکل: وابستگی‌های دایره‌ای

خطا
Circular dependency detected
راه‌حل
  • رابط‌های پرس‌وجو باید فقط به رابط‌های پرس‌وجوی دیگر وابسته باشند
  • هرگز وابستگی‌های دایره‌ای بین ماژول‌ها ایجاد نکنید
  • از رویدادها برای عملیات نوشتن به جای پرس‌وجوها استفاده کنید

مشکل: مشکلات عملکرد

علائم
  • اجرای کند پرس‌وجو
  • مشکلات پرس‌وجوی N+1
  • استفاده بالای حافظه
راه‌حل‌ها

1. استفاده از eager loading در پیاده‌سازی:

public function getWithItems(OrderId $id): OrderWithItemsDTO
{
$order = $this->repository
->newQuery()
->with('items') // Eager load
->findOrFail($id->value);

return $this->toDTO($order);
}

2. استفاده از عملیات دسته‌ای:

foreach ($orderIds as $id) {
$order = $orderQuery->getById(new OrderId($id));
}

3. افزودن کش در صورت نیاز:

public function getById(OrderId $id): OrderDTO
{
return Cache::remember(
"order:{$id->value}",
3600,
fn() => $this->fetchFromDatabase($id)
);
}

نتیجه‌گیری

زیرساخت پرس‌وجو یک روش قوی و type-safe برای خواندن داده در مرزهای ماژول در عین حفظ جداسازی سست فراهم می‌کند. با پیروی از این راهنمای پیاده‌سازی و بهترین شیوه‌ها، می‌توانید:

دستاوردها
  • ✅ کد قابل نگهداری و قابل تست ایجاد کنید
  • ✅ مرزهای معماری را اعمال کنید
  • ✅ تکامل مستقل ماژول را فعال کنید
  • ✅ از نیازهای مقیاس‌پذیری آینده پشتیبانی کنید
به یاد داشته باشید

از پرس‌وجوها برای خواندن داده استفاده کنید، از رویدادها برای فعال‌سازی عملیات.