وقتی یک پروژه PHP کوچک است، معمولاً میتوان منطق برنامه را داخل Controller، Service یا چند تابع ساده قرار داد و بدون مشکل خاصی پروژه را توسعه داد.
اما با بزرگتر شدن سیستم، شرایط تغییر میکند.
فرض کنید در حال ساخت یک فروشگاه اینترنتی هستید. سفارش میتواند وضعیتهای مختلفی داشته باشد، پرداخت فقط در شرایط خاصی مجاز باشد، سفارش لغوشده دیگر قابل پرداخت نباشد، تخفیف قوانین مختلفی داشته باشد و موجودی محصول نیز باید هنگام ثبت سفارش کنترل شود.
اگر تمام این قوانین را داخل Controllerها یا Serviceهای مختلف قرار دهیم، بهمرور Business Logic بین قسمتهای مختلف برنامه پخش میشود.
اینجاست که Domain Model Pattern میتواند به سازماندهی منطق پیچیده کسبوکار کمک کند.
در این مقاله میخواهیم ببینیم Domain Model Pattern در PHP چیست، چه مشکلی را حل میکند، چگونه آن را با PHP پیادهسازی کنیم و در پروژههای Laravel چه ساختاری میتواند داشته باشد.
Domain Model Pattern چیست؟
Domain Model یکی از الگوهای مربوط به Domain Logic در معماری نرمافزارهای سازمانی است.
Martin Fowler در کاتالوگ Patterns of Enterprise Application Architecture، Domain Model را یک مدل شیءگرا از دامنه میداند که هم داده و هم رفتار را در خود جای میدهد.
به زبان ساده:
> به جای اینکه فقط اطلاعات یک موجودیت را نگهداری کنیم، قوانین و رفتارهای مربوط به آن موجودیت را نیز در همان مدل قرار میدهیم.
برای مثال، فرض کنید یک Order داریم.
در یک طراحی ساده ممکن است چنین چیزی داشته باشیم:
class Order
{
public int $id;
public string $status;
public float $total;
}
این کلاس فقط اطلاعات سفارش را نگهداری میکند.
اما در Domain Model میتوانیم رفتار سفارش را نیز در خود Order قرار دهیم:
class Order
{
private string $status;
public function pay(): void
{
if ($this->status !== 'pending') {
throw new RuntimeException(
'Only pending orders can be paid.'
);
}
$this->status = 'paid';
}
public function cancel(): void
{
if ($this->status === 'paid') {
throw new RuntimeException(
'Paid orders cannot be cancelled.'
);
}
$this->status = 'cancelled';
}
}
در اینجا Order فقط یک ظرف برای نگهداری اطلاعات نیست.
خود سفارش میداند:
- چه زمانی میتواند پرداخت شود.
- چه زمانی میتواند لغو شود.
- چه وضعیتی دارد.
- چه قوانینی باید هنگام تغییر وضعیت رعایت شوند.
این همان ایده اصلی Domain Model است.
Domain Model چه مشکلی را حل میکند؟
در پروژههای ساده، Business Logic ممکن است در Controller قرار بگیرد:
public function cancel(Order $order)
{
if ($order->status === 'paid') {
return back()->with('error', 'Cannot cancel paid order.');
}
$order->status = 'cancelled';
$order->save();
return back();
}
در ابتدا مشکلی وجود ندارد.
اما فرض کنید همین قانون لغو سفارش در چند قسمت سیستم مورد نیاز باشد:
- پنل کاربر
- پنل مدیریت
- API
- سیستم پشتیبانی
- Command
- Job
- Webhook
حالا اگر قانون لغو سفارش در همه این قسمتها تکرار شود، احتمال ایجاد ناسازگاری افزایش پیدا میکند.
برای مثال:
// Controller
if ($order->status !== 'paid') {
// cancel
}
و جای دیگر:
// API
if ($order->status !== 'completed') {
// cancel
}
و جای دیگر:
// Job
if ($order->status === 'pending') {
// cancel
}
بهمرور مشخص نیست قانون واقعی سیستم دقیقاً کجاست.
Domain Model تلاش میکند رفتار مرتبط با Domain Object را در خود مدل قرار دهد.
# ساختار Domain Model چگونه است؟
میتوانیم یک Domain Model ساده را به شکل زیر تصور کنیم:
Application
|
v
Domain
|
+---- Entities
|
+---- Value Objects
|
+---- Domain Services
|
+---- Domain Rules
|
v
Repositories / Infrastructure
|
v
Database
برای مثال در یک فروشگاه اینترنتی:
Order
├── OrderItem
├── Money
├── Address
└── PaymentStatus
هر کدام از این بخشها میتوانند بخشی از مفاهیم واقعی Domain را نمایش دهند.
# Domain Model در PHP چگونه پیادهسازی میشود؟
برای درک بهتر، یک سیستم سفارش ساده میسازیم.
فرض کنیم سفارش سه وضعیت دارد:
pending
paid
cancelled
قوانین ما:
- سفارش Pending قابل پرداخت است.
- سفارش Paid دوباره قابل پرداخت نیست.
- سفارش Pending قابل لغو است.
- سفارش Paid قابل لغو نیست.
- سفارش Cancelled قابل پرداخت نیست.
بدون Domain Model ممکن است این قوانین در چند Controller پخش شوند.
اما میتوانیم آنها را داخل خود Order قرار دهیم.
# پیادهسازی Entity در PHP
ابتدا کلاس Order را ایجاد میکنیم:
<?php
class Order
{
private string $status;
public function __construct(
private int $id,
private float $total
) {
$this->status = 'pending';
}
public function pay(): void
{
if ($this->status !== 'pending') {
throw new RuntimeException(
'Only pending orders can be paid.'
);
}
$this->status = 'paid';
}
public function cancel(): void
{
if ($this->status !== 'pending') {
throw new RuntimeException(
'Only pending orders can be cancelled.'
);
}
$this->status = 'cancelled';
}
public function isPaid(): bool
{
return $this->status === 'paid';
}
public function isCancelled(): bool
{
return $this->status === 'cancelled';
}
public function getStatus(): string
{
return $this->status;
}
}
حالا میتوانیم از آن استفاده کنیم:
$order = new Order(
id: 1001,
total: 2500000
);
$order->pay();
echo $order->getStatus();
خروجی:
paid
اگر دوباره بخواهیم سفارش را پرداخت کنیم:
$order->pay();
با Exception مواجه میشویم.
چون خود Order میداند پرداخت مجدد مجاز نیست.
# چرا این طراحی بهتر از قرار دادن Logic در Controller است؟
فرض کنید Controller این کار را انجام دهد:
if ($order->status === 'pending') {
$order->status = 'paid';
}
در این حالت Controller از جزئیات Business Rule اطلاع دارد.
اما در Domain Model:
$order->pay();
Controller فقط میگوید:
> این سفارش را پرداخت کن.
و خود Domain Model تصمیم میگیرد که آیا این کار مجاز است یا خیر.
این موضوع باعث میشود Business Rule به مفهوم واقعی خودش نزدیکتر شود.
# Entity در Domain Model چیست؟
در Domain Model معمولاً با مفاهیمی مثل Entity، Value Object و Domain Service روبهرو میشویم.
Entity شیئی است که هویت آن برای سیستم اهمیت دارد.
برای مثال:
User
Order
Product
Invoice
Subscription
Ticket
Payment
یک سفارش با شناسه 1001 همان سفارش مشخص است، حتی اگر بعضی از ویژگیهای آن تغییر کند.
مثلاً:
Order #1001
status = pending
بعداً:
Order #1001
status = paid
هنوز همان Entity است.
# Value Object چیست؟
Value Object معمولاً برای نمایش یک مقدار مفهومی استفاده میشود و هویت مستقل مانند Entity ندارد.
Martin Fowler نیز Value Object را در میان الگوهای پایه معماری Enterprise Application معرفی میکند و نمونههایی مانند Money و Date Range را در این دسته قرار میدهد.
برای مثال به جای اینکه مبلغ را فقط با float نگهداری کنیم:
$total = 2500000;
میتوانیم یک Value Object بسازیم:
final class Money
{
public function __construct(
private int $amount,
private string $currency = 'IRR'
) {
if ($amount < 0) {
throw new InvalidArgumentException(
'Amount cannot be negative.'
);
}
}
public function amount(): int
{
return $this->amount;
}
public function currency(): string
{
return $this->currency;
}
}
حالا:
$price = new Money(
amount: 2500000,
currency: 'IRR'
);
این طراحی از نظر مدلسازی Domain میتواند مفهوم پول را واضحتر کند.
# ترکیب Entity و Value Object
اکنون میتوانیم Order را بهتر طراحی کنیم:
final class Order
{
private string $status;
public function __construct(
private int $id,
private Money $total
) {
$this->status = 'pending';
}
public function pay(): void
{
if ($this->status !== 'pending') {
throw new RuntimeException(
'Order cannot be paid.'
);
}
$this->status = 'paid';
}
public function cancel(): void
{
if ($this->status !== 'pending') {
throw new RuntimeException(
'Order cannot be cancelled.'
);
}
$this->status = 'cancelled';
}
}
حالا مدل ما مفهوم بیشتری دارد.
Order یک سفارش است.
Money مفهوم پول را نمایش میدهد.
و قوانین مربوط به وضعیت سفارش داخل Order قرار گرفتهاند.
# Domain Service چیست؟
همه Business Logicها الزاماً متعلق به یک Entity نیستند.
فرض کنید میخواهیم منطق انتقال پول بین دو حساب را پیادهسازی کنیم.
این رفتار ممکن است به دو Entity وابسته باشد:
Account A
|
| Transfer
v
Account B
در چنین شرایطی میتوان از Domain Service استفاده کرد.
مثلاً:
final class MoneyTransferService
{
public function transfer(
Account $from,
Account $to,
Money $amount
): void {
if ($from->balance()->amount() < $amount->amount()) {
throw new RuntimeException(
'Insufficient balance.'
);
}
$from->withdraw($amount);
$to->deposit($amount);
}
}
در اینجا منطق انتقال پول به یک Entity خاص تعلق ندارد.
بنابراین قرار دادن آن داخل Account ممکن است مدل را نامناسب کند.
# Repository چه نقشی در Domain Model دارد؟
Domain Model نباید الزاماً مسئول ارتباط مستقیم با دیتابیس باشد.
برای مثال میتوانیم یک Interface برای Repository داشته باشیم:
interface OrderRepository
{
public function findById(int $id): ?Order;
public function save(Order $order): void;
}
سپس یک پیادهسازی برای دیتابیس داشته باشیم:
final class DatabaseOrderRepository implements OrderRepository
{
public function findById(int $id): ?Order
{
// Load order from database
}
public function save(Order $order): void
{
// Save order to database
}
}
در این حالت Domain Model میتواند روی مفهوم OrderRepository کار کند و جزئیات ذخیرهسازی در Infrastructure قرار بگیرد.
Fowler نیز Repository را الگویی برای واسطهگری بین Domain و Data Mapping Layer و ارائه یک Interface شبیه Collection برای دسترسی به Domain Objectها معرفی میکند.
# Domain Model در Laravel
Laravel به صورت پیشفرض از Eloquent استفاده میکند و Eloquent خودش ویژگیهای Active Record را در اختیار مدلها قرار میدهد.
بنابراین یک پروژه Laravel میتواند به شکل ساده چنین مدلی داشته باشد:
class Order extends Model
{
protected $fillable = [
'status',
'total',
];
}
اما اگر Business Logic پیچیده شود، بهتر است تمام قوانین Domain را به شکل کنترلنشده داخل Eloquent Model قرار ندهیم.
برای پروژههای پیچیدهتر میتوان ساختار Domain را جدا کرد.
برای مثال:
app/
├── Domain/
│ └── Orders/
│ ├── Entities/
│ │ └── Order.php
│ │
│ ├── ValueObjects/
│ │ └── Money.php
│ │
│ ├── Services/
│ │ └── OrderPaymentService.php
│ │
│ └── Contracts/
│ └── OrderRepository.php
│
├── Application/
│ └── Orders/
│ ├── CreateOrder.php
│ └── PayOrder.php
│
├── Infrastructure/
│ └── Persistence/
│ └── EloquentOrderRepository.php
│
└── Http/
└── Controllers/
└── OrderController.php
این ساختار تنها یک نمونه معماری است و لازم نیست برای هر پروژه Laravel با چنین پیچیدگیای استفاده شود.
# یک مثال عملی در Laravel
فرض کنیم کاربر میخواهد سفارش خود را پرداخت کند.
Controller میتواند بسیار ساده باشد:
public function pay(int $id)
{
$order = $this->orders->findById($id);
$order->pay();
$this->orders->save($order);
return response()->json([
'message' => 'Order paid successfully.',
]);
}
Controller دیگر لازم نیست بداند:
- چه وضعیتی قابل پرداخت است.
- آیا سفارش قبلاً پرداخت شده؟
- آیا سفارش لغو شده؟
- قوانین تغییر وضعیت چیست؟
این قوانین در Domain Model قرار گرفتهاند.
# Application Layer چه کاری انجام میدهد؟
یک نکته مهم این است که Domain Model و Application Service یکی نیستند.
مثلاً:
final class PayOrder
{
public function __construct(
private OrderRepository $orders
) {
}
public function execute(int $orderId): void
{
$order = $this->orders->findById($orderId);
if (!$order) {
throw new RuntimeException(
'Order not found.'
);
}
$order->pay();
$this->orders->save($order);
}
}
اینجا PayOrder فرآیند Application را هماهنگ میکند.
اما تصمیم اصلی:
$order->pay();
در خود Domain Model گرفته میشود.
این تفاوت بسیار مهم است.
# Rich Domain Model چیست؟
در یک Rich Domain Model، Entityها علاوه بر داده، رفتار و Business Ruleهای مرتبط با خود را نیز دارند.
مثلاً:
$order->pay();
$order->cancel();
$order->addItem($item);
$order->removeItem($item);
در این طراحی، Order صرفاً مجموعهای از Getter و Setter نیست.
بلکه رفتار واقعی سفارش را مدل میکند.
برای مثال:
$order->status = 'paid';
ممکن است از نظر طراحی Domain ضعیفتر باشد.
در مقابل:
$order->pay();
معنای بیشتری دارد.
# Anemic Domain Model چیست؟
در مقابل Rich Domain Model، اصطلاح Anemic Domain Model برای مدلی به کار میرود که بیشتر شبیه یک Data Container است و Business Logic عمدتاً خارج از آن قرار گرفته است.
برای مثال:
class Order
{
public string $status;
public float $total;
}
و سپس:
class OrderService
{
public function pay(Order $order): void
{
if ($order->status !== 'pending') {
throw new RuntimeException();
}
$order->status = 'paid';
}
}
در این حالت Order تقریباً فقط داده نگهداری میکند و رفتار در Service قرار گرفته است.
این طراحی همیشه اشتباه نیست؛ انتخاب مناسب به پیچیدگی Domain و معماری کل برنامه بستگی دارد.
# تفاوت Rich Domain Model و Anemic Domain Model
به صورت ساده:
| ویژگی | Rich Domain Model | Anemic Domain Model |
|---|---|---|
| داده | دارد | دارد |
| رفتار | داخل مدل | عمدتاً خارج مدل |
| Business Rule | نزدیک به Domain | در Serviceها |
| خوانایی Domain | معمولاً بیشتر | وابسته به Serviceها |
| پیچیدگی پیادهسازی | بیشتر | معمولاً کمتر |
| مناسب برای Domain پیچیده | معمولاً مناسبتر | ممکن است دشوار شود |
| مناسب برای CRUD ساده | ممکن است بیش از نیاز باشد | معمولاً سادهتر |
بنابراین نباید تصور کنیم Rich Domain Model همیشه بهترین انتخاب برای تمام پروژههاست.
اگر برنامه صرفاً CRUD سادهای دارد، پیادهسازی یک معماری Domain پیچیده ممکن است ارزش اضافی ایجاد نکند.
# تفاوت Domain Model و Transaction Script
این دو الگو از مهمترین الگوهای سازماندهی Domain Logic هستند. Fowler در دستهبندی خود Transaction Script، Domain Model و Table Module را در بخش Domain Logic Patterns قرار میدهد.
در Transaction Script، منطق معمولاً بر اساس عملیات یا درخواست سازماندهی میشود.
مثلاً:
CreateOrder()
PayOrder()
CancelOrder()
RefundOrder()
اما در Domain Model، تمرکز بیشتر روی اشیای Domain و رفتار آنهاست:
Order
├── pay()
├── cancel()
├── refund()
└── addItem()
Transaction Script
function cancelOrder(Order $order): void
{
if ($order->status !== 'pending') {
throw new RuntimeException();
}
$order->status = 'cancelled';
}
Domain Model
$order->cancel();
در حالت دوم، قانون لغو سفارش به خود مفهوم Order نزدیکتر شده است.
# تفاوت Domain Model و Active Record
در Active Record، یک Object معمولاً هم داده مربوط به رکورد را نگهداری میکند و هم عملیات مربوط به Persistence را در خود دارد.
Fowler در کاتالوگ Enterprise Application Architecture، Active Record را الگویی معرفی میکند که یک Row دیتابیس را Wrap کرده، دسترسی به دیتابیس را کپسوله میکند و میتواند Domain Logic مرتبط با آن داده را نیز در خود داشته باشد.
برای مثال در Laravel:
$order = Order::find(10);
$order->status = 'paid';
$order->save();
این رویکرد بسیار کاربردی است.
اما در یک Domain Model مستقل، ممکن است Entity ما اصلاً از Eloquent اطلاعی نداشته باشد:
$order->pay();
و عملیات ذخیرهسازی توسط Repository انجام شود:
$repository->save($order);
این جداسازی در Domainهای پیچیده میتواند مفید باشد.
# تفاوت Domain Model و Service Layer
Service Layer یک Boundary برای عملیات Application ایجاد میکند و عملیات موجود در سیستم را هماهنگ میکند. Fowler نیز Service Layer را الگویی برای تعریف مرز سرویسهای Application و هماهنگکردن پاسخ عملیات معرفی میکند.
برای مثال:
$payOrder->execute($orderId);
میتواند Application Service باشد.
اما:
$order->pay();
رفتار Domain است.
در یک معماری مناسب ممکن است هر دو را داشته باشیم:
Controller
|
v
Application Service
|
v
Domain Model
|
v
Repository
# آیا Domain Model همان DDD است؟
خیر.
این دو مفهوم مرتبط هستند اما یکی نیستند.
Domain Model یک الگو برای سازماندهی Domain Logic است.
اما Domain-Driven Design یا DDD یک رویکرد گستردهتر برای طراحی نرمافزار بر اساس Domain و زبان مشترک کسبوکار است.
DDD میتواند از مفاهیمی مانند:
- Entity
- Value Object
- Aggregate
- Repository
- Domain Service
- Domain Event
- Bounded Context
استفاده کند.
بنابراین نباید صرفاً با ساختن یک کلاس Order ادعا کنیم که کل پروژه DDD شده است.
# Domain Model چه زمانی مناسب است؟
Domain Model معمولاً زمانی ارزش بیشتری پیدا میکند که Business Logic پیچیده باشد.
برای مثال:
- سیستمهای مالی
- فروشگاههای پیچیده
- سیستمهای رزرو
- سیستمهای اشتراک
- سیستمهای حسابداری
- سیستمهای بیمه
- سیستمهای مدیریت سفارش
- سیستمهای لجستیک
- سیستمهای CRM پیچیده
- سیستمهای پرداخت
در چنین پروژههایی قوانین Business ممکن است دائماً تغییر کنند.
Fowler نیز Domain Model را برای زمانی مطرح میکند که پیچیدگی Business Logic زیاد است و مدل شیءگرا میتواند این پیچیدگی را در قالب اشیای مرتبط سازماندهی کند.
# چه زمانی Domain Model انتخاب مناسبی نیست؟
اگر پروژه شما فقط شامل چند CRUD ساده باشد، ممکن است ساختن:
Domain
Application
Infrastructure
Repositories
Value Objects
Domain Services
بیش از نیاز پروژه باشد.
مثلاً یک سیستم ساده مدیریت دستهبندی:
Create Category
Update Category
Delete Category
List Categories
احتمالاً نیازی به یک Domain Model پیچیده ندارد.
در چنین پروژهای یک Laravel Model ساده و Service معمولی ممکن است کاملاً کافی باشد.
اصل مهم این است:
> معماری باید متناسب با پیچیدگی واقعی سیستم باشد.
# تست Domain Model با PHPUnit
یکی از مزایای مهم Domain Model این است که میتوان Business Ruleها را بدون اجرای کامل HTTP Request تست کرد.
مثلاً:
public function test_pending_order_can_be_paid(): void
{
$order = new Order(
id: 1,
total: new Money(2500000)
);
$order->pay();
$this->assertTrue(
$order->isPaid()
);
}
حالا قانون جلوگیری از پرداخت مجدد:
public function test_paid_order_cannot_be_paid_again(): void
{
$order = new Order(
id: 1,
total: new Money(2500000)
);
$order->pay();
$this->expectException(
RuntimeException::class
);
$order->pay();
}
و تست لغو سفارش:
public function test_pending_order_can_be_cancelled(): void
{
$order = new Order(
id: 1,
total: new Money(2500000)
);
$order->cancel();
$this->assertTrue(
$order->isCancelled()
);
}
این تستها مستقیماً Business Rule را بررسی میکنند.
# یک ساختار حرفهایتر برای Domain در Laravel
برای پروژههای بزرگتر میتوان ساختار را بر اساس Domain سازماندهی کرد:
app/
└── Domain/
└── Orders/
├── Entities/
│ └── Order.php
│
├── ValueObjects/
│ ├── Money.php
│ └── OrderNumber.php
│
├── Services/
│ └── OrderPricingService.php
│
├── Contracts/
│ └── OrderRepository.php
│
└── Exceptions/
├── OrderAlreadyPaid.php
└── OrderCannotBeCancelled.php
و لایه Application:
app/
└── Application/
└── Orders/
├── CreateOrder.php
├── PayOrder.php
└── CancelOrder.php
و Infrastructure:
app/
└── Infrastructure/
└── Persistence/
└── Eloquent/
└── EloquentOrderRepository.php
این نوع جداسازی کمک میکند وابستگیهای Laravel و Database از Core Domain جدا شوند.
# آیا Domain Entity باید از Eloquent Model ارثبری کند؟
الزامی نیست.
برای یک Domain Model مستقل، میتوان Entity را یک کلاس معمولی PHP نگه داشت:
final class Order
{
// Domain logic
}
و Eloquent را در Infrastructure قرار داد.
مثلاً:
final class EloquentOrderRepository
{
public function save(Order $order): void
{
// Map domain object to Eloquent model
}
}
این روش به جداسازی Domain از Persistence کمک میکند.
در مقابل، برای پروژههای سادهتر میتوان Business Logic را در Eloquent Model یا Serviceهای Laravel نگه داشت.
انتخاب معماری باید بر اساس نیاز پروژه باشد، نه صرفاً استفاده از یک الگوی معماری به دلیل محبوبیت آن.
# اشتباهات رایج در پیادهسازی Domain Model
۱. تبدیل تمام Modelها به Domain Model
هر کلاس PHP یا Laravel Model الزاماً نیازمند Domain Model نیست.
اگر Domain بسیار ساده است، پیچیدهکردن معماری ممکن است هزینه اضافی ایجاد کند.
۲. قرار دادن همه چیز داخل Entity
اینکه Domain Model داریم به این معنی نیست که تمام منطق برنامه را داخل Entity قرار دهیم.
اگر یک رفتار به چند Entity وابسته است، ممکن است Domain Service انتخاب مناسبتری باشد.
۳. وابستگی شدید Domain به Laravel
مثلاً:
class Order extends Model
{
// hundreds of business rules
}
اگر تمام Business Logic، Eloquent، HTTP، Notification و Database را در یک Model قرار دهیم، مدل بهمرور بسیار بزرگ و وابسته میشود.
۴. استفاده بیش از حد از Repository
Repository زمانی مفید است که واقعاً به جداسازی Domain از Persistence نیاز داشته باشیم.
برای یک CRUD ساده، ساختن چندین Interface و Implementation ممکن است فقط پیچیدگی را افزایش دهد.
۵. اشتباه گرفتن Domain Model با DDD
ساختن Entity و Repository به تنهایی به معنی پیادهسازی کامل DDD نیست.
Domain Model یکی از ابزارهای مدلسازی Domain است.
# مزایای Domain Model Pattern
مهمترین مزایای این الگو عبارتاند از:
۱. نزدیک شدن Business Logic به Domain
قوانین مربوط به Order در خود Order قرار میگیرند.
۲. کاهش تکرار Business Rule
به جای اینکه یک قانون در چند Controller تکرار شود، میتوان آن را در Domain Model قرار داد.
۳. تستپذیری بهتر
میتوان بسیاری از Business Ruleها را بدون اجرای HTTP یا Database تست کرد.
۴. مدلسازی بهتر مفاهیم کسبوکار
مفاهیمی مثل:
Order
Money
Address
Invoice
Subscription
Payment
میتوانند به Objectهای واقعی تبدیل شوند.
۵. مناسب برای سیستمهای پیچیده
هرچه Business Ruleها پیچیدهتر شوند، مدلسازی صحیح Domain اهمیت بیشتری پیدا میکند.
# معایب Domain Model Pattern
در مقابل، این الگو هزینههایی نیز دارد.
پیچیدگی بیشتر
برای یک CRUD ساده ممکن است بیش از نیاز باشد.
نیاز به طراحی دقیق
ساختن Entityهای مناسب، Value Objectها و Domain Serviceها نیازمند شناخت خوب Domain است.
نیاز به آموزش تیم
اگر تیم با Domain Modeling آشنا نباشد، ممکن است ساختار ایجادشده به جای سادهتر کردن پروژه، آن را پیچیدهتر کند.
هزینه اولیه بیشتر
در پروژههای کوچک، معماری سادهتر ممکن است سرعت توسعه بیشتری داشته باشد.
# Domain Model یا Transaction Script؟
برای تصمیمگیری میتوان سؤالهای زیر را مطرح کرد.
اگر Business Logic ساده است:
ثبت سفارش
ویرایش سفارش
حذف سفارش
Transaction Script میتواند کافی باشد.
اما اگر سفارش قوانین پیچیدهای مانند:
تخفیف
مالیات
پرداخت
لغو
بازپرداخت
محدودیت زمانی
انواع مشتری
انواع ارسال
موجودی
قوانین قیمتگذاری
داشته باشد، مدلسازی Domain اهمیت بیشتری پیدا میکند.
بنابراین انتخاب بین این دو بیشتر به پیچیدگی Domain وابسته است تا اینکه یکی از آنها همیشه بهتر باشد.
# یک مثال نهایی از طراحی Domain Model
فرض کنیم سیستم فروشگاهی ما چنین ساختاری دارد:
Order
│
├── OrderItem
│
├── Money
│
├── Address
│
├── pay()
│
├── cancel()
│
└── calculateTotal()
Application Layer:
PayOrder
CancelOrder
CreateOrder
Repository:
OrderRepository
Infrastructure:
EloquentOrderRepository
در نتیجه جریان کلی سیستم میتواند چنین باشد:
HTTP Request
|
v
Controller
|
v
Application Service
|
v
Domain Model
|
+---- Entity
|
+---- Value Object
|
+---- Domain Service
|
v
Repository
|
v
Database
این ساختار کمک میکند هر بخش مسئولیت مشخصتری داشته باشد.
# جمعبندی
Domain Model Pattern در PHP الگویی برای سازماندهی Business Logic است که در آن Domain به شکل مجموعهای از Objectها مدل میشود و این Objectها علاوه بر داده، رفتار و قوانین مرتبط با خود را نیز در اختیار دارند. Fowler این الگو را در مجموعه الگوهای Domain Logic در Patterns of Enterprise Application Architecture قرار میدهد.
تفاوت اصلی آن با Transaction Script این است که در Transaction Script تمرکز روی عملیات و Procedureهاست، اما در Domain Model تمرکز روی مفاهیم Domain و رفتار آنها قرار میگیرد.
در یک پروژه PHP یا Laravel میتوان Domain Model را با مفاهیمی مانند:
Entity
Value Object
Domain Service
Repository
Application Service
ترکیب کرد.
با این حال، استفاده از Domain Model برای همه پروژهها ضروری نیست.
اگر پروژه ساده و CRUD محور است، معماری ساده میتواند انتخاب مناسبی باشد.
اما اگر Business Logic پیچیده است و قوانین زیادی در سیستم وجود دارد، Domain Model میتواند به سازماندهی این پیچیدگی کمک کند.
# سوالات متداول
Domain Model Pattern در PHP چیست؟
Domain Model Pattern الگویی برای سازماندهی Business Logic است که داده و رفتار مرتبط با مفاهیم Domain را در Objectهای مدل قرار میدهد.
تفاوت Domain Model و Transaction Script چیست؟
Transaction Script منطق را معمولاً بر اساس عملیات یا درخواست سازماندهی میکند، در حالی که Domain Model منطق را در مدلهای Domain و رفتار آنها سازماندهی میکند.
آیا Domain Model همان DDD است؟
خیر. Domain Model یک الگو برای مدلسازی Domain است، در حالی که DDD رویکرد گستردهتری برای طراحی نرمافزار بر اساس Domain است.
آیا Domain Model در Laravel قابل استفاده است؟
بله. میتوان Domain Model را در Laravel به صورت جدا از Eloquent طراحی کرد و Persistence را از طریق Repository یا Infrastructure مدیریت کرد.
Entity و Value Object چه تفاوتی دارند؟
Entity دارای هویت مشخص است؛ مانند سفارش یا کاربر. Value Object بیشتر برای نمایش یک مقدار مفهومی مانند Money یا Date Range استفاده میشود.
آیا Domain Model باید به Eloquent وابسته باشد؟
خیر. در معماریهای Domain محور میتوان Domain Entity را از Eloquent مستقل نگه داشت و عملیات Persistence را در Infrastructure انجام داد.
چه زمانی Domain Model مناسب است؟
زمانی که Business Logic و قوانین Domain نسبتاً پیچیده هستند و قرار دادن آنها در Controllerها یا Scriptهای متعدد باعث پراکندگی و تکرار میشود.
آیا برای همه پروژههای PHP باید Domain Model استفاده کنیم؟
خیر. برای پروژههای ساده ممکن است استفاده از Domain Model پیچیدگی غیرضروری ایجاد کند. انتخاب الگو باید متناسب با پیچیدگی واقعی پروژه باشد.





