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

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

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

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

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

در بسیاری از پروژه‌های PHP، مخصوصاً پروژه‌های کوچک و متوسط، هر عملیات برنامه را می‌توان به‌عنوان یک Transaction یا Use Case در نظر گرفت.

برای مثال در یک فروشگاه اینترنتی ممکن است عملیات‌های زیر را داشته باشیم:


ثبت سفارش

لغو سفارش

پرداخت سفارش

ثبت کاربر

ویرایش محصول

حذف محصول

هرکدام از این عملیات مراحل و منطق خاص خودشان را دارند.

یکی از ساده‌ترین روش‌ها برای سازمان‌دهی چنین منطقی، استفاده از Transaction Script Pattern است.

در این الگو، منطق مربوط به هر درخواست یا تراکنش در یک Procedure یا Script متمرکز می‌شود. Martin Fowler این الگو را روشی برای سازمان‌دهی Business Logic بر اساس رویه‌هایی معرفی می‌کند که هر رویه یک درخواست از Presentation را مدیریت می‌کند.

این الگو برخلاف بسیاری از Design Patternهای کلاسیک GoF، در مجموعه الگوهای Patterns of Enterprise Application Architecture قرار می‌گیرد و بیشتر یک الگوی سازمان‌دهی Domain Logic محسوب می‌شود.


# Transaction Script Pattern در PHP چیست؟

Transaction Script Pattern الگویی برای سازمان‌دهی Business Logic است که در آن هر عملیات یا Use Case اصلی برنامه توسط یک Procedure یا Script مشخص اجرا می‌شود.

به زبان ساده:

> برای هر عملیات اصلی برنامه، یک Script داریم که مراحل اجرای همان عملیات را مدیریت می‌کند.

مثلاً:


CreateOrder

    ↓

Transaction Script



CancelOrder

    ↓

Transaction Script



PayOrder

    ↓

Transaction Script

هر Script می‌تواند:


دریافت داده

↓

اعتبارسنجی

↓

خواندن اطلاعات

↓

اجرای منطق

↓

ذخیره اطلاعات

↓

برگرداندن نتیجه

را انجام دهد.

در توضیح رسمی Fowler، Transaction Script معمولاً منطق یک تراکنش را به‌صورت یک رویه متمرکز می‌کند و این رویه می‌تواند مستقیماً با دیتابیس یا یک لایه نازک دسترسی به دیتابیس کار کند.


# Transaction Script چه مشکلی را حل می‌کند؟

فرض کنید یک سیستم ساده مدیریت سفارش داریم.

ممکن است برای ثبت سفارش چنین مراحلی داشته باشیم:


1. پیدا کردن محصول

2. بررسی موجودی

3. محاسبه قیمت

4. ایجاد سفارش

5. کاهش موجودی

6. ثبت تراکنش

در یک پروژه ساده می‌توانیم همه این مراحل را داخل یک تابع قرار دهیم:


function createOrder(

    int $userId,

    int $productId,

    int $quantity

): array {

    // ...

}

این تابع همان Transaction Script ماست.

به این ترتیب منطق ثبت سفارش در یک نقطه مشخص قرار می‌گیرد.


# ساختار Transaction Script چگونه است؟

یک ساختار ساده می‌تواند چنین باشد:


Request

   ↓

Transaction Script

   ↓

Database

   ↓

Response

مثلاً:


POST /orders

      ↓

CreateOrderScript

      ↓

Product

Order

Payment

      ↓

Database

در پروژه‌های ساده، این ساختار بسیار قابل فهم است.


# یک مثال ساده از Transaction Script در PHP

فرض کنید یک سیستم فروش محصول داریم.

تابع زیر یک محصول را خریداری می‌کند:


<?php



declare(strict_types=1);



function purchaseProduct(

    PDO $db,

    int $userId,

    int $productId,

    int $quantity

): int {

    $statement = $db->prepare(

        'SELECT id, price, stock

         FROM products

         WHERE id = :id'

    );



    $statement->execute([

        'id' => $productId,

    ]);



    $product = $statement->fetch(PDO::FETCH_ASSOC);



    if (!$product) {

        throw new RuntimeException(

            'محصول پیدا نشد.'

        );

    }



    if ((int) $product['stock'] < $quantity) {

        throw new RuntimeException(

            'موجودی محصول کافی نیست.'

        );

    }



    $total = (int) $product['price'] * $quantity;



    $statement = $db->prepare(

        'INSERT INTO orders

        (user_id, total)

        VALUES (:user_id, :total)'

    );



    $statement->execute([

        'user_id' => $userId,

        'total' => $total,

    ]);



    $orderId = (int) $db->lastInsertId();



    $statement = $db->prepare(

        'UPDATE products

         SET stock = stock - :quantity

         WHERE id = :id'

    );



    $statement->execute([

        'quantity' => $quantity,

        'id' => $productId,

    ]);



    return $orderId;

}

این تابع یک Transaction Script ساده است.

چون عملیات:


Load Product

↓

Check Stock

↓

Calculate Total

↓

Create Order

↓

Decrease Stock

همگی در یک Procedure قرار گرفته‌اند.


# چرا این کد Transaction Script محسوب می‌شود؟

چون تابع purchaseProduct() یک عملیات کامل سیستم را انجام می‌دهد.

ورودی:


$userId

$productId

$quantity

پردازش:


خواندن محصول

بررسی موجودی

محاسبه مبلغ

ثبت سفارش

کاهش موجودی

خروجی:


$orderId

در نتیجه:


One Use Case

      ↓

One Transaction Script

این دقیقاً ایده اصلی Transaction Script است.


# استفاده از Database Transaction

یک نکته مهم:

Transaction Script با Database Transaction یکی نیست.

Transaction Script یک الگوی سازمان‌دهی Business Logic است.

اما Database Transaction مکانیزمی برای حفظ Atomicity عملیات دیتابیس است.

مثلاً می‌توانیم Transaction Script بالا را با Database Transaction ترکیب کنیم:


<?php



declare(strict_types=1);



function purchaseProduct(

    PDO $db,

    int $userId,

    int $productId,

    int $quantity

): int {

    $db->beginTransaction();



    try {

        $statement = $db->prepare(

            'SELECT id, price, stock

             FROM products

             WHERE id = :id

             FOR UPDATE'

        );



        $statement->execute([

            'id' => $productId,

        ]);



        $product = $statement->fetch(PDO::FETCH_ASSOC);



        if (!$product) {

            throw new RuntimeException(

                'محصول پیدا نشد.'

            );

        }



        if ((int) $product['stock'] < $quantity) {

            throw new RuntimeException(

                'موجودی کافی نیست.'

            );

        }



        $total =

            (int) $product['price']

            * $quantity;



        $statement = $db->prepare(

            'INSERT INTO orders

            (user_id, total)

            VALUES (:user_id, :total)'

        );



        $statement->execute([

            'user_id' => $userId,

            'total' => $total,

        ]);



        $orderId = (int) $db->lastInsertId();



        $statement = $db->prepare(

            'UPDATE products

             SET stock = stock - :quantity

             WHERE id = :id'

        );



        $statement->execute([

            'quantity' => $quantity,

            'id' => $productId,

        ]);



        $db->commit();



        return $orderId;

    } catch (Throwable $e) {

        $db->rollBack();



        throw $e;

    }

}

در اینجا:


Transaction Script

+

Database Transaction

با هم استفاده شده‌اند.


# Transaction Script در Laravel

در Laravel نیز می‌توان همین ایده را پیاده کرد.

فرض کنید Route زیر را داریم:


Route::post(

    '/orders',

    [OrderController::class, 'store']

);

Controller می‌تواند یک Script را اجرا کند.

مثلاً:


<?php



namespace App\Services;



use App\Models\Order;

use App\Models\Product;

use Illuminate\Support\Facades\DB;

use RuntimeException;



final class OrderService

{

    public function create(

        int $userId,

        int $productId,

        int $quantity

    ): Order {

        return DB::transaction(function () use (

            $userId,

            $productId,

            $quantity

        ) {

            $product = Product::query()

                ->lockForUpdate()

                ->findOrFail($productId);



            if ($product->stock < $quantity) {

                throw new RuntimeException(

                    'موجودی محصول کافی نیست.'

                );

            }



            $total =

                $product->price

                * $quantity;



            $order = Order::create([

                'user_id' => $userId,

                'total' => $total,

                'status' => 'pending',

            ]);



            $product->decrement(

                'stock',

                $quantity

            );



            return $order;

        });

    }

}

اینجا متد create() را می‌توان یک Transaction Script برای Use Case ثبت سفارش در نظر گرفت.

البته در Laravel، نام‌گذاری کلاس به Service یا Action نیز رایج است؛ مهم‌تر از نام کلاس، این است که منطق یک عملیات مشخص را متمرکز و قابل فهم نگه داریم.


# ساخت Transaction Script به‌صورت کلاس مستقل

یکی از روش‌های مرتب‌تر این است که برای هر عملیات یک کلاس داشته باشیم:


app/

└── Actions/

    └── Orders/

        └── CreateOrder.php

مثلاً:


<?php



namespace App\Actions\Orders;



use App\Models\Order;

use App\Models\Product;

use Illuminate\Support\Facades\DB;

use RuntimeException;



final class CreateOrder

{

    public function handle(

        int $userId,

        int $productId,

        int $quantity

    ): Order {

        return DB::transaction(function () use (

            $userId,

            $productId,

            $quantity

        ) {

            $product = Product::query()

                ->lockForUpdate()

                ->findOrFail($productId);



            if ($product->stock < $quantity) {

                throw new RuntimeException(

                    'موجودی کافی نیست.'

                );

            }



            $order = Order::create([

                'user_id' => $userId,

                'total' =>

                    $product->price * $quantity,

            ]);



            $product->decrement(

                'stock',

                $quantity

            );



            return $order;

        });

    }

}

استفاده:


$order = app(CreateOrder::class)->handle(

    userId: auth()->id(),

    productId: $productId,

    quantity: $quantity

);

این ساختار به ایده‌ای نزدیک است که Fowler نیز برای سازمان‌دهی Transaction Scriptها مطرح می‌کند: می‌توان چند Script مرتبط را در یک کلاس قرار داد یا برای هر Script یک کلاس جدا ساخت؛ انتخاب بین این دو به نیاز پروژه بستگی دارد.


# Transaction Script و Controller

یک اشتباه رایج این است که Transaction Script را با Controller یکی بدانیم.

مثلاً:


public function store(Request $request)

{

    // تمام Business Logic

}

اگر Controller تمام عملیات Use Case را انجام دهد، از نظر مفهومی می‌تواند شبیه Transaction Script عمل کند، اما Controller هم‌زمان مسئول Presentation/HTTP نیز هست.

ساختار بهتر:


HTTP Request

     ↓

Controller

     ↓

Transaction Script

     ↓

Database

Controller:


public function store(

    StoreOrderRequest $request,

    CreateOrder $script

) {

    $order = $script->handle(

        userId: auth()->id(),

        productId: $request->integer('product_id'),

        quantity: $request->integer('quantity')

    );



    return response()->json([

        'data' => $order,

    ]);

}

در این حالت HTTP concern از Business/Application Logic جدا شده است.


# هر Transaction Script باید چه مسئولیتی داشته باشد؟

بهتر است هر Script یک Use Case مشخص داشته باشد.

مثلاً:


CreateOrder

CancelOrder

PayOrder

RefundOrder

RegisterUser

ChangePassword

PublishProduct

ArchiveProduct

به جای:


EverythingService

هر کلاس بهتر است هدف مشخصی داشته باشد.

مثلاً:


final class CancelOrder

{

    public function handle(int $orderId): void

    {

        // ...

    }

}

و:


final class PayOrder

{

    public function handle(int $orderId): void

    {

        // ...

    }

}

این ساختار باعث می‌شود نام کلاس دقیقاً Use Case را توضیح دهد.


# Transaction Script و Service Layer چه تفاوتی دارند؟

این دو الگو به‌شدت به یکدیگر نزدیک هستند و گاهی در پروژه‌های واقعی با یک ساختار مشترک پیاده می‌شوند.

اما از نظر مفهومی تفاوت دارند.

Transaction Script

تمرکز:


یک عملیات / تراکنش

مثلاً:


CreateOrder

تمام منطق لازم برای این عملیات در یک Script قرار می‌گیرد.


Service Layer

تمرکز:


مرز Application / Use Cases

Service Layer می‌تواند مجموعه‌ای از عملیات را سازمان‌دهی کند.

مثلاً:


OrderService

├── create()

├── cancel()

├── pay()

└── refund()

در حالی که یک طراحی مبتنی بر Transaction Script می‌تواند داشته باشد:


CreateOrder

CancelOrder

PayOrder

RefundOrder

Fowler هر دو Transaction Script و Service Layer را در مجموعه الگوهای Domain Logic کتاب Patterns of Enterprise Application Architecture قرار می‌دهد، اما نقش آن‌ها یکی نیست.


# Transaction Script و Domain Model چه تفاوتی دارند؟

این یکی از مهم‌ترین تفاوت‌هاست.

در Transaction Script:


Business Logic

      ↓

Procedures / Scripts

اما در Domain Model:


Business Logic

      ↓

Domain Objects

فرض کنید قانون داریم:


سفارش پرداخت‌شده قابل لغو نیست.

در Transaction Script ممکن است بنویسیم:


if ($order->status === 'paid') {

    throw new RuntimeException(

        'Paid order cannot be cancelled.'

    );

}

اما در Domain Model ممکن است خود Entity رفتار را کنترل کند:


$order->cancel();

و خود Order تصمیم بگیرد آیا لغو مجاز است یا خیر.


# Transaction Script و Active Record چه تفاوتی دارند؟

Active Record بیشتر روی ارتباط Object با رکورد دیتابیس تمرکز دارد.

مثلاً:


$order->save();

در Active Record، Object معمولاً عملیات Persistence مرتبط با خودش را نیز ارائه می‌کند.

اما Transaction Script می‌گوید:


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

یک Script داشته باش.

بنابراین این دو می‌توانند با هم استفاده شوند.

مثلاً:


CreateOrder

     ↓

Order Model

     ↓

save()

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


Transaction Script

→ Workflow



Active Record

→ Data + Persistence


# یک مثال واقعی: سیستم فروشگاهی

فرض کنید فروشگاه ما این Use Caseها را دارد:


CreateOrder

CancelOrder

PayOrder

RefundOrder

با Transaction Script می‌توانیم:


app/

└── Actions/

    └── Orders/

        ├── CreateOrder.php

        ├── CancelOrder.php

        ├── PayOrder.php

        └── RefundOrder.php

هر کلاس یک عملیات مشخص را انجام می‌دهد.

مثلاً:


final class CancelOrder

{

    public function handle(

        Order $order

    ): void {

        if ($order->status === 'paid') {

            throw new RuntimeException(

                'Paid order cannot be cancelled.'

            );

        }



        $order->update([

            'status' => 'cancelled',

        ]);

    }

}


# مزایای Transaction Script Pattern

۱. سادگی

Transaction Script یکی از ساده‌ترین روش‌ها برای سازمان‌دهی Business Logic است.

برای پروژه‌هایی که Domain Logic پیچیده‌ای ندارند، این سادگی می‌تواند مزیت مهمی باشد. Fowler نیز Transaction Script را برای موقعیت‌هایی توصیف می‌کند که منطق هر درخواست را می‌توان به‌صورت یک رویه مستقل سازمان‌دهی کرد.


۲. یادگیری آسان

برای توسعه‌دهنده‌ای که تازه با معماری نرم‌افزار آشنا شده، درک:


CreateOrder

    ↓

Load Data

    ↓

Validate

    ↓

Process

    ↓

Save

آسان‌تر از یک Domain Model پیچیده است.


۳. مناسب برای CRUD

در سیستم‌هایی که بیشتر عملیات آن‌ها CRUD است، Transaction Script می‌تواند انتخاب ساده‌ای باشد.

مثلاً:


Create Product

Update Product

Delete Product

Publish Product


۴. مناسب برای پروژه‌های کوچک

در پروژه کوچک، ساختن Entityهای متعدد، Value Objectها و Domain Serviceهای زیاد ممکن است پیچیدگی غیرضروری ایجاد کند.

Transaction Script اجازه می‌دهد با ساختار ساده‌تری پروژه را پیاده کنیم.


۵. مشخص بودن Use Case

اگر نام کلاس:


CreateOrder

باشد، دقیقاً می‌دانیم این کلاس برای چه کاری است.


# معایب Transaction Script Pattern

Transaction Script نیز با رشد پیچیدگی Domain محدودیت‌هایی پیدا می‌کند.

۱. تکرار Business Logic

فرض کنید سه Script مختلف یک قانون مشترک داشته باشند:


CreateOrder

RenewSubscription

UpgradeSubscription

و هر سه نیاز به بررسی اعتبار حساب داشته باشند.

ممکن است این قانون در سه Script تکرار شود.

در چنین شرایطی باید به استخراج منطق مشترک فکر کنیم.


۲. بزرگ شدن Scriptها

مثلاً:


final class CreateOrder

{

    public function handle(...)

    {

        // 500 lines

    }

}

اگر یک Script بیش از حد بزرگ شود، نگهداری آن دشوار خواهد شد.


۳. وابستگی زیاد به دیتابیس

Transaction Scriptها در برخی پیاده‌سازی‌ها مستقیماً با دیتابیس کار می‌کنند.

مثلاً:


$db->query(...);

برای پروژه ساده ممکن است مشکلی ایجاد نکند.

اما در پروژه پیچیده‌تر می‌توان Database Access را جدا کرد.


۴. دشوار شدن مدیریت قوانین پیچیده

وقتی Business Ruleها زیاد و وابسته به یکدیگر می‌شوند، نگه داشتن همه چیز در Scriptها می‌تواند باعث پیچیدگی شود.

در این مرحله Domain Model یا سایر الگوهای Domain Logic ممکن است مناسب‌تر باشند.


# چه زمانی از Transaction Script استفاده کنیم؟

Transaction Script برای شرایطی مانند این‌ها مناسب است:

  • پروژه کوچک یا متوسط است.
  • Business Logic ساده است.
  • بیشتر عملیات CRUD هستند.
  • Use Caseها مشخص و مستقل هستند.
  • قوانین پیچیده Domain نداریم.
  • سرعت توسعه مهم است.
  • نمی‌خواهیم معماری بیش از نیاز پیچیده شود.

برای مثال:


Admin Panel

CRM ساده

Blog

CMS

پنل مدیریت محصولات

سیستم گزارش‌گیری

CRUD API

می‌توانند کاندیدهای خوبی باشند.


# چه زمانی Transaction Script انتخاب مناسبی نیست؟

اگر Domain بسیار پیچیده است، Transaction Script ممکن است به‌مرور مشکل‌ساز شود.

مثلاً:


سیستم قیمت‌گذاری پیچیده

سیستم مالی

سیستم بیمه

سیستم زمان‌بندی پیچیده

سیستم قوانین تخفیف چندلایه

اگر قوانین زیادی بین Objectها وجود داشته باشند، ممکن است Domain Model گزینه مناسب‌تری باشد.

نکته مهم این است که Transaction Script به‌خودی‌خود «بد» یا «قدیمی» نیست؛ انتخاب Pattern باید بر اساس پیچیدگی واقعی Domain و Trade-offهای پروژه انجام شود. مستندات DesignPatternsPHP نیز بر توجه به زمان و Trade-offهای هر Pattern تأکید می‌کند.


# Transaction Script برای پروژه CRUD

فرض کنید یک سیستم مدیریت محصولات داریم.

عملیات:


Create Product

Update Product

Delete Product

می‌توانیم داشته باشیم:


CreateProduct

UpdateProduct

DeleteProduct

مثلاً:


final class CreateProduct

{

    public function handle(

        string $name,

        int $price,

        int $stock

    ): Product {

        return Product::create([

            'name' => $name,

            'price' => $price,

            'stock' => $stock,

        ]);

    }

}

این ساختار ساده و قابل فهم است.

لازم نیست برای چنین CRUD ساده‌ای حتماً یک معماری Domain-Driven Design پیچیده ایجاد کنیم.


# Transaction Script و Repository

می‌توان Transaction Script را با Repository نیز ترکیب کرد.

مثلاً:


final class CreateOrder

{

    public function __construct(

        private OrderRepository $orders,

        private ProductRepository $products

    ) {

    }



    public function handle(

        int $userId,

        int $productId,

        int $quantity

    ): Order {

        $product = $this->products

            ->findById($productId);



        if ($product->stock < $quantity) {

            throw new RuntimeException(

                'موجودی کافی نیست.'

            );

        }



        return $this->orders->create([

            'user_id' => $userId,

            'product_id' => $productId,

            'quantity' => $quantity,

        ]);

    }

}

در اینجا:


Transaction Script

        ↓

Repository

        ↓

Database

داریم.

Repository مسئول Persistence است و Script مسئول Workflow.


# Transaction Script و Specification

این دو Pattern نیز می‌توانند کنار هم استفاده شوند.

مثلاً:


CreateOrder

      ↓

EligibleCustomerSpecification

      ↓

Repository

      ↓

Database

در Script:


if (

    !$eligibleCustomer

        ->isSatisfiedBy($user)

) {

    throw new RuntimeException(

        'Customer is not eligible.'

    );

}

در این حالت:


Transaction Script

→ اجرای Use Case



Specification

→ بررسی Business Rule

این ترکیب به‌خصوص وقتی قوانین قابل استفاده مجدد هستند، مفید است.


# Transaction Script و Service Layer در Laravel

در Laravel ممکن است این دو معماری ظاهری بسیار مشابهی داشته باشند.

مثلاً:


app/Services/OrderService.php

با:


public function create(...)

{

    // ...

}

می‌تواند عملاً Transaction Scriptها را در یک Service Class سازمان‌دهی کند.

یا:


app/Actions/Orders/CreateOrder.php

می‌تواند فقط یک Transaction Script داشته باشد.

بنابراین تفاوت اصلی همیشه در نام فایل نیست.

مهم این است که بدانیم:


Service Layer

→ یک مرز Application برای Use Caseها



Transaction Script

→ سازمان‌دهی منطق هر تراکنش به‌صورت Procedure/Script

در برخی معماری‌ها این دو حتی می‌توانند با هم ترکیب شوند.


# Transaction Script و تست PHPUnit

Transaction Scriptها معمولاً به‌راحتی قابل تست هستند، مخصوصاً اگر Dependencyهای خود را از بیرون دریافت کنند.

مثلاً:


final class CreateOrder

{

    public function __construct(

        private OrderRepository $orders

    ) {

    }



    public function handle(

        int $userId,

        int $productId,

        int $quantity

    ): Order {

        // ...

    }

}

در تست می‌توان Repository را Mock کرد:


$repository = $this->createMock(

    OrderRepository::class

);

و سپس بررسی کرد که Script رفتار مورد انتظار را دارد.

اگر Script مستقیماً به DB، Request و چند سرویس خارجی وابسته باشد، تست آن دشوارتر می‌شود.


# اشتباهات رایج در استفاده از Transaction Script

اشتباه اول: ساخت Scriptهای چندصد خطی

اگر:


CreateOrder.php

به چندصد خط رسید، باید بررسی کنیم آیا بعضی قوانین باید به Domain Object، Specification یا سرویس‌های مستقل منتقل شوند.


اشتباه دوم: قرار دادن HTTP Logic در Script

بهتر است Script به:


Request

Response

Redirect

وابسته نباشد.

مثلاً این کار مناسب نیست:


final class CreateOrder

{

    public function handle(Request $request)

    {

        return redirect(...);

    }

}

بهتر است Script نتیجه Domain/Application را برگرداند:


$order = $script->handle(...);

و Controller Response را بسازد.


اشتباه سوم: یکی کردن Transaction Script و Database Transaction

این دو مفهوم متفاوت هستند:


Transaction Script

→ الگوی سازمان‌دهی Business Logic



Database Transaction

→ مکانیزم Atomicity دیتابیس

اما می‌توانند با هم استفاده شوند.


اشتباه چهارم: استفاده از Pattern فقط به خاطر نام آن

اگر یک CRUD ساده داریم، نیازی نیست ده‌ها Abstraction ایجاد کنیم.

مهم‌تر از اسم Pattern این است که ساختار ایجادشده واقعاً مشکل پروژه را حل کند. منابع مربوط به DesignPatternsPHP نیز بر انتخاب Pattern بر اساس دلیل استفاده و Trade-offهای آن تأکید می‌کنند.


# ساختار پیشنهادی Transaction Script در Laravel

برای یک پروژه متوسط می‌توانیم چنین ساختاری داشته باشیم:


app/

├── Actions/

│   ├── Orders/

│   │   ├── CreateOrder.php

│   │   ├── CancelOrder.php

│   │   ├── PayOrder.php

│   │   └── RefundOrder.php

│   │

│   └── Products/

│       ├── CreateProduct.php

│       ├── UpdateProduct.php

│       └── DeleteProduct.php

│

├── Models/

│   ├── Order.php

│   └── Product.php

│

├── Repositories/

│   ├── OrderRepository.php

│   └── ProductRepository.php

│

└── Http/

    ├── Controllers/

    └── Requests/

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


Controller

    ↓

Action / Transaction Script

    ↓

Repository / Model

    ↓

Database

قرار می‌گیرد.


# آیا Transaction Script فقط برای PHP است؟

خیر.

Transaction Script یک الگوی عمومی برای سازمان‌دهی Business Logic است و وابسته به PHP نیست.

اما در PHP، Laravel، Symfony و سایر Backend Frameworkها می‌توان آن را با:


Service

Action

Command

Use Case

Procedure

پیاده‌سازی کرد.

نام کلاس مهم‌ترین بخش نیست؛ ساختار و مسئولیت آن مهم‌تر است.


# چه زمانی از Transaction Script به Domain Model برویم؟

فرض کنید پروژه را با Transaction Script شروع کرده‌ایم.

ابتدا:


CreateOrder

ساده است.

اما بعداً قوانین اضافه می‌شوند:


Order

├── Discount Rules

├── Payment Rules

├── Refund Rules

├── Shipping Rules

├── Inventory Rules

└── Cancellation Rules

اگر تمام این قوانین داخل Scriptها قرار بگیرند، احتمالاً زمان بازنگری معماری فرا رسیده است.

در این مرحله ممکن است بخشی از رفتار به Domain Model منتقل شود:


$order->cancel();

$order->pay();

$order->refund();

به جای اینکه همه قوانین در Script باشند.

این همان مسیری است که معمولاً با افزایش پیچیدگی Domain، از Transaction Script ساده به مدل‌های غنی‌تر Domain حرکت می‌کند. منابع معماری نیز Transaction Script را بیشتر برای منطق ساده و Domain Model را برای قوانین پیچیده‌تر معرفی می‌کنند.


# جمع‌بندی

Transaction Script Pattern در PHP روشی ساده برای سازمان‌دهی Business Logic بر اساس Use Caseها یا تراکنش‌های مستقل است.

ایده اصلی:


یک عملیات

   ↓

یک Script

   ↓

خواندن داده

   ↓

اجرای Business Logic

   ↓

ذخیره نتیجه

مثلاً:


CreateOrder

CancelOrder

PayOrder

RefundOrder

هرکدام می‌توانند Transaction Script مستقل خودشان را داشته باشند.

مزیت اصلی این Pattern سادگی و قابل فهم بودن است.

برای پروژه‌های ساده و CRUDمحور می‌تواند انتخاب مناسبی باشد؛ اما با پیچیده شدن قوانین Domain، ممکن است تکرار Business Logic و بزرگ شدن Scriptها به مشکل تبدیل شود.

در چنین شرایطی می‌توان از الگوهایی مانند:


Domain Model

Specification

Service Layer

Repository

Data Mapper

برای سازمان‌دهی بهتر سیستم استفاده کرد.

بنابراین سؤال اصلی این نیست که:

> «آیا Transaction Script Pattern خوب است یا بد؟»

بلکه باید پرسید:

> «پیچیدگی واقعی Domain من چقدر است و کدام روش با این پیچیدگی تناسب دارد؟»

این دیدگاه با توصیه کلی منابع الگوهای معماری نیز همخوان است که Patternها را باید بر اساس مسئله و Trade-offهای واقعی پروژه انتخاب کرد، نه صرفاً به دلیل شناخته‌شدن یک Pattern.


# سوالات متداول

Transaction Script Pattern در PHP چیست؟

Transaction Script الگویی است که Business Logic را به‌صورت Procedureهایی سازمان‌دهی می‌کند که هرکدام یک درخواست یا Use Case مشخص را اجرا می‌کنند.

آیا Transaction Script همان Database Transaction است؟

خیر.

Transaction Script یک الگوی سازمان‌دهی منطق برنامه است، در حالی که Database Transaction مکانیزمی برای اجرای Atomic عملیات دیتابیس است.

آیا Transaction Script در Laravel قابل استفاده است؟

بله. می‌توان آن را در قالب Service، Action یا کلاس‌های Use Case در Laravel پیاده‌سازی کرد.

تفاوت Transaction Script و Service Layer چیست؟

Transaction Script روی سازمان‌دهی منطق یک تراکنش یا Use Case تمرکز دارد، در حالی که Service Layer یک مرز Application برای سازمان‌دهی عملیات و Use Caseهای سیستم است.

تفاوت Transaction Script و Domain Model چیست؟

Transaction Script منطق را بیشتر در Procedureها قرار می‌دهد، در حالی که Domain Model رفتار و قوانین را در Objectهای Domain قرار می‌دهد.

آیا Transaction Script برای پروژه‌های CRUD مناسب است؟

بله. برای پروژه‌هایی که Business Logic ساده و عملیات آن‌ها عمدتاً CRUD هستند، این الگو می‌تواند ساختار ساده و قابل فهمی ایجاد کند.

آیا Transaction Script با Active Record قابل استفاده است؟

بله. Transaction Script می‌تواند Workflow را مدیریت کند و Active Record نیز مسئولیت Data و Persistence مربوط به Entity را بر عهده داشته باشد.

آیا هر Transaction Script باید یک کلاس جدا داشته باشد؟

خیر. می‌توان چند Script مرتبط را در یک کلاس قرار داد یا برای هر Script یک کلاس جدا ساخت. هر دو رویکرد در منابع مربوط به Transaction Script مطرح شده‌اند.

چه زمانی Transaction Script مناسب نیست؟

وقتی Business Ruleها پیچیده، مشترک و به‌شدت وابسته به رفتار چند Entity هستند، نگه داشتن همه منطق در Scriptها می‌تواند باعث پیچیدگی شود و استفاده از Domain Model یا سایر الگوهای Domain Logic ممکن است مناسب‌تر باشد.

آیا Transaction Script یک Design Pattern از نوع GoF است؟

خیر. Transaction Script در مجموعه Patterns of Enterprise Application Architecture از Martin Fowler قرار دارد و نباید آن را با دسته‌بندی ۲۳ الگوی کلاسیک GoF یکی دانست.

نظرات (0)

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

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

مقالات مرتبط

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

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

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

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

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

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

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

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

۲۲ دقیقه