در بسیاری از پروژههای 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 یکی دانست.





