برنامه نویس فان | Fun Developer یک آدم ساده که عاشق برنامه نویسی و کد زدنه :) تلاش میکنه تا به بقیه کمک کنه. توسعه دهنده هسته لاراول و فضای اوپن سورس. فاندر پرانتز و کد نیوز.

معماری نرمافزار یکی از مهمترین بخشهای توسعهی اپلیکیشنهای مقیاسپذیر و قابل نگهداری است. در دنیای امروز، که پروژهها روزبهروز پیچیدهتر میشوند، استفاده از معماریهای سنتی مانند لایهای (Layered) یا MVC بهتنهایی پاسخگوی نیازهای ما نیست. همین موضوع باعث شده که الگوهایی مانند Hexagonal Architecture یا همان معماری ششضلعی (Ports and Adapters) بیش از پیش مورد توجه قرار بگیرند.
معماری Hexagonal با هدف جداسازی منطق اصلی کسبوکار از جزئیات زیرساختی طراحی شده است. در این رویکرد، اپلیکیشن ما بهگونهای سازماندهی میشود که هستهی اصلی (Domain) هیچ وابستگی مستقیمی به لایههای خارجی مانند دیتابیس، فریمورک، یا سرویسهای جانبی نداشته باشد. این موضوع باعث میشود تستپذیری، انعطافپذیری و امکان جایگزینی تکنولوژیها در طول زمان بسیار آسانتر شود.
در دنیای PHP و مخصوصاً فریمورک لاراول، پیادهسازی معماری Hexagonal میتواند کمک بزرگی در مدیریت پروژههای بزرگ باشد. اگرچه لاراول بهطور پیشفرض از معماری MVC استفاده میکند، اما با کمی تغییر در ساختار پوشهها و رعایت اصول Ports و Adapters میتوان آن را به معماری ششضلعی نزدیک کرد.
در این مقاله قصد داریم بهصورت گامبهگام بررسی کنیم که معماری Hexagonal چیست، چرا اهمیت دارد و چگونه میتوانیم آن را در یک پروژهی لاراولی پیادهسازی کنیم.
به زبان ساده:
-
دامنه (Domain): قلب نرمافزار، شامل قوانین و منطق اصلی.
-
Port (پورت): قراردادها یا اینترفیسهایی که دامنه برای ارتباط بیرونی تعریف میکند.
-
Adapter (آداپتر): پیادهسازی پورتها برای اتصال دامنه به دیتابیس، UI، سرویسهای خارجی و … .
چرا MVC در مقیاس بزرگ آسیب میزند؟
1. وابستگی بیشازحد به فریمورک
در معماری MVC، معمولاً مدلها، کنترلرها و حتی منطق تجاری (Business Logic) بهشدت به فریمورک وابسته میشوند. این وابستگی باعث میشود اگر بخواهیم دیتابیس یا سرویس دیگری را تغییر دهیم، هزینهی Refactor بسیار بالا باشد.
2. منطق تجاری و زیرساخت
به مرور زمان، کنترلرها و مدلها تبدیل به محلی برای قرار گرفتن منطق تجاری میشوند. نتیجه این است که لایهی Domain (که باید مستقل باشد) با جزئیات دیتابیس، کش، لاگینگ و... در هم تنیده میشود. این کار نگهداری و تستپذیری کد را سخت میکند.
3. سختی تستنویسی
در MVC وقتی لاجیک ما مستقیم داخل مدل یا کنترلر نوشته شود، تستنویسی بسیار دشوار میشود. چون برای تست یک بخش ساده از کد، باید دیتابیس یا فریمورک کامل بوت شود. در مقیاس بزرگ این یعنی سرعت پایین و پیچیدگی زیاد در CI/CD.
4. رشد بیشازحد فایلها و کلاسها
در پروژههای بزرگ:
-
کنترلرها بهمرور چاق (Fat Controller) میشوند.
-
مدلها نیز با تعداد زیادی Query و Relationship سنگین میشوند.
این باعث میشود کدها به جای اینکه ماژولار باشند، به صورت پراکنده و پیچیده رشد کنند.
5. نبودن مرز مشخص بین Domain و Infrastructure
در MVC، معماری بهخودیخود مرز مشخصی بین منطق اصلی کسبوکار (Domain) و بخشهای زیرساختی مثل دیتابیس یا API ندارد. در نتیجه تغییر یکی از این بخشها به تغییر در هستهی سیستم منجر میشود.
نمونه ساختار پروژه
پیادهسازی گامبهگام
۱. تعریف Entity (دامنه)
// app/Domain/User/Entities/User.php
namespace AppDomainUserEntities;
class User
{
public function __construct(
public string $name,
public string $email,
) {}
}
۲. تعریف Repository Interface (پورت)
// app/Domain/User/Repositories/UserRepositoryInterface.php
namespace AppDomainUserRepositories;
use AppDomainUserEntitiesUser;
interface UserRepositoryInterface
{
public function save(User $user): void;
public function findByEmail(string $email): ?User;
}
۳. سرویس دامنه (منطق تجاری)
// app/Domain/User/Services/RegisterUserService.php
namespace AppDomainUserServices;
use AppDomainUserEntitiesUser;
use AppDomainUserRepositoriesUserRepositoryInterface;
class RegisterUserService
{
public function __construct(
private UserRepositoryInterface $repository
) {}
public function register(string $name, string $email): void
{
if ($this->repository->findByEmail($email)) {
throw new Exception("User already exists.");
}
$user = new User($name, $email);
$this->repository->save($user);
}
}
۴. پیادهسازی آداپتر (Eloquent)
// app/Infrastructure/Persistence/EloquentUserRepository.php
namespace AppInfrastructurePersistence;
use AppDomainUserEntitiesUser as DomainUser;
use AppDomainUserRepositoriesUserRepositoryInterface;
use AppModelsUser; // مدل Eloquent
class EloquentUserRepository implements UserRepositoryInterface
{
public function save(DomainUser $user): void
{
User::create([
'name' => $user->name,
'email' => $user->email,
]);
}
public function findByEmail(string $email): ?DomainUser
{
$model = User::where('email', $email)->first();
return $model
? new DomainUser($model->name, $model->email)
: null;
}
}
۵. اتصال در سرویسپراوایدر
// app/Providers/AppServiceProvider.php
use AppDomainUserRepositoriesUserRepositoryInterface;
use AppInfrastructurePersistenceEloquentUserRepository;
public function register(): void
{
$this->app->bind(UserRepositoryInterface::class, EloquentUserRepository::class);
}
۶. کنترلر (Adapter به HTTP)
// app/Infrastructure/Http/Controllers/UserController.php
namespace AppInfrastructureHttpControllers;
use AppDomainUserServicesRegisterUserService;
use IlluminateHttpRequest;
class UserController extends Controller
{
public function store(Request $request, RegisterUserService $service)
{
$service->register(
$request->input('name'),
$request->input('email')
);
return response()->json(['message' => 'User registered successfully']);
}
}
تستنویسی راحتتر
چون دامنه به دیتابیس وابسته نیست، تست راحت میشود:
use AppDomainUserServicesRegisterUserService;
use AppDomainUserRepositoriesUserRepositoryInterface;
use AppDomainUserEntitiesUser;
class InMemoryUserRepository implements UserRepositoryInterface
{
private array $users = [];
public function save(User $user): void
{
$this->users[$user->email] = $user;
}
public function findByEmail(string $email): ?User
{
return $this->users[$email] ?? null;
}
}
// تست
$repo = new InMemoryUserRepository();
$service = new RegisterUserService($repo);
$service->register("Ali", "ali@example.com");
جمعبندی
-
در معماری Hexagonal، لاراول فقط یک آداپتر است، نه مرکز پروژه.
-
دامنه مستقل میماند و میتوانید به راحتی آن را تست، توسعه یا حتی به فریمورک دیگری منتقل کنید.
-
این الگو در پروژههای بزرگ باعث میشود از وابستگی به زیرساخت رها شوید.
اولین نفر باش که نظر ثبت میکنی :) یعنی یه کامنت به ما نمیرسه 😁