Shahin Sorkh's Blog

A full stack dev journal, maybe?

 updated at 05 Jul 2023
With ♥️ by Shahin Sorkh Theme by mattgraham
Can be found at Telegram , LinkedIn , StackOverflow Can be called via email or mobile

لاراول فراتر از CRUD: یکم. لاراول دامنه‌گرا

Laravel   Domain oriented   Programming

متن اصلی در Laravel beyond CRUD: 01. Domain oriented Laravel نوشته Brent.

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

اول از همه، واژه «دامنه» از من نیست؛ بلکه از الگوی برنامه‌نویسی محبوب DDD (طراحی دامنه‌محور) گرفتم. براساس واژه‌نامه، «دامنه» می‌تونه معادل واژه‌های «حوزه» یا «گستره» درنظر گرفته بشه. [نویسنده از فرهنگ لغت آکسفورد تعریف انگلیسی آورده. م]

هرچند «دامنه» تو این کتاب دقیقا هم‌معنی چیزی که توی DDD به کار می‌ره نیست، ولی تشابهاتی وجود داره. اگه با DDD آشنا باشید، در طی این کتاب متوجهشون می‌شید. من تمام سعیم رو کردم که همه هم‌پوشانی‌ها و تفاوت‌ها رو بگم.

خب، دامنه‌ها. می‌تونید بشون «گروه» یا «ماژول» هم بگید؛ بعضیا هم بشون می‌گن «سرویس». هرچی صداش کنیم، دامنه‌ها مجموعه‌ای از مسائل بیزینس رو توصیف می‌کنن که شما قراره حلشون کنید.

یه لحظه وایسید.. من همین الان اولین واژه اینترپرایزم رو تو این کتاب استفاده کردم: «مسائل بیزینسی». در طی مطالعه این کتاب، متوجه می‌شید که من تمام تلاشمو کردم که از جنبه‌های تئوری، مدیریت بالادستی و بیزینسی کار دوری کنم. من خودم دولوپرم و ترجیح می‌دم که همه چیز رو کاربردی نگه‌دارم. پس بجای این واژه، از یه کلمه ساده‌تر استفاده می‌کنم: «پروژه».

یه مثالی بزنیم: برنامه‌ای برای مدیریت رزرو هتل. باید مشتری‌ها، رزروها، صورت‌حساب‌ها، دارایی‌های هتل و غیره رو مدیریت کنه.

فریمورک‌های مدرن بهتون یاد می‌دن که گروه‌های مرتبط از مفاهیم رو بردارید و جاهای مختلف کدبیس پخش کنید: بخشی رو در کنترلرها، بخشی در مدل‌ها؛ می‌دونید چی می‌خوام بگم.

هرگز شده مشتری بهتون بگه «الان رو کنترلرها کار کن»، یا «یخورده بیشتر روی مدل وقت بذار»؟ نه. بلکه ازتون می‌خوان که روی صورت‌حساب، مدیریت مشتری یا فیچرهای رزرو کار کنید.

من به این گروه‌ها می‌گم دامنه. این‌ها قراره مفاهیمی که به همدیگه تعلق دارن رو در یک دسته بگذارن. شاید اولش بدیهی به نظر بیاد، ولی پیچیده‌تر از چیزیه که فکر می‌کنید. به همین خاطر، تمرکز بخشی از این کتاب روی دسته‌ای از قواعد و تمریناتیه که کد شما رو به خوبی مرتب می‌کنه.

واضحه که فرمول ریاضی وجود نداره و تقریبا همه چیز بستگی به شرایط پروژه داره. پس انتظار نداشته باشید که این کتاب یه سری قواعد ثابت و همیشه درست بهتون بده. بلکه اینطور فکر کنید که می‌خواید مجموعه‌ای از ایده‌ها به دست بیارید که می‌تونید هرطور مایلید ازشون استفاده کنید.

این کتاب، بیشتر از اینکه یک راه‌حل کلی برای همه مشکلات شما باشه، یه فرصت یادگیریه.

دامنه‌ها و اپلیکیشن‌ها


اگه قرار باشه ایده‌ها رو دسته‌بندی کنیم، این سوال پیش میاد: تا کجا باید پیش بریم؟ مثلا می‌تونیم همه چیزای مربوط به صورت‌حساب رو بذاریم کنار هم: مدل‌ها، کنترلرها، منابع، قواعد ولیدیشن، جاب‌ها الخ.

این کار تو اپلیکیشن‌های سنتی HTTP یه مشکلی به وجود میاره: اغلب نمی‌شه کنترلرها و مدل‌ها رو به صورت یک به یک به هم مرتبط کرد. چرا، تو REST APIها و برای اکثریت کنترلرهای سنتی CRUD ممکنه که بشه، ولی متاسفانه این موارد استثنائاتین که ما رو با سختی مواجه می‌کنن. مثلا صورت‌حساب‌ها به سادگی فارق از بقیه سیستم قابل رسیدگی نیستن؛ باید مشتری‌ای وجود داشته باشه که براش ارسال بشه، رزروی وجود داشته باشه که صورت‌حساب براش صادر بشه و غیره.

به همین دلیل ما نیاز به مرزهای مشخص‌تری داریم که کد مربوط به دامنه از بقیه جدا بشه.

از طرفی دامنه است، که منطق بیزینس رو نمایندگی می‌کنه؛ از طرف دیگه، کدی داریم که می‌خواد از این دامنه استفاده کنه و از طریق یکپارچه‌سازی با فریمورک، به دست کاربر نهایی برسونه. هدف اپلیکیشن‌ها اینه که بستر استفاده و دست‌کاری دامنه رو به شکل کاربرپسند به کاربر نهایی ارائه کنن.

در عمل


خب تمام اینا که گفتیم در عمل چه شکلی می‌شه؟ دامنه کلاس‌هایی مثل مدل‌ها، کوئری بیلدرها، رویدادهای دامنه، قواعد ولیدیشن و بقیه رو نگهداری می‌کنن؛ تمام این مفاهیم رو عمیق‌تر بررسی می‌کنیم.

لایه اپلیکیشن یک یا چند اپلیکیشن درون خودش داره. می‌تونیم هر اپلیکیشن رو به عنوان یک اپ منزوی (ایزوله) درنظر بگیریم که اجازه داره تمام دامنه رو به کار بگیره. عموما این اپلیکیشن‌ها باهم مبادله ندارن.

یک مثال می‌تونه یه پنل ادمین استاندارد HTTP باشه، مثال دیگه هم می‌تونه یه REST API باشه. من همچنین دوست دارم کنسول، آرتیزان، رو هم به عنوان یه اپ مستقل درنظر بگیرم.

از دید سطح بالاتر، ساختار فایلی یه پروژه دامنه‌گرا به این شکل درمیاد:

// One specific domain folder per business concept
app/Domain/Invoices/
    ├── Actions
    ├── QueryBuilders
    ├── Collections
    ├── DataTransferObjects
    ├── Events
    ├── Exceptions
    ├── Listeners
    ├── Models
    ├── Rules
    └── States

app/Domain/Customers/
    // …

و سطح اپلیکیشن به این شکل:

// The admin HTTP application
app/App/Admin/
    ├── Controllers
    ├── Middlewares
    ├── Requests
    ├── Resources
    └── ViewModels

// The REST API application
app/App/Api/
    ├── Controllers
    ├── Middlewares
    ├── Requests
    └── Resources

// The console application
app/App/Console/
    └── Commands

درباره namespaceها


احتمالا متوجه شدید که مثال بالا برخلاف رسم لاراول از \App به عنوان تنها نیم‌اسپیس ریشه استفاده نمی‌کنه. از اونجایی که اپلیکیشن‌ها فقط بخشی از پروژه هستند، و چون می‌تونن متعدد باشن، منطقی نیست که از \App به عنوان ریشه همه چیز استفاده کنیم.

البته اگه ترجیح می‌دید به پیشفرض‌های لاراول نزدیک بمونید، اشکالی نداره. به این معنی که با نیم‌اسپیس‌هایی مثل \App\Domain و \App\Api مواجه می‌شید در نهایت. اما آزادی کامل دارید که هرکاری دوست دارید انجام بدید.

هرچند اگه بخواید نیم‌اسپیس‌های ریشه رو جدا کنید، باید کمی در بخش بوت‌استرپینگ لاراول تغییر ایجاد کنید.

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

// composer.json
{
    // …

    "autoload" : {
        "psr-4" : {
            "App\\" : "app/App/",
            "Domain\\" : "app/Domain/",
            "Support\\" : "app/Support/"
        }
    }
}

من یه نیم‌اسپیس \Support هم معرفی کردم که فعلا اینجور درنظر بگیرید که همه توابع کمکی که به هیچ‌جا تعلق ندارن رو می‌خواد نگه‌داری کنه.

حالا، باید نیم‌اسپیس \App رو مجددا معرفی کنیم چون لاراول داخل خودش واسه کارای زیادی از این نیم‌اسپیس استفاده می‌کنه.

// app/App/BaseApplication.php

namespace App;

use Illuminate\Foundation\Application as LaravelApplication;

class BaseApplication extends LaravelApplication
{
    protected $namespace = 'App\\';

    public function path($path = '')
    {
        return $this->basePath.DIRECTORY_SEPARATOR.'app/App'.($path ? DIRECTORY_SEPARATOR.$path : $path);
    }
}

در نهایت، باید اپلیکیشن پایه کاستوم خودمونو معرفی کنیم:

// bootstrap/app.php

$app = new App\BaseApplication(
    realpath(__DIR__.'/../')
);

متاسفانه راه تمیزتری وجود نداره، چون فریمورک انتظار نداره که ساختار فایلیش تغییر کنه. بازم می‌گم، اگه اینجوری خوشتون نمیاد، می‌تونید از نیم‌اسپیس ریشه پیش‌فرض استفاده کنید و این تغییراتو اعمال نکنید.


هر ساختار فایلی که استفاده می‌کنید، مهم‌ترین نکته اینه که نحوه فکر کردنتون از گروه‌بندی بر اساس ویژگی‌های فنی، به گروه‌هایی بر اساس مفاهیم بیزینس تغییر کنه.

درون هر گروه، هر دامنه، ساختار کد رو طوری شکل می‌دیم که به راحتی بشه ازش در هر گروه استفاده کرد. در بخش اول این کتاب نگاهی از نزدیک می‌اندازیم به ساختار درونی دامنه و الگوهایی که می‌تونن مورد استفاده قرار بگیرن برای قابل نگه‌داری موندن کدبیس در طول زمان. بعد از اون، به لایه اپلیکیشن نگاهی می‌کنیم، می‌بینیم که دامنه دقیقا چجوری می‌تونه استفاده بشه و چجوری به کمک مفاهیم موجود در لاراول مثل view modelها بهبود پیدا می‌کنیم.

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



سری پست‌های لاراول فراتر از CRUD: