سی ام اس نویسسی ام اس نویسیاد بگیر، سیستم خودتو بنویس
مجلهتماس با ما
سی ام اس نویسسی ام اس نویس

CMS نویس مرجعی برای آموزش برنامه نویسی وب و PHP، توسعه افزونه‌های اختصاصی وردپرس، طراحی ورک‌فلو و اتوماسیون با n8n و توسعه CMS های اختصاصی است. هدف ما ارائه آموزش‌های کاربردی و راهکارهای حرفه‌ای برای توسعه‌دهندگان، کسب‌وکارها و علاقه‌مندان به دنیای برنامه‌نویسی است.

پیوندهای سریع
محصولات فیزیکیمحصولات دیجیتالدوره‌های آموزشی
سی ام اس نویس
مجله سی ام اس نویستماس با ماپنل کاربری
سی ام اس نویس — تمامی حقوق برای سی ام اس نویس محفوظ است. © 1406 تماس با ما09307711941
Domain Model Pattern در PHP چیست؟ آموزش الگوی Domain Model با مثال عملی

Domain Model Pattern در PHP چیست؟ آموزش الگوی Domain Model با مثال عملی

ممدیر کل سیستم ۲۶ شهریور ۱۴۰۵ ۲۳ دقیقه مطالعه ۶ بازدید
Domain Model Pattern در PHP چیست؟ آموزش الگوی Domain Model با مثال عملی

وقتی یک پروژه 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

قوانین ما:

  1. سفارش Pending قابل پرداخت است.
  2. سفارش Paid دوباره قابل پرداخت نیست.
  3. سفارش Pending قابل لغو است.
  4. سفارش Paid قابل لغو نیست.
  5. سفارش 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 ModelAnemic 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 پیچیدگی غیرضروری ایجاد کند. انتخاب الگو باید متناسب با پیچیدگی واقعی پروژه باشد.

نظرات (0)

نظرات پس از تأیید مدیر نمایش داده می‌شوند

هنوز نظری ثبت نشده است؛ اولین نفر باشید!

مقالات مرتبط

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

۲۴ دقیقه
ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

۲۲ دقیقه
ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

۲۲ دقیقه
مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

۲۲ دقیقه