پورتو یک الگوی معماری نرم افزار مدرن است که مجموعهای از دستورالعملها، اصول، و الگوها را به توسعه دهندگان ارائه میدهد تا کد خود را به روشی با قابلیت نگهداری و استفاده مجدد بسیار بالا سازماندهی کنند. هدف اصلی پورتو کمک به توسعه دهندگان برای ایجاد نرم افزاری است که مقیاس پذیر و انعطاف پذیر بوده و نگهداری آن در طول زمان آسان باشد.
«سادگی نهایت پیچیدگی است.» - لئوناردو داوینچی
پورتو به ویژه برای پروژههای وب با اندازه متوسط تا بزرگ که به انعطافپذیری و مقیاسپذیری بالایی نیاز دارند، مناسب است. با پورتو، توسعهدهندگان میتوانند پروژههای مونولیتیک بسیار مقیاسپذیری بسازند که به راحتی میتوانند هر زمان که نیاز باشد به چندین میکروسرویس تقسیم شوند. این رویکرد استفاده مجدد از بیزنس لاجیک یا فیچرهای کاربردی در چندین پروژه را ممکن میسازد و آن را به انتخابی ایدهآل برای تیمهایی تبدیل میکند که روی پروژههای مختلف کار میکنند.
این معماری بر اساس چندین مفهوم معماری تثبیت شده، از جمله معماریهای Domain Driven Design (DDD)، ماژولار، Model View Controller (MVC)، میکروکرنل، چندلایه، و Action Domain Responder (ADR) ساخته شده است. پورتو با استفاده از این مفاهیم تثبیت شده، چارچوبی قابل اعتماد و تست شده برای ساختن نرم افزار در اختیار توسعه دهندگان قرار می دهد.
علاوه بر این مفاهیم اساسی، پورتو همچنین به مجموعهای از اصول طراحی ثابت مانند Generalization، سالید، LIFT، برنامهنویسی شیگرا، DRY یا عدم تکرار، Low Coupling یا حداقل وابستگی، GRASP یا الگوهای نرم افزاری تفویض کلی مسئولیت، CoC و High Cohesion پایبند است. این اصول به گونهای طراحی شدهاند که اطمینان حاصل شود که نرم افزار ساخته شده با استفاده از پورتو قابل نگهداری، مقیاسپذیر و قابل درک است.
به طور کلی، پورتو یک الگوی معماری نرم افزار قدرتمند و انعطافپذیر است که مجموعهای جامع از ابزارها را برای ساختن نرم افزارهای مقیاسپذیر، قابل نگهداری و قابل استفاده مجدد به توسعهدهندگان ارائه میدهد. چه در حال کار بر روی یک پروژه کوچک یا یک برنامه سازمانی در مقیاس بزرگ باشید، پورتو میتواند به شما کمک کند تا نرم افزاری بسازید که نیازهای امروز و آینده شما را برآورده کند.
نکته: پورتو به عنوان یک معماری آزمایشی شروع به کار کرد که با این هدف طراحی شده بود که مشکلات رایجی که توسعه دهندگان وب هنگام ساخت پروژههای بزرگ با آن مواجه میشوند، را حل کند. پورتو از همان ابتدای پیدایش به یک الگوی معماری نرم افزاری محبوب در میان توسعه دهندگان تبدیل شده است که مجموعهای قدرتمند از ابزارها را برای ساختن نرم افزارهای مقیاسپذیر، قابل نگهداری و قابل استفاده مجدد ارائه میدهد. از بازخوردها و مشارکتها قدردانی ویژه دارد.
پورتو از دو لایه تشکیل شده است: "کانتینرها یا containers" و "کشتی یا ship".
این لایهها را میتوانید در هر جایی از فریمورک منتخب خودتان ایجاد کرد. به عنوان مثال، در لاراول یا Rails، آنها را میتوان در دایرکتوری app/
یا در یک دایرکتوری جدید با نام src/
در روت ایجاد کرد.
پورتو با جدا کردن کد اپلیکیشن شما به این دو لایه، به شما این امکان را میدهد که بیزنس لاجیک را از کد زیرساخت جدا نگه دارید که نگهداری و به روزرسانی اپلیکیشن را در گذر رمان آسانتر میکند. لایه کانتینرز شامل تمام کدهای مختص اپلیکیشن شما است، در حالی که لایه شیپ حاوی کدهای مشترکی است که میتواند در چندین کانتینر استفاده شوند.
این جداسازی مفاهیم همچنین به شما این امکان را میدهد تا به راحتی و با افزودن یا حذف کانتینرهای مورد نیازتان، بدون تحت تأثیر قرار دادن کد زیرساخت، اپلیکیشن خود را مقیاسبندی کنید. با پورتو، میتوانید یک معماری نرمافزاری مقیاسپذیر و قابل نگهداری ایجاد کنید که میتواند با نیازهای در حال تغییر شما در طول زمان سازگار شود.
قبل از بررسی دقیقتر، بیاید دقایقی را برای درک سطوح مختلف کدی صرف کنیم که در کدبیس خود خواهید داشت:
سطوح کد
با درک این سه سطح کد، کدبیس خود را میتوانید بهتر سازماندهی کنید و اطمینان حاصل کنید که هر سطح مسئولیت تسکهای مناسبی را بر عهده دارد. کد سطح پایین عملکرد اصلی برنامه شما را آماده میکند، در حالی که کد سطح متوسط به عنوان پلی بین کدهای سطح پایین و سطح بالا عمل میکند. کد سطح بالا حاوی بیزنس لاجیک خاص برنامه شما است و عملیات پیچیده را در بر میگیرد.
با پورتو، به آسانی میتوانید کد خود را به این سطوح مختلف جدا کنید و اطمینان حاصل کنید که هر سطح مسئولیت وظایف مناسب را بر عهده دارد. این جداسازی مفاهیم کمک میکند تا کدتان در طول زمان قابل نگهداری و مقیاس پذیرتر شود.
برای تجسم بهتر رابطه بین لایه کانتینرها، لایه شیپ و فریمورک زیرین، میتوانید لایه کانتینر را به عنوان کانتینرهای باری در نظر بگیرید که به لایه شیپ (کشتی باری) متکی است که به نوبه خود به فریمورک زیرین (دریا) متکی است. این نمودار رابطه بین لایههای مختلف را نشان میدهد:
لایه کانتینر (کانتینرهای باری) >> متکی بر >> لایه شیپ (کشتی باری) >> متکی بر >> فریمورک (دریا)
پورتو به گونهای طراحی شده است که بر حسب مقیاس پروژه شما اسکیل میشود! در حالی که بسیاری از شرکتها با افزایش مقیاس و رشد پروژه، از مونولیتیک به میکروسرویس (و اخیراً سرورلِس) حرکت میکنند، پورتو این انعطافپذیری را ارائه میدهد که در هر زمان با کمترین تلاش، پروژه مونولیتیک خود را به میکروسرویس (یا SOA) تبدیل کنید.
پورتو از دید واژه شناسی، یک پروژه مونولیتیک را معادل یک کشتی باری حاوی کانتینرهای مختلف میداند، در حالی که میکروسرویسها را معادل چندین کشتی باری حاوی کانتینرهای مختلف (و بدون در نظر گرفتن اندازه آنها) میداند. مفهوم این جملات این است که با پورتو، میتوانید با یک سرویس مونولیتیک کوچک به خوبی سازمانیافته، کار خود را آغاز کنید و در صورت نیاز با انتقال کانتینرها به چندین سرویس مجزا، همزمان با رشد تیم و کسبوکارتان، رشد کنید.
پورتو با سازماندهی کد خود در کانتینرها، که در سِکشنهای مجزا گروهبندی میشوند، استخراج سکشنهای جداگانه و دپلوی جداگانه آنها به عنوان میکروسرویس را آسان میکند. این قابلیت به شما این امکان را میدهد تا معماری برنامه خود را با توجه به نیازهایتان در طول زمان تغییر دهید، بدون اینکه نیازی به بازنویسی کل اپلیکیشنتان از ابتدا داشته باشید.
با این حال، بهرهبرداری از چندین سرویس (با مخازن متعدد، خطوط لوله CI و غیره) به جای یک سرویس یکپارچه (مونولیتیک) میتواند هزینه تعمیر و نگهداری را افزایش دهد و نیازمند یک رویکرد جدید برای ارتباط بین سرویسها است. نحوه ارتباط سکشنهای «سرویسها» با یکدیگر کاملاً به توسعهدهندگان بستگی دارد، با این حال پورتو استفاده از اِونتها و یا کامندها را توصیه میکند.
با پورتو، میتوانید یک معماری نرمافزاری مقیاسپذیر و انعطافپذیر ایجاد کنید که بتواند با نیازهای در حال تغییر کسبوکار شما سازگار شود. این مسئله به شما این امکان را میدهد که از مشکلات جلوتر بمانید و بهترین تجربه ممکن را برای کاربران خود فراهم کنید.
لایه شیپ یا کشتی یک جزء حیاتی از معماری پورتو است. این لایه شامل کلاسهای پایه Parent است، که کلاسهایی هستند که توسط هر کامپوننت مستقلی در کانتینرها اکستند شدهاند. همچنین برخی از کدهای کاربردی نیز در این لایه قرار گرفتهاند.
کلاسهای Parent در لایه شیپ به توسعهدهندگان کنترل کامل روی کامپوننتهای کانتینر میدهد. به عنوان مثال، افزودن یک فانکشن به کلاس پایه Model، آن را در هر مدل موجود در کانتینرهای شما در دسترس قرار میدهد و در زمان و تلاش صرفهجویی میکند.
لایه شیپ همچنین نقش مهمی در جداسازی کد اپلیکیشن از کد فریمورک دارد. این جداسازی ارتقاء فریمورک را بدون تأثیر بر کد برنامه تسهیل میکند و به مرور زمان نگهداری و بهروزرسانی اپلیکیشن شما را آسانتر میکند.
در پورتو، لایه Ship سبک و متمرکز نگه داشته میشود و فقط شامل کلاسهای اصلی Parent و کدهای ابزاری است. این لایه شامل قابلیتهای قابل استفاده مجدد متداول مانند احراز هویت یا مجوز نمیشود، زیرا کانتینرها این قابلیتها را ارائه میکنند. این روش جداسازی لایهها به توسعهدهندگان انعطافپذیری بیشتری برای سفارشیسازی برنامههای خود برای رفع نیازهای خاص خود میدهد.
پورتو با جدا کردن کد زیرساخت از کد بیزنس لاجیک، به شما این امکان را میدهد تا کد برنامه خود را سازماندهی شده و قابل نگهداری نگه دارید، در حالی که انعطاف پذیری را برای سفارشیسازی و مقیاسبندی برنامه خود در صورت نیاز فراهم میکند.
لایه شیپ از چندین نوع کد تشکیل شده است که با هم کار میکنند تا به اپلیکیشن شما قدرت دهند:
کد هسته: این موتور کشتی است که تمام کامپوننتهای کانتینر شما را به صورت خودکار ریجستر و لود میکند تا اپلیکیشن شما را بوت کند. این نوع کد شامل اکثر کدهای جادویی است که هر چیزی را که بخشی از بیزنس لاجیک شما نیست کنترل میکند و بیشتر شامل کدهایی است که توسعه را با گسترش فیچرهای فریمورک تسهیل میکند.
کد مشترک بین کانتینرها:
توجه به این نکته مهم است که تمام کامپوننتهای کانتینر باید از لایه Ship اکستند یا به ارث برده شوند، به ویژه پوشه Parent.
هنگام جداسازی هسته به یک پکیج خارجی، Parentهای شیپ باید از Parentهای هسته (که میتوان آنها را اَبسترکت نامید، زیرا اغلب آنها قرار است کلاسهای ابستزکت باشند) اکستند شوند. Parentهای شیپ، بیزنس لاجیک مشترک اپلیکیشن سفارشی شما را نگه میدارند، در حالی که Parentهای هسته (ابسترکتها) کد مشترک فریمورک شما را نگه میدارند. اساساً، هر چیزی که بیزنس لاجیک نباشد باید از اپلیکیشن واقعی در حال توسعه پنهان شود.
با سازماندهی کد خود به این روش، پورتو نگهداری و به روزرسانی اپلیکشن شما را در طول زمان آسان میکند، در حالی که به شما این امکان را میدهد تا فیچرهای فریمورک را برای رفع نیازهای خاص خود گسترش دهید و سفارشی کنید.
لایه کانتینر قلب معماری پورتو است. اینجا جایی است که بیزنس لاجیک خاص اپلیکیشن، شامل تمامی فیچرها و عملکردهای اپلیکیشن، قرار دارند. شما ۹۰٪ از زمان خود را صرف کار در این لایه، توسعه فیچرهای جدید و نگهداری فیچرهای موجود خواهید کرد.
یکی از مزایای اصلی استفاده از پورتو این است که پیچیدگی یک مسئله را با شکستن آن به کانتینرهای کوچکتر و قابل مدیریتتر مدیریت میکند. هر کانتینر به گونهای طراحی شده است که بخش خاصی از عملکرد را در خود جای دهد و توسعه، آزمایش و نگهداری آن را آسانتر کند.
با سازماندهی کد خود به این روش، پورتو به شما کمک میکند تا کدبیسی ماژولارتر و با قابلیت استفاده مجدد ایجاد کنید. این باعث میشود مقیاسبندی و نگهداری برنامه شما در طول زمان آسانتر شود و همچنین میزان تکرار کد در بخشهای مختلف برنامه شما کاهش مییابد.
به طور کلی، لایه کانتینرها کلید ایجاد یک معماری کاربردی قابل نگهداری و مقیاس پذیر با پورتو است.
یک کانتینر یک ماژول مستقل است که بخش خاصی از عملکرد در اپلیکیشن شما را در برمیگیرد. این ماژول میتواند یک فیچر، یک ظرف نگهدارنده APIهای RESTful یا هر چیز دیگری که بخواهید باشد.
مثال 1:
به عنوان مثال، در یک برنامه TODO، آبجکتهای "Task" و "User" و "Calendar" هر کدام در یک کانتینر متفاوت با روتها، کنترلرها، مدلها، اکسپشنها و موارد دیگر قرار میگیرند. هر کانتینر مسئول دریافت ریکوئستها و ریترن کردن ریسپانسها از هر رابط کاربری پشتیبانیشده (وب، API و غیره) است.
در حالی که توصیه میشود در هر کانتینر فقط از یک مدل استفاده کنید، در برخی موارد، ممکن است به بیش از یک مدل نیاز داشته باشید، و این کاملاً صحیح است. همچنین می توانید از Value Objectها استفاده کنید که شبیه به مدلها هستند اما دیتای خود را در دیتابیس ذخیره نمیکنند، بلکه به عنوان داده در مدلها نشان داده میشوند. این آبجکتها پس از دریافت دادهها از دیتابیس به طور خودکار ساخته میشوند، مانند قیمت، لوکیشن، زمان و موارد دیگر.
مهم است که به خاطر داشته باشید که دو مدل به معنای دو ریپازیتوری، دو ترنسفورمر و ... است. جز در حالتی که بخواهید از هر دو مدل همیشه با هم استفاده کنید، آنها را به دو کانتینر تقسیم کنید.
اگر بین دو کانتینر وابستگی بالایی دارید، قرار دادن آنها در یک سکشن، استفاده مجدد از آنها در پروژههای دیگر را آسانتر میکند.
مثال 2:
به عنوان مثال، اگر به اِپیاتو، اولین پروژه پیادهسازی با پورتو نگاهی بیندازید، متوجه خواهید شد که Authentication و Authorization هر دو فیچرهایی هستند که به عنوان کانتینر ارائه میشوند.
پورتو با شکستن اپلیکیشن شما به کانتینرهای کوچکتر و قابل مدیریتتر، توسعه، تست و نگهداری کدبیس شما را در طول زمان آسانتر میکند.
ContainerA ├── Actions ├── Tasks ├── Models └── UI ├── WEB │ ├── Routes │ ├── Controllers │ └── Views ├── API │ ├── Routes │ ├── Controllers │ └── Transformers └── CLI ├── Routes └── Commands ContainerB ├── Actions ├── Tasks ├── Models └── UI ├── WEB │ ├── Routes │ ├── Controllers │ └── Views ├── API │ ├── Routes │ ├── Controllers │ └── Transformers └── CLI ├── Routes └── Commands
کانتینرها میتوانند به روشهای مختلفی در یک سکشن با یکدیگر ارتباط برقرار کنند:
اگر از ارتباطات مبتنی بر رویداد بین کانتینرها استفاده میکنید، پس از جداسازی کدبیس خود به چندین سرویس، میتوانید از همان مکانیسم استفاده کنید.
توجه داشته باشید که اگر با تفکیک کد خود به ماژولها/دامنهها آشنا نیستید یا ترجیح میدهید از آن رویکرد استفاده نکنید، میتوانید کل برنامه خود را در یک کانتینر ایجاد کنید. با این حال، روش توصیه شدهای نیست و ممکن است در طول زمان آنقدر مقیاسپذیر یا قابل نگهداری نباشد.
سکشن یکی دیگر از گونههای بسیار مهم در معماری پورتو است.
یک سکشن گروهی از کانتینرهای مرتبط است. هر سکشن میتواند یک سرویس (میکرو یا بزرگتر)، یا یک سیستم فرعی در سیستم اصلی یا هر چیز دیگری باشد.
یک سکشن را به عنوان ردیفهایی از کانتینرها در یک کشتی باری در نظر بگیرید. کانتینرها به خوبی سازماندهی شده در ردیفها، سرعت بارگیری و تخلیه کانتینرهای مرتبط را برای یک مشتری خاص افزایش می دهد.
تعریف اولیه سکشن پوشهای است که حاوی کانتینرهای مرتبط است. با این حال مزایای آن بسیار زیاد است. (یک سکشن معادل یک bounded context در Domain-driven design است) هر سکشن بخشی از سیستم شما را نشان می دهد و کاملاً از سکشنهای دیگر جدا شده است.
هر سکشن میتواند به طور جداگانه دپلوی شود.
مثال 1:
اگر در حال ساخت یک بازی مسابقهای مانند Need for Speed هستید، ممکن است دو سکشن زیر را داشته باشید: سکشن مسابقه و سکشن لابی، که هر سکشن شامل یک کانتینر Car و یک مدل Car در داخل آن است، اما با پراپرتیها و عملکردهای متفاوت. در این مثال، مدل Car از سکشن Race میتواند شامل بیزنس لاجیک شتاب دادن و کنترل ماشین باشد، در حالی که مدل Car از سکشن Lobby شامل بیزنس لاجیک سفارشی کردن ماشین (رنگ، لوازم جانبی...) قبل از مسابقه است.
سکشنها این امکان را فراهم میآورند تا مدلهای بزرگ را به مدلهای کوچکتر بشکنید. و میتوانند مرزهایی را برای مدلهای مختلف در سیستم شما فراهم کنند.
اگر سادگی را ترجیح میدهید یا تنها یک تیم دارید که روی پروژه کار میکند، میتوانید هیچ سکشنی (جایی که همه کانتینرها در پوشه کانتینرها قرار دارند) نداشته باشید، به این معنی که پروژه شما تنها یک سکشن است. در این حالت اگر پروژه به سرعت رشد کرد و تصمیم گرفتید که باید از سکشنها استفاده کنید، میتوانید یک پروژه جدید با یک سکشن ایجاد کنید که به آن میکروسرویس می گویند. در میکروسرویس هر سکشن "بخش پروژه" در پروژه خود (ریپازیتوری) قرار گرفته است و آنها میتوانند از طریق شبکه معمولاً با استفاده از پروتکل HTTP ارتباط برقرار کنند.
مثال 2:
در یک اپلیکیشن ایکامرس معمولی میتوانید سکشنهای زیر را داشته باشید: سکشن موجودی، سکشن حمل و نقل، سکشن سفارش، سکشن پرداخت، سکشن کاتالوگ و موارد دیگر...
همانطور که میتوانید تصور کنید هر یک از این سکشنها میتوانند به تنهایی یک میکروسرویس باشند. و میتوانند بر اساس ترافیکی که دریافت میکند استخراج و در سرور خود دپلوی شود.
این معماری امکان اتصال آزاد بین سکشنها را فراهم میکند و سیستم مقیاسپذیرتر و انعطافپذیرتر را امکانپذیر میکند. از رویدادها و کامندها میتوان برای برقراری ارتباط بین سکشنهای مختلف استفاده کرد که امکان گسترش و اصلاح آسان سیستم را در طول زمان فراهم میکند.
در لایه کانتینر، مجموعهای از کلاسهای Component
با مسئولیتهای از پیش تعریف شده وجود دارند. هر تکه کدی که مینویسید باید در یک کامپوننت (عملکرد کلاس یا class function) قرار داده شود. پورتو لیست بزرگی از آن کامپوننتها را برای شما تعریف میکند، همراه با مجموعهای از دستورالعملها که باید هنگام استفاده از آنها رعایت کنید تا فرآیند توسعه را روان نگه دارید.
کامپوننتها یکپارچگی و تبات را تضمین میکنند و نگهداری از کد شما را آسانتر میکنند، زیرا از قبل میدانید هر قطعه کد را کجا باید پیدا کنید.
هر کانتینر از تعدادی کامپوننت تشکیل شده است. کامپوننتها در پورتو به دو نوع تقسیم میشوند: کامپوننتهای اصلی و کامپوننتهای اختیاری.
با استفاده از این کامپوننتها، میتوانید یک کدبیس ماژولار و قابل استفاده مجدد ایجاد کنید و نگهداری و اصلاح کد خود را در آینده آسانتر کنید.
شما باید از این کامپوننتها استفاده کنید زیرا تقریباً برای همه انواع برنامههای وب ضروری هستند:
روتها - کنترلرها - ریکوئستها - اکشنها - تسکها - مدلها - ویوها - ترنسفورمرها.
ویوها: باید در مواردی استفاده شود که برنامه صفحات HTML را سرو میکند.
ترنسفورمر: باید در مواردی استفاده شود که برنامه دادههای جیسون یا XML را سرو میکند.
برای تعاریف و اصول دقیق هر یک از اجزای اصلی، لطفاً به بخش "اصول و تعاریف برخی از کامپوننتها" در بخش مراجعه کنید.
لایف سایکل ریکوئست یا چرخه عمر درخواست فرآیندی است که یک ریکوئست از زمان فراخوانی یک API و حرکت از طریق کامپوننتها، تا آمادهسازی ریسپانس طی میکند. مراحل زیر یک سناریوی فراخوانی یک API بیسیک را توضیح میدهد:
مهم است که توجه داشته باشید که آبجکت ریکوئست قوانین ولیدیشن و احرازهویت ریکوئست را هندل میکند، در حالی که اکشن بیزنس لاجیک را اجرا میکند. تسکها میتوانند برای اجرای زیرمجموعههای قابل استفاده مجدد از بیزنس لاجیک استفاده شوند که هر تسک مسئول یک بخش از اکشن اصلی است. ویو یا ترنسفورمر برای ایجاد ریسپانسی استفاده میشود که به کاربر ارسال میشود.
روتها مسئول مَپ کردن ریکوئستهای HTTP ورودی به متدهای متناظر خود در کنترلرها هستند. هنگامی که یک ریکوئست HTTP به اپلیکیشن ارسال میشود، اندپوینتها، پترن URL منطبق با خودشان رو یافته و متد کنترلر متناظر با آن را فراخوانی میکنند.
اصول
کنترلرها مسئول اعتبارسنجی ریکوئستها، سرور کردن دادههای ریکوئست و ساختن ریسپانس هستند. اعتبارسنجی و ریسپانس در کلاسهای جداگانه اتفاق میافتد اما از کنترلر فراخوانی میشود.
کنترلرها مفهومی مشابه مفهومشان در MVC دارند (حرف C در MVC هستند)، اما با مسئولیتهای محدود و از پیش تعریف شده.
اصول
ممکن است تعجب کنید که چرا وقتی میتوانیم مستقیماً اکشن را از روت فراخوانی کنیم، به Controller نیاز داریم. لایه کنترلر کمک میکند تا اکشن در چندین رابط کاربری (Web & API) قابل استفاده مجدد باشد، زیرا اکشن ریسپانسی ایجاد نمیکند و این باعث کاهش میزان تکرار کد در رابطهای کاربری مختلف میشود.
در زیر مثالی آورده شده است:
همانطور که در مثال بالا می بینید، اکشن A1 توسط هر دو روت W-R1 و A-R1 استفاده شده که این به لطف لایه کنترلرهای موجود در هر UI محقق شده است.
ریکوئستها عمدتاً ورودی کاربر را در اپلیکیشن سِرو میکنند. آنها برای اعمال خودکار قوانین اعتبارسنجی و احرازهویت بسیار کاربردی هستند.
ریکوئستها بهترین مکان برای اعمال اعتبارسنجیها هستند زیرا هر مجموعه قوانین اعتبارسنجی مربوط به یک ریکوئست میشوند. ریکوئستها همچنین میتوانند مجوزهای دسترسی (Authorization) را بررسی کنند، بهعنوان مثال، بررسی کنند که آیا این کاربر به این متد کنترلر دسترسی دارد یا خیر (برای مثال، بررسی کند که آیا کاربر خاصی که قصد حذف محصولی را دارد مالک آن محصول است، یا بررسی کند که آیا این کاربر، ادمین است که بتواند چیزی را ویرایش است).
اصول
اکشنها نشان دهنده موارد استفاده از اپلیکیشن (یعنی اکشنها یا عملیاتی که توسط کاربر یا نرم افزار در اپلیکیشن قابل انجام است) هستند.
اکشنها میتوانند حاوی بیزنس لاجیک باشند و یا تسکها را برای اجرای بیزنس لاجیک مدیریت کنند.
اکشنها ساختارهای داده را به عنوان ورودی میگیرند، آنها را مطابق بیزنس رول (business rules) دستکاری میکنند و ساختارهای داده جدید را به عنوان خروجی ارائه میدهند.
اکشنها نباید کانسرنی راجع به مربوط به نحوه جمع آوری دادهها یا نحوه نمایش آنها داشته باشد.
با نگاه کردن به پوشه اکشنهای یک کانتینر، میتوانید بفهمید که کانتینر شما چه موارد استفاده (فیچرهایی) را ارائه میدهد. با مشاهده تمام اکشنها، میتوانید بگویید که یک اپلیکیشن چه کارهایی میتواند انجام دهد.
اصول
run()
داشته باشد.run()
می تواند یک آبجکت ریکوئست را به عنوان پارامتر بپذیرد.تسکها کلاسهایی هستند که بیزنس لاجیک مشترک بین چندین اکشن از کانتینرهای مختلف را نگه میدارند.
هر تسک مسئول بخش کوچکی از لاجیک است و معمولاً دارای یک متد به نام run()
است. با این حال، تسکها در صورت نیاز میتواند توابع بیشتری با نامهای صریح داشته باشند، که باعث میشود کلاس تسک جایگزین مفهوم پرچمهای فانکشن شود.
تسکها اختیاری هستند، اما در بیشتر موارد، ناچار به استفاده از آنها خواهید بود. به عنوان مثال، اگر اکشن ۱ را تعریف کردهاید که باید رکوردی را با ID خود از دیتابیس پیدا کند، سپس یک اونت را فایر کند. و اکشن ۲ دارید که باید رکورد مشابهی را با ID خود پیدا کند، سپس یک API خارجی را فراخوانی میکند. از آنجایی که هر دو اکشن لاجیک "یافتن یک رکورد با ID" را اجرا میکنند، میتوانیم آن بیزنس لاجیک را گرفته و در کلاس خودش یعنی تسک قرار دهیم. این تسک اکنون توسط این اکشنها و هر اکشن دیگری که ممکن است در آینده ایجاد کنید قابل استفاده مجدد است.
قانون این است که هر زمان که امکان استفاده مجدد از تکه کدی از یک اکشن را یافتید، باید آن قطعه کد را در یک تسک قرار دهید. کورکورانه برای همه چیز تسک ایجاد نکنید. همیشه میتوانید با نوشتن تمام بیزنس لاجیک در یک اکشن شروع کنید و فقط زمانی که نیاز به استفاده مجدد از آن دارید یک تسک اختصاصی ایجاد کنید. ریفکتور کردن برای سازگاری با رشد کدها ضروری است.
اصول
FindUserByIdAction
و FindUserByEmailAction
) که هر دو اکشن یک تسک یکسان را فراخوانی میکنند، بسیار خوب و منطقی است، همچنین بسیار خوب است که یک اکشن FindUserAction
تصمیم بگیرد که کدام تسک را باید فراخوانی کند.مدلها یک انتزاع برای دادهها ارائه میکنند و دادهها را در دیتابیس نشان میدهند. (آنها حرف M در MVC هستند).
مدلها مسئول نحوه مدیریت دادهها هستند و اطمینان حاصل میکنند که دادهها به درستی در بکاند (به عنوان مثال دیتابیس) ذخیره میشوند.
اصول
ویوها حاوی HTML ارائه شده توسط اپلیکیشن شما هستند.
هدف اصلی آنها جداسازی لاجیک اپلیکیشن از لاجیک پرزنتیشن است. (آنها حرف V در MVC هستند).
ویوها دادهها را از کنترلر دریافت میکنند و از آن برای تولید HTML خروجیای استفاده میکنند که به مرورگر کلاینت ارسال میشود. ویوها همچنین میتوانند شامل فایلهای قالبی باشند که ساختار و طرحبندی HTML را تعریف میکنند و حفظ یکپارچگی در چندین صفحه را آسانتر میکنند.
اصول
ترنسفورمرها، مخفف ترنسفورمرهای ریسپانس (Response Transformers)، معادل ویوها هستند اما برای ریسپانسهای جیسون. در حالی که ویوها دادهها را میگیرد و آنها را در قالب HTML نمایش میدهد، ترنسفورمرها دادهها را میگیرند و آنها را در قالب JSON نمایش میدهند.
ترنسفورمرها مسئول تبدیل مدلها به آرایه هستند. آنها یک مدل یا گروهی از مدلها "کالکشن" را میگیرند و آن را به یک آرایه قابل سریالایز فرمت شده تبدیل میکنند.
اصول
اکسپشنها نیز شکلی از خروجی هستند که مورد انتظار (مانند یک اکسپشن API) هستند و باید به خوبی تعریف شوند.
اکسپشنها راهی برای هندل کردن خطاها با ساختار از پیش تعریف شده و مورد انتظار است. در اپلیکیشنی که به خوبی طراحی شده است، هر زمان که خطایی رخ میدهد که نمیتواند توسط کد در زمینه فعلی آن هندل شود، باید اکپشنهایی رخ دهند.
اصول
ساب اکشنها برای حذف کدهای تکراری در اکشنها طراحی شدهاند. ساب اکشنها به اکشنها اجازه میدهند تا دنباله ای از تسکها را به اشتراک بگذارد، در حالی که تسکها به اکشنها اجازه میدهند تا بخشی از عملکرد را به اشتراک بگذارد.
ساب اکشنها برای حل یک مشکل ایجاد میشوند. گاهی اوقات یک بخش بزرگ از بیزنس لاجیک در چندین اکشن مجددا استفاده میشود و آن کد قبلاً برخی از تسکها را فراخوانی میکند. در چنین مواردی راه حل ایجاد یک ساب اکشن است.
به عنوان مثال، با فرض اینکه یک اکشن A1
در حال فراخوانی تسک ۱، تسک ۲ و تسک ۳ است و یک اکشن دیگری با نام اکشن A2
، تسک ۲، تسک ۳، تسک ۴ و تسک ۵ را فراخوانی میکند. توجه داشته باشید که هر دو اکشن وظایف ۲ و ۳ را فراخوانی میکنند. برای از بین بردن تکرار کد، میتوانیم یک ساب اکشن ایجاد کنیم که حاوی تمام کدهای مشترک بین هر دو اکشن باشد.
اصول
run()
داشته باشد.کامپوننتهای اختیاری مختلفی وجود دارند که میتوانند بر اساس نیازهای خاص شما به اپلیکیشنتان اضافه شوند. اگرچه ممکن است همه آنها ضروری نباشند، برخی از آنها به شدت توصیه میشود. این کامپوننتها عبارتند از:
در صورت نیاز برای بهبود عملکرد و قابلیت نگهداری آن، این کامپوننتها را به برنامه خود اضافه کنید.
Container ├── Actions ├── Tasks ├── Models ├── Values ├── Events ├── Listeners ├── Policies ├── Exceptions ├── Contracts ├── Traits ├── Jobs ├── Notifications ├── Providers ├── Configs ├── Mails │ └── Templates ├── Data │ ├── Migrations │ ├── Seeders │ ├── Factories │ ├── Criteria │ ├── Repositories │ ├── Validators │ ├── Transporters │ └── Rules ├── Tests │ └── Unit └── UI ├── API │ ├── Routes │ ├── Controllers │ ├── Requests │ ├── Transformers │ └── Tests │ └── Functional ├── WEB │ ├── Routes │ ├── Controllers │ ├── Requests │ ├── Views │ └── Tests │ └── Acceptance └── CLI ├── Routes ├── Commands └── Tests └── Functional
در پورتو، بیزنس لاجیک اپلیکیشن شما در کانتینرها قرار میگیرد. کانتینرهای پورتو از نظر ماهیت شبیه به ماژولها (از معماری ماژولار) و دامنهها (از معماری DDD) هستند.
کانتینرها میتوانند به کانتینرهای دیگر وابسته باشند، مشابه اینکه یک لایه میتواند به لایههای دیگر در معماری چندلایه وابسته باشد.
قوانین و دستورالعملهای پورتو، جهتهای وابستگی بین کانتینرها را به حداقل رسانده و تعریف میکند تا از ارجاعات دایرهای بین آنها جلوگیری شود.
علاوه بر این، امکان گروهبندی کانتینرهای مرتبط را در سکشنهای مختلف فراهم میکند و امکان استفاده مجدد از آنها را در پروژههای مختلف فراهم میکند. هر سکشن شامل یک بخش قابل استفاده مجدد از بیزنس لاجیک اپلیکیشن شما است.
وقتی صحبت از مدیریت وابستگی به میان میآید، توسعهدهنده مختار است که هر کانتینر را به ریپازیتوری خود منتقل کند یا همه کانتینرها را با هم در یک ریپازیتوری نگه دارد.
هدف پورتو کاهش هزینههای نگهداری با صرفهجویی در زمان توسعهدهندگان است. ساختار آن به گونهای است که مستقل بودن کدها از هم را تضمین کرده و یکپارچگی را در سیستم اجباری کند، که همگی به قابلیت نگهداری آن کمک میکنند.
داشتن یک فانکشن مستقل در هر کلاس برای توصیف یک عملکرد، افزودن و حذف ویژگیها را به یک فرآیند آسان تبدیل میکند.
پورتو یک کدبیس بسیار سازمان یافته دارد و وابستگی کد آن صفر است. علاوه بر این، یک گردشکار (workflow) توسعه واضح با مسیرهای جریان داده (data flow) و وابستگیهای از پیش تعریف شده دارد که همه اینها به مقیاس پذیری آن کمک میکنند.
پایبندی بسیار به اصل تک مسئولیتی (Single Responsibility Principle) با داشتن یک عملکرد واحد در هر کلاس منجر به داشتن کلاسهای سبک میشود که منجر به تستپذیری آسانتر میشود.
در پورتو، هر کامپوننت انتظار یک نوع ورودی و خروجی یکسان را دارد که تست کردن، ماک کردن و اشکالزدایی را ساده میکند.
ساختار پورتو خود باعث میشود نوشتن تستهای خودکار فرآیندی روان باشد. هر کانتینر دارای یک پوشه tests
در ریشه خود است که حاوی یونیت تستهایی برای تسکهای خود باشد. علاوه بر این، هر پوشه UI دارای یک پوشه tests
است که حاوی فانکشنال تستها است (برای آزمایش هر UI به طور جداگانه).
کلید آسان کردن تست و اشکال زدایی، تنها در سازماندهی تستها و مسئولیت از پیش تعریف شده اجزا نیست، بلکه در جداسازی کد شما نیز هست.
پورتو امکان تطبیق آسان تغییرات آینده را با حداقل تلاش فراهم می کند.
به عنوان مثال، فرض کنید یک برنامه وب دارید که HTML خاصی را ارائه میدهد و تصمیم میگیرید که به یک اپلیکیشن تلفن همراه با API نیز نیاز دارید. رابط کاربری قابل اتصال پورتو (WEB، API و CLI) شما را قادر میسازد ابتدا بیزنس لاجیک اپلیکیشن خود را بنویسید و سپس یک UI برای تعامل با کد خود پیادهسازی کنید. این قابلیت به شما انعطافپذیری میدهد تا رابطها مورد نیاز خود را اضافه کنید و به راحتی با تغییرات آینده سازگار شوید.
دلیل اینکه این امکان وجود دارد این است که اکشنها اصل سازماندهی مرکزی هستند، نه کنترلرها، و میتوانند در چندین UI به اشتراک گذاشته شوند. علاوه بر این، رابطهای کاربری از بیزنس لاجیک اپلیکیشن جدا شده و در هر کانتینر از یکدیگر جدا میشوند.
پورتو سهولت استفاده و قابل فهم بودن را در اولویت قرار میدهد. پیادهسازی زبان تخصصی دامنه هنگام نامگذاری کلاسها و پیروی از قانون تک فانکشنی در هر کلاس، امکان مکانیابی سریع هر فیچر یا عملکرد را فراهم میکند. این بدان معنی است که شما به راحتی می توانید هر مورد استفاده (اکشن) را در کد خود به سادگی با مرور فایل ها پیدا کنید.
پورتو تضمین میکند که میتوانید هر فیچر را در کمتر از 3 ثانیه پیدا کنید. به عنوان مثال، اگر به دنبال مکانهایی هستید که آدرسهای کاربر در حال اعتبارسنجی هستند، کافیست سری به کانتینر Adress بزنید، لیست اکشنها را باز کنید و ValidateUserAddressAction را جستجو کنید.
پورتو رشد آینده را در نظر میگیرد و تضمین میکند که کد شما بدون توجه به اینکه پروژه چقدر بزرگ میشود قابل نگهداری است. ساختار ماژولار، جداسازی کانسرنها، و کوپلینگ سازمانیافته بین کلاسهای داخلی ("کامپوننتها") اجازه میدهد تا تغییرات بدون عوارض جانبی نامطلوب انجام شود.
علاوه بر این، توسعهپذیری و انعطافپذیری پورتو امکان ادغام آسان با ابزارها و تکنولوژیهای دیگر را فراهم میکند. ساختار ماژولار آن افزودن قابلیتهای جدید را بدون تأثیرگذاری بر روی کد موجود امکانپذیر میسازد و مقیاسپذیری پروژه را در صورت نیاز آسان میکند. این بدان معنی است که پورتو نه تنها یک انتخاب عالی برای پروژههای فعلی است، بلکه برای پروژههایی که ممکن است در آینده به فیچرها یا ادغامهای اضافی نیاز داشته باشند نیز انتخاب خوبی است. انعطاف پذیری ارائه شده توسط پورتو همچنین امکان سفارشیسازی آسان کدبیس را برای مطابقت با نیازهای خاص پروژه فراهم میکند. این باعث میشود آن را به یک انتخاب همه کاره برای طیف گستردهای از نیازهای توسعه تبدیل کند.
پورتو حرکت سریع و آسان در فرآیند توسعه را امکانپذیر میکند.
ارتقاء چارچوب به دلیل جدایی کامل بین اپلیکیشن و کد فریمورک از طریق لایه شیپ ساده است.
علاوه بر این، رابط کاربری قابل اتصال پورتو، افزودن یا حذف اینترفیسها را آسان میکند، و ساختار ماژولار آن، اضافه کردن ویژگیهای جدید یا اصلاح فیچرهای موجود را بدون ایجاد تأثیر منفی بر سایر بخشهای کدبیس امکانپذیر میسازد. این چابکی و قابلیت ارتقا، پورتو را به گزینهای عالی برای پروژههایی تبدیل میکند که نیاز به انعطافپذیری و سازگاری با تغییرات آینده دارند.
لیست پیادهسازیهای معماری پورتو را در ادامه میتوانید مشاهده کنید.
برای زبان PHP در ۳ فریمورک لاراول، زند و سیمفونی پیادهسازی شده است:
لاراول: اِپیاتو (توسط خالق پورتو) - یک فریمورک PHP برای ساخت APIهای مقیاسپذیر برو روی لاراول
زند اکسپرسیو: اکسپرسیو پورتو
سیمفونی: سیمفونی پورتو
برای زبان پایتون در حال حاضر تنها در فریمورک جنگو پیاده سازی شده است:
جنگو: پایپورتو