در پروژههای PHP، مخصوصاً پروژههای Laravel، معمولاً کار از یک Controller ساده شروع میشود.
مثلاً برای ثبت سفارش ممکن است ابتدا چنین کدی داشته باشیم:
public function store(Request $request)
{
$product = Product::findOrFail($request->product_id);
if ($product->stock < $request->quantity) {
return back()->with('error', 'موجودی کافی نیست.');
}
$total = $product->price * $request->quantity;
$order = Order::create([
'user_id' => auth()->id(),
'total' => $total,
]);
$product->decrement('stock', $request->quantity);
return redirect()
->route('orders.show', $order);
}
در یک پروژه کوچک شاید این کد مشکلی ایجاد نکند.
اما با بزرگتر شدن پروژه، فرآیند ثبت سفارش میتواند شامل موارد بیشتری شود:
بررسی موجودی
↓
محاسبه قیمت
↓
اعمال تخفیف
↓
ایجاد سفارش
↓
کاهش موجودی
↓
ثبت تراکنش
↓
پرداخت
↓
ارسال Notification
↓
ثبت Log
اگر همه این منطق داخل Controller قرار بگیرد، Controller بهمرور بسیار بزرگ و سختنگهداری میشود.
اینجاست که مفهوم Service Layer Pattern اهمیت پیدا میکند.
Service Layer یک مرز مشخص برای عملیات سطح برنامه ایجاد میکند و میتواند چند جزء Domain، Repository و سایر سرویسها را برای اجرای یک Use Case هماهنگ کند.
# Service Layer Pattern در PHP چیست؟
Service Layer Pattern یک الگوی معماری است که عملیات و منطق مربوط به Use Caseهای برنامه را در یک لایه یا مجموعهای از Serviceها قرار میدهد.
هدف اصلی این است که Controller صرفاً وظیفه دریافت درخواست و برگرداندن پاسخ را انجام دهد و منطق اجرای فرآیند اصلی در Service قرار بگیرد.
به صورت ساده:
Request
↓
Controller
↓
Service Layer
↓
Domain / Repository / External Services
↓
Database
برای مثال به جای اینکه Controller خودش تمام مراحل ثبت سفارش را انجام دهد:
$orderService->createOrder($user, $data);
را صدا میزند.
این ایده باعث میشود یک عملیات مشخص برنامه یک نقطه ورود مشخص داشته باشد و بتوان آن را از چند Client یا Workflow مختلف استفاده کرد.
# Service Layer چه مشکلی را حل میکند؟
یکی از مشکلات رایج در پروژههای PHP، پراکنده شدن Application Logic است.
مثلاً فرض کنید ثبت سفارش از سه مسیر انجام میشود:
Web Controller
API Controller
Console Command
اگر منطق ثبت سفارش داخل Web Controller باشد، برای API باید همان منطق را دوباره بنویسیم.
مثلاً:
Web
└── create order logic
API
└── create order logic
Command
└── create order logic
این ساختار باعث Duplicate Code میشود.
اما با Service Layer:
Web Controller ──┐
│
API Controller ───┼──> OrderService
│
Console Command ──┘
هر سه مسیر از یک منطق مشترک استفاده میکنند.
Service Layer به همین دلیل میتواند یک نقطه ورود مشترک برای عملیات برنامه فراهم کند.
# Fat Controller چیست؟
اصطلاح Fat Controller به Controllerهایی گفته میشود که بیش از حد مسئولیت دارند.
برای مثال:
public function store(Request $request)
{
// Validate
// Find product
// Check stock
// Calculate price
// Apply discount
// Create order
// Create order items
// Update inventory
// Charge payment
// Send notification
// Log operation
}
ممکن است این متد به صدها خط برسد.
مشکل فقط تعداد خطوط نیست.
مشکل اصلی این است که Controller به بخشهای مختلف سیستم وابسته شده است.
مثلاً:
Controller
├── Database
├── Payment Gateway
├── Inventory
├── Notification
├── Discount
└── Logging
در نتیجه تغییر هر بخش میتواند Controller را تحت تأثیر قرار دهد.
# Thin Controller چیست؟
هدف Service Layer این نیست که Controller را کاملاً خالی کنیم.
Controller باید همچنان کارهای مربوط به Presentation Layer را انجام دهد.
مثلاً:
public function store(
StoreOrderRequest $request,
OrderService $service
) {
$order = $service->create(
auth()->user(),
$request->validated()
);
return redirect()
->route('orders.show', $order);
}
Controller اینجا:
- Request را دریافت میکند.
- داده را Validate میکند.
- Service را صدا میزند.
- Response را برمیگرداند.
اما فرآیند اصلی ثبت سفارش در Service قرار دارد.
# Application Logic و Domain Logic چه تفاوتی دارند؟
این قسمت بسیار مهم است.
Service Layer الزاماً محل قرار دادن تمام Business Logic نیست.
باید بین Application Logic و Domain Logic تفاوت قائل شویم.
Application Logic
Application Logic مشخص میکند یک Use Case چگونه اجرا شود.
مثلاً:
Create Order
Cancel Order
Complete Payment
Register User
Publish Product
Service میتواند این فرآیند را هماهنگ کند.
Domain Logic
Domain Logic مربوط به قوانین خود Domain است.
مثلاً:
سفارش لغوشده دوباره قابل پرداخت نیست.
موجودی محصول نمیتواند منفی شود.
تخفیف VIP فقط برای کاربران واجد شرایط فعال است.
این قوانین میتوانند در Domain Model، Value Object، Specification یا Domain Service قرار بگیرند.
پس Service Layer نباید به یک کلاس غولپیکر تبدیل شود که تمام منطق پروژه را در خود نگه میدارد.
# ساختار کلی Service Layer
یک معماری ساده میتواند چنین باشد:
Controller
↓
Service
↓
Repository
↓
Database
اما در پروژههای بزرگتر:
Controller
↓
Application Service
↓
Domain
├── Entity
├── Value Object
├── Specification
└── Domain Service
↓
Repository
↓
Database
در کنار آن ممکن است:
Payment Gateway
Notification
Queue
External API
نیز توسط Application Service هماهنگ شوند.
# پیادهسازی Service Layer در PHP خام
قبل از رفتن سراغ Laravel، یک مثال ساده PHP بسازیم.
فرض کنید کلاس Product داریم:
<?php
declare(strict_types=1);
final class Product
{
public function __construct(
public int $id,
public string $name,
public int $price,
public int $stock
) {
}
}
یک Repository ساده:
<?php
declare(strict_types=1);
interface ProductRepository
{
public function findById(int $id): Product;
public function save(Product $product): void;
}
حالا Service:
<?php
declare(strict_types=1);
final class OrderService
{
public function __construct(
private ProductRepository $products
) {
}
public function createOrder(
int $productId,
int $quantity
): array {
$product = $this->products->findById($productId);
if ($product->stock < $quantity) {
throw new RuntimeException(
'موجودی محصول کافی نیست.'
);
}
$total = $product->price * $quantity;
$product->stock -= $quantity;
$this->products->save($product);
return [
'product_id' => $product->id,
'quantity' => $quantity,
'total' => $total,
];
}
}
حالا Controller یا هر Client دیگری فقط Service را صدا میزند:
$order = $orderService->createOrder(
productId: 10,
quantity: 2
);
# چرا Dependency Injection در Service Layer مهم است؟
یکی از اصول مهم در طراحی Service، استفاده از Dependency Injection است.
مثلاً به جای اینکه داخل Service بنویسیم:
$this->repository = new MysqlProductRepository();
بهتر است Dependency را از بیرون دریافت کنیم:
public function __construct(
private ProductRepository $products
) {
}
حالا Service به Interface وابسته است:
OrderService
↓
ProductRepository
↓
Implementation
نه اینکه مستقیماً به یک Implementation خاص وابسته باشد.
این ساختار تست Service را نیز سادهتر میکند.
# استفاده از Repository در Service Layer
یکی از ترکیبهای رایج:
Controller
↓
Service
↓
Repository
↓
Database
است.
برای مثال:
final class OrderService
{
public function __construct(
private ProductRepository $products,
private OrderRepository $orders
) {
}
public function create(
int $productId,
int $quantity
): Order {
$product = $this->products->findById($productId);
// Business/Application logic...
return $this->orders->create([
'product_id' => $product->id,
'quantity' => $quantity,
]);
}
}
در این معماری:
- Service فرآیند را هماهنگ میکند.
- Repository مسئول Persistence است.
- Domain Object قوانین Domain را نگه میدارد.
این موضوع با الگوهایی مثل Repository و Data Mapper ارتباط نزدیکی دارد. Data Mapper بهطور مشخص وظیفه انتقال داده بین Representation داخل حافظه و Data Store را بر عهده میگیرد و آنها را از یکدیگر مستقل نگه میدارد.
# مدیریت Transaction در Service Layer
یکی از کاربردهای مهم Service Layer مدیریت یک فرآیند چندمرحلهای است.
فرض کنید ثبت سفارش شامل این عملیات باشد:
Create Order
↓
Create Order Items
↓
Decrease Stock
↓
Create Payment Transaction
اگر مرحله سوم موفق شود ولی مرحله چهارم شکست بخورد، ممکن است دادهها ناسازگار شوند.
بنابراین میتوانیم Transaction را در مرز Use Case قرار دهیم.
در PHP خام، مفهوم میتواند به شکل زیر باشد:
$db->beginTransaction();
try {
$order = $orderRepository->create($data);
$inventory->decrease(
$productId,
$quantity
);
$payment->createTransaction(
$order->id,
$order->total
);
$db->commit();
return $order;
} catch (Throwable $e) {
$db->rollBack();
throw $e;
}
در چنین شرایطی Service نقطه مناسبی برای هماهنگ کردن این عملیات است، چون Service مرز اجرای Use Case را مشخص میکند.
# پیادهسازی Service Layer در Laravel
حالا به Laravel برسیم.
فرض کنید ساختار پروژه:
app/
├── Http/
│ └── Controllers/
│ └── OrderController.php
│
├── Services/
│ └── OrderService.php
│
├── Models/
│ ├── Order.php
│ └── Product.php
│
└── Repositories/
└── OrderRepository.php
میتوانیم Service را ایجاد کنیم:
app/Services/OrderService.php
محتوا:
<?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;
});
}
}
این Service یک Use Case مشخص دارد:
Create Order
# استفاده از Service در Controller
Controller:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreOrderRequest;
use App\Services\OrderService;
final class OrderController extends Controller
{
public function store(
StoreOrderRequest $request,
OrderService $service
) {
$order = $service->create(
userId: auth()->id(),
productId: $request->integer('product_id'),
quantity: $request->integer('quantity')
);
return redirect()
->route('orders.show', $order);
}
}
در اینجا Controller بسیار ساده شده است.
منطق اصلی:
Find Product
Check Stock
Calculate Total
Create Order
Decrease Stock
Transaction
داخل Service قرار گرفته است.
# استفاده از Service Container در Laravel
Laravel دارای Service Container است و میتواند Dependencyهای کلاسها را Resolve کند.
برای مثال اگر Service ما:
final class OrderService
{
public function __construct(
private OrderRepository $orders
) {
}
}
باشد، میتوان Dependency را از طریق Constructor دریافت کرد.
در Controller نیز:
public function __construct(
private OrderService $service
) {
}
یا در Method:
public function store(
StoreOrderRequest $request,
OrderService $service
) {
// ...
}
میتوان Service را دریافت کرد.
این روش باعث میشود وابستگیهای کلاسها صریح باشند و برای تست نیز مناسبتر باشد.
# Service Layer و API
یکی از مزیتهای مهم Service Layer زمانی مشخص میشود که یک Use Case چند Client داشته باشد.
مثلاً:
Web
↓
OrderService
API
↓
OrderService
Admin Panel
↓
OrderService
Queue Job
↓
OrderService
Console Command
↓
OrderService
برای مثال API Controller:
public function store(
StoreOrderRequest $request,
OrderService $service
) {
$order = $service->create(
auth()->id(),
$request->integer('product_id'),
$request->integer('quantity')
);
return response()->json([
'data' => $order,
]);
}
همان Service میتواند توسط Web Controller نیز استفاده شود.
# استفاده از Service Layer در Job
فرض کنید میخواهیم سفارشهای معلق را پردازش کنیم.
Job:
final class ProcessPendingOrderJob implements ShouldQueue
{
public function __construct(
private int $orderId
) {
}
public function handle(
OrderService $service
): void {
$service->process(
$this->orderId
);
}
}
در این حالت Business Workflow داخل Job نوشته نشده است.
Job فقط وظیفه اجرای کار پسزمینه را دارد.
# استفاده از Service Layer در Artisan Command
همین Service را میتوان از Command نیز استفاده کرد:
final class ProcessOrdersCommand extends Command
{
protected $signature = 'orders:process';
public function handle(
OrderService $service
): int {
$service->processPendingOrders();
$this->info(
'Orders processed successfully.'
);
return self::SUCCESS;
}
}
بنابراین:
Controller
Job
Command
API
همگی میتوانند یک Application Service را صدا بزنند.
# Service Layer و Eloquent
یک سؤال مهم:
آیا Service Layer باید Eloquent را کنار بگذارد؟
خیر.
در Laravel کاملاً میتوان Serviceای داشت که مستقیماً با Eloquent کار کند.
مثلاً:
final class ProductService
{
public function create(array $data): Product
{
return Product::create($data);
}
}
برای پروژههای ساده این روش کاملاً قابل استفاده است.
مشکل زمانی ایجاد میشود که Serviceها به کلاسهای بسیار بزرگ تبدیل شوند:
ProductService
├── create
├── update
├── delete
├── import
├── export
├── sync
├── calculate
├── notify
├── publish
├── archive
└── ...
در پروژههای بزرگ بهتر است Serviceها بر اساس Use Case یا مسئولیت مشخصتر تقسیم شوند.
# Service Layer و Repository چه تفاوتی دارند؟
این دو Pattern اغلب با هم اشتباه گرفته میشوند.
Repository
Repository بیشتر با دسترسی به داده سروکار دارد.
مثلاً:
$product = $repository->findById(10);
یا:
$repository->save($product);
Service Layer
Service یک عملیات یا Use Case را هماهنگ میکند.
مثلاً:
$orderService->createOrder(...);
و ممکن است درون آن:
Repository
Payment
Inventory
Notification
Domain Object
Transaction
را هماهنگ کند.
بنابراین:
Repository
→ Data Access
Service
→ Application Workflow / Use Case
# Service Layer و Domain Service چه تفاوتی دارند؟
این دو اصطلاح نیز نباید با یکدیگر اشتباه شوند.
Application Service
معمولاً Use Case برنامه را هماهنگ میکند.
مثلاً:
CreateOrder
CancelOrder
RegisterUser
ProcessPayment
Domain Service
منطق Domainای را نگه میدارد که بهطور طبیعی متعلق به یک Entity خاص نیست.
مثلاً:
CalculateShippingCost
CurrencyExchange
ComplexPricingRule
بنابراین:
Application Service
→ اجرای Use Case
Domain Service
→ Business Rule سطح Domain
این تفکیک در معماریهای Domain-Driven Design اهمیت بیشتری پیدا میکند.
# Service Layer و Facade چه تفاوتی دارند؟
Facade یک Interface ساده برای دسترسی به یک مجموعه پیچیده از عملیات فراهم میکند.
Service Layer بیشتر یک مرز معماری برای Application Operations است.
در Laravel نیز Facadeهای این فریمورک را نباید صرفاً به خاطر وجود کلمه Service با Service Layer یکی بدانیم.
مثلاً:
DB::transaction(...)
یک Facade است.
اما:
$orderService->create(...)
یک Application Service است.
# مزایای Service Layer Pattern
۱. Controllerهای کوچکتر
Controller به جای صدها خط Business Logic، فقط Use Case را اجرا میکند.
۲. استفاده مجدد از منطق
یک Service را میتوان از:
Web
API
Job
Command
استفاده کرد.
۳. تستپذیری بهتر
Service را میتوان مستقل از HTTP Request تست کرد.
۴. جداسازی مسئولیتها
به صورت ساده:
Controller
→ HTTP
Service
→ Application Workflow
Repository
→ Persistence
Domain
→ Business Rules
۵. مدیریت بهتر فرآیندهای چندمرحلهای
Service برای Use Caseهایی که چند عملیات را هماهنگ میکنند، نقطه مناسبی ایجاد میکند.
# معایب Service Layer Pattern
Service Layer نیز مانند هر Pattern دیگری بدون Trade-off نیست. انتخاب Pattern باید بر اساس مسئله واقعی پروژه انجام شود و نباید صرفاً به خاطر نام Pattern از آن استفاده کرد.
۱. افزایش تعداد کلاسها
ممکن است پروژهای داشته باشید که برای هر عملیات یک Service ساخته شده است:
CreateUserService
UpdateUserService
DeleteUserService
GetUserService
اگر این کار بدون نیاز واقعی انجام شود، ساختار پروژه بیش از حد پیچیده خواهد شد.
۲. God Service
یکی از بدترین حالتها:
UserService
که ۳۰۰۰ خط کد دارد.
وجود Service Layer به خودی خود به معنی Clean Architecture نیست.
اگر Service همه چیز را انجام دهد، فقط Fat Controller را به Fat Service تبدیل کردهایم.
۳. Abstraction بیش از حد
گاهی برای یک عملیات ساده چنین ساختاری ساخته میشود:
Controller
↓
Interface
↓
Service
↓
Interface
↓
Repository
↓
Eloquent
در حالی که Use Case بسیار ساده است.
Service Layer باید به اندازه مسئله پیچیدگی داشته باشد، نه بیشتر.
# چه زمانی از Service Layer استفاده کنیم؟
Service Layer زمانی ارزش بیشتری پیدا میکند که:
- Business Workflow پیچیده باشد.
- یک Use Case از چند مرحله تشکیل شده باشد.
- منطق یک عملیات از چند Controller یا Client استفاده شود.
- نیاز به Transaction داشته باشیم.
- Job و Command نیز همان فرآیند را اجرا کنند.
- Integration با سرویسهای خارجی وجود داشته باشد.
- پروژه در حال رشد باشد.
مثلاً:
Create Subscription
ممکن است شامل:
Validate Plan
↓
Calculate Price
↓
Create Subscription
↓
Charge Wallet
↓
Create Transaction
↓
Activate Service
↓
Send Notification
باشد.
این Use Case کاندید بسیار مناسبی برای یک Application Service است.
# چه زمانی Service Layer اضافه است؟
برای یک CRUD بسیار ساده:
public function show(Product $product)
{
return $product;
}
ساختن:
ProductService
صرفاً برای اینکه همهچیز Service داشته باشد، الزاماً ارزشمند نیست.
همچنین:
public function store(Request $request)
{
Product::create(
$request->validated()
);
}
اگر هیچ Workflow یا Business Logic قابل توجهی وجود ندارد، ممکن است Service Layer فقط یک Wrapper اضافی باشد.
هدف معماری نباید افزایش تعداد فایلها باشد.
# اشتباهات رایج در طراحی Service Layer
اشتباه اول: انتقال تمام کد Controller به Service
اگر Controller این بود:
// 500 lines
و فقط همان ۵۰۰ خط را به:
OrderService
منتقل کنیم، مشکل معماری کاملاً حل نشده است.
باید مسئولیتها نیز تفکیک شوند.
اشتباه دوم: Service بسیار بزرگ
اگر Service مربوط به سفارش شامل:
Payment
Shipping
Inventory
Notification
Reporting
User
Discount
باشد، احتمالاً باید آن را به چند Use Case یا Component تقسیم کنیم.
اشتباه سوم: قرار دادن Validation مربوط به HTTP داخل Service
مثلاً:
$request->validate(...)
به Presentation Layer وابسته است.
بهتر است HTTP Validation در Form Request باشد:
StoreOrderRequest
و Service داده معتبرشده را دریافت کند.
اشتباه چهارم: وابستگی شدید به Request
این طراحی:
public function create(Request $request)
Service را به HTTP وابسته میکند.
بهتر است:
public function create(
int $userId,
int $productId,
int $quantity
)
یا DTO دریافت کند.
در این صورت Service را میتوان از API، Job یا Command نیز استفاده کرد.
# استفاده از DTO در Service Layer
در پروژههای بزرگتر میتوان به جای ارسال آرایههای بزرگ از DTO استفاده کرد.
مثلاً:
final readonly class CreateOrderData
{
public function __construct(
public int $userId,
public int $productId,
public int $quantity,
) {
}
}
Service:
final class OrderService
{
public function create(
CreateOrderData $data
): Order {
// ...
}
}
حالا Controller:
$data = new CreateOrderData(
userId: auth()->id(),
productId: $request->integer('product_id'),
quantity: $request->integer('quantity'),
);
$order = $service->create($data);
این روش Contract بین Presentation و Application Layer را واضحتر میکند.
# تست Service Layer با PHPUnit
یکی از مزیتهای Service Layer این است که میتوانیم Use Case را بدون اجرای HTTP تست کنیم.
مثلاً:
<?php
use Tests\TestCase;
final class OrderServiceTest extends TestCase
{
public function test_order_can_be_created(): void
{
$product = Product::factory()->create([
'price' => 100000,
'stock' => 10,
]);
$user = User::factory()->create();
$service = app(OrderService::class);
$order = $service->create(
userId: $user->id,
productId: $product->id,
quantity: 2
);
$this->assertDatabaseHas('orders', [
'id' => $order->id,
'user_id' => $user->id,
]);
$this->assertDatabaseHas('products', [
'id' => $product->id,
'stock' => 8,
]);
}
}
در اینجا به جای اینکه حتماً HTTP Request ایجاد کنیم، مستقیماً Use Case را تست میکنیم.
# ساختار پیشنهادی پروژه Laravel
برای پروژه متوسط:
app/
├── Http/
│ ├── Controllers/
│ └── Requests/
│
├── Models/
│
├── Services/
│ ├── OrderService.php
│ ├── PaymentService.php
│ └── ProductService.php
│
├── Repositories/
│ ├── OrderRepository.php
│ └── ProductRepository.php
│
└── DTOs/
└── CreateOrderData.php
اما برای پروژه بزرگتر میتوان ساختار Domain-oriented داشت:
app/
├── Domain/
│ ├── Order/
│ │ ├── Entities/
│ │ ├── Services/
│ │ ├── Specifications/
│ │ └── Repositories/
│ │
│ └── Product/
│ ├── Entities/
│ ├── Services/
│ └── Repositories/
│
├── Application/
│ ├── Order/
│ │ ├── Services/
│ │ └── DTOs/
│ │
│ └── Product/
│ ├── Services/
│ └── DTOs/
│
└── Infrastructure/
├── Persistence/
└── ExternalServices/
در این مدل، Service Layer میتواند در Application Layer قرار بگیرد و Domain Serviceها نیز در Domain Layer باقی بمانند.
# آیا Laravel خودش Service Layer دارد؟
Laravel به صورت اجباری معماریای با نام Service Layer تعریف نمیکند.
Service Layer بیشتر یک انتخاب معماری در پروژه است که میتواند با امکانات Laravel مانند Dependency Injection و Service Container پیادهسازی شود. منابع مربوط به معماری Laravel نیز Service Layer را معمولاً به عنوان Application-level organization معرفی میکنند، نه یک جزء اجباری از Framework.
بنابراین:
Laravel
≠
Service Layer
بلکه:
Laravel
+
Service Layer Architecture
یک ترکیب رایج برای سازماندهی پروژههای بزرگتر است.
# Service Layer در پروژههای بزرگ
فرض کنید یک سیستم فروشگاهی داریم:
Order
Payment
Inventory
Shipping
Wallet
Notification
ممکن است Serviceهای مشخصی داشته باشیم:
CreateOrderService
CancelOrderService
ProcessPaymentService
RefundPaymentService
ReserveInventoryService
ShipOrderService
هر Service یک Use Case مشخص را مدیریت میکند.
به جای یک Service عظیم:
OrderService
میتوان عملیات را بر اساس رفتار و Use Case تقسیم کرد.
این رویکرد باعث میشود نام کلاس نیز هدف آن را بهتر توضیح دهد.
# Service Layer و معماری Clean Architecture
در معماریهای تمیز، معمولاً Application Layer وظیفه هماهنگکردن Use Caseها را بر عهده دارد.
برای مثال:
Presentation
↓
Application
↓
Domain
↓
Infrastructure
یک Application Service میتواند:
Use Case
↓
Domain Objects
↓
Repository Interface
↓
Infrastructure Implementation
را هماهنگ کند.
این ساختار البته یک الزام برای تمام پروژههای Laravel نیست.
اگر پروژه ساده باشد، چنین معماریای ممکن است بیش از نیاز پروژه پیچیده باشد.
# Service Layer را با Service Container اشتباه نگیریم
این دو مفهوم کاملاً متفاوت هستند.
Service Layer
یک مفهوم معماری است:
Application Logic
Service Container
یک مکانیزم Dependency Resolution در Laravel است.
مثلاً:
app(OrderService::class);
یا:
public function __construct(
OrderService $service
) {
}
Service Container به Laravel کمک میکند Dependencyهای کلاس را Resolve کند.
اما Service Layer مشخص میکند Application Logic کجا قرار بگیرد.
# جمعبندی
Service Layer Pattern در PHP یک الگوی معماری برای سازماندهی عملیات سطح Application و Use Caseهای برنامه است.
ایده اصلی این است که Controllerها مستقیماً تمام مراحل Business Workflow را اجرا نکنند.
به جای:
Controller
├── Validation
├── Database
├── Payment
├── Inventory
├── Notification
└── Transaction
میتوانیم داشته باشیم:
Controller
↓
OrderService
↓
┌────┼──────────┐
↓ ↓ ↓
Domain Repository External API
↓
Database
مهمترین مزیت Service Layer این است که یک عملیات پیچیده میتواند یک نقطه ورود مشخص داشته باشد و از مسیرهای مختلف مانند Web، API، Queue و Command استفاده شود.
اما نباید هر متد ساده را به Service تبدیل کنیم.
اگر Service Layer باعث شود:
Controller
↓
Service
↓
Repository
↓
Model
برای یک عملیات دوخطی ایجاد شود، ممکن است صرفاً پیچیدگی اضافی ایجاد کرده باشیم.
پس اصل مهم این است:
> Service Layer باید پیچیدگی واقعی Application را سازماندهی کند، نه اینکه صرفاً تعداد کلاسهای پروژه را افزایش دهد.
# سوالات متداول
Service Layer Pattern در PHP چیست؟
Service Layer یک الگوی معماری برای قرار دادن و سازماندهی Application Logic و Use Caseهای برنامه در Serviceهای مشخص است.
آیا Service Layer همان Repository است؟
خیر.
Repository بیشتر مسئول دسترسی و Persistence داده است، در حالی که Service Layer معمولاً یک Use Case را هماهنگ میکند.
Repository
→ Data Access
Service
→ Application Workflow
آیا استفاده از Service Layer در Laravel ضروری است؟
خیر. Laravel استفاده از Service Layer را اجباری نمیکند. این یک تصمیم معماری است که بر اساس اندازه و پیچیدگی پروژه گرفته میشود.
آیا Service Layer باید با Eloquent کار کند؟
الزامی نیست. در پروژههای ساده میتواند مستقیماً از Eloquent استفاده کند؛ در معماریهای پیچیدهتر میتوان Persistence را پشت Repository یا Interface قرار داد.
آیا Transaction باید داخل Service Layer باشد؟
برای Use Caseهایی که چند عملیات وابسته را در یک فرآیند انجام میدهند، قرار دادن مرز Transaction در Application Service میتواند انتخاب مناسبی باشد.
تفاوت Service Layer و Domain Service چیست؟
Application Service بیشتر اجرای یک Use Case را هماهنگ میکند، در حالی که Domain Service برای Business Logicای استفاده میشود که بهطور طبیعی متعلق به یک Entity یا Value Object خاص نیست.
آیا Service Layer باعث Clean Architecture میشود؟
خیر. صرف ایجاد Service Class به معنی Clean Architecture نیست. Service Layer تنها یکی از ابزارهای سازماندهی Application است و معماری کلی پروژه به Dependencyها، مرزهای لایهها و نحوه قرارگیری Domain و Infrastructure نیز بستگی دارد.
آیا برای هر Model باید Service بسازیم؟
خیر. ساختن Service برای هر Model یک قانون اجباری نیست. Service بهتر است زمانی ایجاد شود که یک Use Case یا Workflow دارای منطق و هماهنگی قابل توجه باشد.
آیا Service Layer برای پروژههای کوچک مناسب است؟
ممکن است، اما همیشه لازم نیست. برای CRUDهای ساده، استفاده مستقیم از Controller، Form Request و Eloquent میتواند ساختار سادهتری ایجاد کند.
Service Layer در Laravel کجا قرار میگیرد؟
در پروژههای ساده میتوان Serviceها را در:
app/Services
قرار داد.
در پروژههای بزرگتر، میتوان Serviceها را بر اساس Domain و Application Layer سازماندهی کرد.





