مجتبی پاکزاد
مجتبی پاکزاد
خواندن ۳۵ دقیقه·۱ سال پیش

پورتو (الگوی معماری نرم افزار)

پورتو (الگوی معماری نرم افزار)
پورتو (الگوی معماری نرم افزار)

مقدمه

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

«سادگی نهایت پیچیدگی است.» - لئوناردو داوینچی

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

این معماری بر اساس چندین مفهوم معماری تثبیت شده، از جمله معماری‌های 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/ در روت ایجاد کرد.

پورتو با جدا کردن کد اپلیکیشن شما به این دو لایه، به شما این امکان را می‌دهد که بیزنس لاجیک را از کد زیرساخت جدا نگه دارید که نگهداری و به روزرسانی اپلیکیشن را در گذر رمان آسان‌تر می‌کند. لایه کانتینرز شامل تمام کدهای مختص اپلیکیشن شما است، در حالی که لایه شیپ حاوی کدهای مشترکی است که می‌تواند در چندین کانتینر استفاده شوند.

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

بررسی بصری

دیاگرام بصری معماری پورتو
دیاگرام بصری معماری پورتو

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

سطوح کد

  • کد سطح پایین: کد فریمورک (عملیات اساسی مانند خواندن فایل‌ها از یک دیسک یا تعامل با دیتابیس را پیاده‌سازی می‌کند). معمولا در دایرکتوری Vendor قرار دارد.
  • کد سطح متوسط: کد عمومی برنامه (عملکردی را پیاده‌سازی می‌کند که کد سطح بالا را سرو می‌کند و برای عملکرد به کد سطح پایین متکی است). باید در لایه Ship باشد.
  • کد سطح بالا: کد بیزنس لاجیک (لاجیک پیچیده اپلیکیشن را در بر می‌گیرد و برای عملکرد به کد سطح متوسط متکی است). باید در لایه Containers باشد.

با درک این سه سطح کد، کدبیس خود را می‌توانید بهتر سازماندهی کنید و اطمینان حاصل کنید که هر سطح مسئولیت تسک‌های مناسبی را بر عهده دارد. کد سطح پایین عملکرد اصلی برنامه شما را آماده می‌کند، در حالی که کد سطح متوسط به عنوان پلی بین کدهای سطح پایین و سطح بالا عمل می‌کند. کد سطح بالا حاوی بیزنس لاجیک خاص برنامه شما است و عملیات پیچیده را در بر می‌گیرد.

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

دیاگرام لایه‌ها

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

لایه کانتینر (کانتینرهای باری) >> متکی بر >> لایه شیپ (کشتی باری) >> متکی بر >> فریمورک (دریا)

دیاگرام بصری لایه‌ها در معماری پورتو
دیاگرام بصری لایه‌ها در معماری پورتو

مونولیتیک به میکروسرویس

پورتو به گونه‌ای طراحی شده است که بر حسب مقیاس پروژه شما اسکیل می‌شود! در حالی که بسیاری از شرکت‌ها با افزایش مقیاس و رشد پروژه، از مونولیتیک به میکروسرویس (و اخیراً سرورلِس) حرکت می‌کنند، پورتو این انعطاف‌پذیری را ارائه می‌دهد که در هر زمان با کمترین تلاش، پروژه مونولیتیک خود را به میکروسرویس (یا SOA) تبدیل کنید.

پورتو از دید واژه شناسی، یک پروژه مونولیتیک را معادل یک کشتی باری حاوی کانتینرهای مختلف می‌داند، در حالی که میکروسرویس‌ها را معادل چندین کشتی باری حاوی کانتینرهای مختلف (و بدون در نظر گرفتن اندازه آنها) می‌داند. مفهوم این جملات این است که با پورتو، می‌توانید با یک سرویس مونولیتیک کوچک به خوبی سازمان‌یافته، کار خود را آغاز کنید و در صورت نیاز با انتقال کانتینرها به چندین سرویس مجزا، همزمان با رشد تیم و کسب‌وکارتان، رشد کنید.

پورتو با سازماندهی کد خود در کانتینرها، که در سِکشن‌های مجزا گروه‌بندی می‌شوند، استخراج سکشن‌های جداگانه و دپلوی جداگانه آنها به عنوان میکروسرویس را آسان می‌کند. این قابلیت به شما این امکان را می‌دهد تا معماری برنامه خود را با توجه به نیازهای‌تان در طول زمان تغییر دهید، بدون اینکه نیازی به بازنویسی کل اپلیکیشن‌تان از ابتدا داشته باشید.

با این حال، بهره‌برداری از چندین سرویس (با مخازن متعدد، خطوط لوله CI و غیره) به جای یک سرویس یکپارچه (مونولیتیک) می‌تواند هزینه تعمیر و نگهداری را افزایش دهد و نیازمند یک رویکرد جدید برای ارتباط بین سرویس‌ها است. نحوه ارتباط سکشن‌های «سرویس‌ها» با یکدیگر کاملاً به توسعه‌دهندگان بستگی دارد، با این حال پورتو استفاده از اِونت‌ها و یا کامندها را توصیه می‌کند.

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

لایه شیپ

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

کلاس‌های Parent در لایه شیپ به توسعه‌دهندگان کنترل کامل روی کامپوننت‌های کانتینر می‌دهد. به عنوان مثال، افزودن یک فانکشن به کلاس پایه Model، آن را در هر مدل موجود در کانتینرهای شما در دسترس قرار می‌دهد و در زمان و تلاش صرفه‌جویی می‌کند.

لایه شیپ همچنین نقش مهمی در جداسازی کد اپلیکیشن از کد فریمورک دارد. این جداسازی ارتقاء فریمورک را بدون تأثیر بر کد برنامه تسهیل می‌کند و به مرور زمان نگهداری و به‌روزرسانی اپلیکیشن شما را آسان‌تر می‌کند.

در پورتو، لایه Ship سبک و متمرکز نگه داشته می‌شود و فقط شامل کلاس‌های اصلی Parent و کدهای ابزاری است. این لایه شامل قابلیت‌های قابل استفاده مجدد متداول مانند احراز هویت یا مجوز نمی‌شود، زیرا کانتینرها این قابلیت‌ها را ارائه می‌کنند. این روش جداسازی لایه‌ها به توسعه‌دهندگان انعطاف‌پذیری بیشتری برای سفارشی‌سازی برنامه‌های خود برای رفع نیازهای خاص خود می‌دهد.

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

ساختار لایه شیپ

لایه شیپ از چندین نوع کد تشکیل شده است که با هم کار می‌کنند تا به اپلیکیشن شما قدرت دهند:

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

کد مشترک بین کانتینرها:

  • کلاس‌های والد (Parent): این کلاس‌ها، کلاس‌های پایه هر کامپوننت در کانتینر شما هستند. افزودن فانکشن‌ها به کلاس‌های والدین، آن‌ها را در تمامی کانتینرها در دسترس قرار می‌دهد، و کلاس‌های والد به گونه‌ای طراحی شده‌اند که حاوی کد مشترک بین کانتینرهای شما باشند.
  • کلاس‌های عمومی (Generic): این کلاس‌ها، فیچرها و کلاس‌های قابل استفاده مجدد مانند اکسپشن‌های گلوبال، میدلورهای اپلیکیشن، فایل‌های کانفیگ گلوبال و غیره هستند که می‌توانند توسط هر کانتینری استفاده شوند.

توجه به این نکته مهم است که تمام کامپوننت‌های کانتینر باید از لایه 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

ارتباط بین کانتینرها

کانتینرها می‌توانند به روش‌های مختلفی در یک سکشن با یکدیگر ارتباط برقرار کنند:

  • هر کانتینر ممکن است به یک یا چند کانتینر دیگر وابستگی داشته باشد.
  • یک کنترلر می‌تواند تسک‌های (Tasks) داخل کانتینر دیگری را فراخوانی کند.
  • یک مدل ممکن است با مدلی از کانتینر دیگر ریلیشن داشته باشد.
  • سایر شکل‌های ارتباطی مانند اونت‌ها و کامندها نیز امکان‌پذیر هستند.

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

توجه داشته باشید که اگر با تفکیک کد خود به ماژول‌ها/دامنه‌ها آشنا نیستید یا ترجیح می‌دهید از آن رویکرد استفاده نکنید، می‌توانید کل برنامه خود را در یک کانتینر ایجاد کنید. با این حال، روش توصیه شده‌ای نیست و ممکن است در طول زمان آنقدر مقیاس‌پذیر یا قابل نگهداری نباشد.

سکشن‌ها

سکشن یکی دیگر از گونه‌های بسیار مهم در معماری پورتو است.

یک سکشن گروهی از کانتینرهای مرتبط است. هر سکشن می‌تواند یک سرویس (میکرو یا بزرگتر)، یا یک سیستم فرعی در سیستم اصلی یا هر چیز دیگری باشد.

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

تعریف اولیه سکشن پوشه‌ای است که حاوی کانتینرهای مرتبط است. با این حال مزایای آن بسیار زیاد است. (یک سکشن معادل یک bounded context در Domain-driven design است) هر سکشن بخشی از سیستم شما را نشان می دهد و کاملاً از سکشن‌های دیگر جدا شده است.

هر سکشن می‌تواند به طور جداگانه دپلوی شود.

مثال 1:

اگر در حال ساخت یک بازی مسابقه‌ای مانند Need for Speed هستید، ممکن است دو سکشن زیر را داشته باشید: سکشن مسابقه و سکشن لابی، که هر سکشن شامل یک کانتینر Car و یک مدل Car در داخل آن است، اما با پراپرتی‌ها و عملکردهای متفاوت. در این مثال، مدل Car از سکشن Race می‌تواند شامل بیزنس لاجیک شتاب دادن و کنترل ماشین باشد، در حالی که مدل Car از سکشن Lobby شامل بیزنس لاجیک سفارشی کردن ماشین (رنگ، لوازم جانبی...) قبل از مسابقه است.

سکشن‌ها این امکان را فراهم می‌آورند تا مدل‌های بزرگ را به مدل‌های کوچکتر بشکنید. و می‌توانند مرزهایی را برای مدل‌های مختلف در سیستم شما فراهم کنند.

اگر سادگی را ترجیح می‌دهید یا تنها یک تیم دارید که روی پروژه کار می‌کند، می‌توانید هیچ سکشنی (جایی که همه کانتینرها در پوشه کانتینرها قرار دارند) نداشته باشید، به این معنی که پروژه شما تنها یک سکشن است. در این حالت اگر پروژه به سرعت رشد کرد و تصمیم گرفتید که باید از سکشن‌ها استفاده کنید، می‌توانید یک پروژه جدید با یک سکشن ایجاد کنید که به آن میکروسرویس می گویند. در میکروسرویس هر سکشن "بخش پروژه" در پروژه خود (ریپازیتوری) قرار گرفته است و آنها می‌توانند از طریق شبکه معمولاً با استفاده از پروتکل HTTP ارتباط برقرار کنند.

مثال 2:

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

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

ارتباط بین سکشن‌ها

  • یک سکشن باید ایزوله باشد و نباید به هیچ سکشن دیگری وابسته باشد.
  • یک سکشن ممکن است به اونت‌هایی که توسط سکشن‌های دیگر فایر می‌شوند گوش دهد. (کامندها را می‌توان به عنوان جایگزینی برای اونت‌ها استفاده کرد.)

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

کامپوننت‌ها

در لایه کانتینر، مجموعه‌ای از کلاس‌های Component با مسئولیت‌های از پیش تعریف شده وجود دارند. هر تکه کدی که می‌نویسید باید در یک کامپوننت (عملکرد کلاس یا class function) قرار داده شود. پورتو لیست بزرگی از آن کامپوننت‌ها را برای شما تعریف می‌کند، همراه با مجموعه‌ای از دستورالعمل‌ها که باید هنگام استفاده از آن‌ها رعایت کنید تا فرآیند توسعه را روان نگه دارید.

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

انواع کامپوننت

هر کانتینر از تعدادی کامپوننت تشکیل شده است. کامپوننت‌ها در پورتو به دو نوع تقسیم می‌شوند: کامپوننت‌های اصلی و کامپوننت‌های اختیاری.

  • کامپوننت‌های اصلی: کامپوننت‌های ضروری که برای کانتینر خود نیاز دارید. این کامپوننت‌ها اجباری هستند و برای دستیابی به عملکرد اصلی کانتینر شما استفاده می‌شوند.
  • کامپوننت‌های اختیاری: این‌ها کامپوننت‌هایی هستند که می‌توانید از آن‌ها برای افزودن عملکرد اضافی به کانتینر خود استفاده کنید. این کامپوننت‌ها اختیاری هستند و بسته به نیاز خود می‌توانید انتخاب کنید که از آنها استفاده کنید یا نه.

با استفاده از این کامپوننت‌ها، می‌توانید یک کدبیس ماژولار و قابل استفاده مجدد ایجاد کنید و نگهداری و اصلاح کد خود را در آینده آسان‌تر کنید.

کامپوننت‌های اصلی

شما باید از این کامپوننت‌ها استفاده کنید زیرا تقریباً برای همه انواع برنامه‌های وب ضروری هستند:

روت‌ها - کنترلرها - ریکوئست‌ها - اکشن‌ها - تسک‌ها - مدل‌ها - ویوها - ترنسفورمرها.

ویوها: باید در مواردی استفاده شود که برنامه صفحات HTML را سرو می‌کند.
ترنسفورمر: باید در مواردی استفاده شود که برنامه داده‌های جیسون یا XML را سرو می‌کند.

برای تعاریف و اصول دقیق هر یک از اجزای اصلی، لطفاً به بخش "اصول و تعاریف برخی از کامپوننت‌ها" در بخش مراجعه کنید.

دیاگرام تعامل کامپوننت‌ها

دیاگرام تعامل بین کامپوننت‌ها در معماری پورتو
دیاگرام تعامل بین کامپوننت‌ها در معماری پورتو

لایف سایکل ریکوئست

لایف سایکل ریکوئست یا چرخه عمر درخواست فرآیندی است که یک ریکوئست از زمان فراخوانی یک API و حرکت از طریق کامپوننت‌ها، تا آماده‌سازی ریسپانس طی می‌کند. مراحل زیر یک سناریوی فراخوانی یک API بیسیک را توضیح می‌دهد:

  1. کاربر یک اندپوینت را در یک فایل روت فراخوانی می‌کند.
  2. اندپوینت یک میدلور را برای هندل کردن احراز هویت فراخوانی می‌کند.
  3. اندپوینت فانکشن متناظرش را در کنترلر فراخوانی می‌کند.
  4. آبجکت ریکوئست، که به صورت خودکار به کنترلر اینجکت شده، ولیدیشن‌های ریکوئست و قوانین احراز هویت را اعمال می‌کند.
  5. کنترلر یک اکشن را فراخوانی کرده و داده‌ها را از طریق آبجکت ریکوئست به آن پاس می‌دهد.
  6. اکشن بیزنس لاجیک را اجرا می‌کند، یا می‌تواند هر تعداد تسکی که برای اجرای زیرمجموعه‌های قابل استفاده مجدد بیزنس لاجیک مورد نیاز دارد را فراخوانی کند.
  7. تسک‌ها زیرمجموعه‌های قابل استفاده مجدد بیزنس لاجیک را اجرا می‌کنند، هر تسک مسئول یک بخش از اکشن اصلی است.
  8. اکشن داده‌هایی را برای ریترن کردن به کنترلر آماده می‌کند، و ممکن است در صورت نیاز این داده‌ها را از تسک‌ها جمع آوری کرده باشد.
  9. کنترلر ریسپانس را با استفاده از یک ویو یا ترنفسفورمر می‌سازد و آن را برای کاربر ارسال می‌کند.

مهم است که توجه داشته باشید که آبجکت ریکوئست قوانین ولیدیشن و احرازهویت ریکوئست را هندل می‌کند، در حالی که اکشن بیزنس لاجیک را اجرا می‌کند. تسک‌ها می‌توانند برای اجرای زیرمجموعه‌های قابل استفاده مجدد از بیزنس لاجیک استفاده شوند که هر تسک مسئول یک بخش از اکشن اصلی است. ویو یا ترنسفورمر برای ایجاد ریسپانسی استفاده می‌شود که به کاربر ارسال می‌شود.

اصول و تعاریف

روت‌ها (Routes)

روت‌ها مسئول مَپ کردن ریکوئست‌های HTTP ورودی به متدهای متناظر خود در کنترلرها هستند. هنگامی که یک ریکوئست HTTP به اپلیکیشن ارسال می‌شود، اندپوینت‌ها، پترن URL منطبق با خودشان رو یافته و متد کنترلر متناظر با آن را فراخوانی می‌کنند.

اصول

  • سه نوع روت وجود دارد، روت‌های API، روت‌های وب و روت‌های CLI.
  • فایل‌های مربوط به روت‌های API باید از فایل‌های روت‌های Web جدا باشند و هر کدام در پوشه خود قرار گیرند.
  • پوشه روت‌های Web تنها حاوی اندپوینت‌های Web (قابل دسترسی توسط مرورگرهای وب) خواهند بود. و پوشه روت‌های API تنها حاوی اندپوینت‌های API (قابل دسترسی توسط هر اپلکیشن مصرف کننده‌ای) خواهد بود.
  • هر کانتینری باید روت‌های خود را داشته باشد.
  • هر فایل روت تنها باید حاوی یک اندپوینت باشد.
  • کار اندپوینت این است که وقتی یک ریکوئست از هر نوعی ساخته شد، یک متد در کنترلر متناظرش فراخوانی کند (اندپوینت هیچ کار دیگری نباید انجام دهد).

کنترلرها

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

کنترلرها مفهومی مشابه مفهوم‌شان در MVC دارند (حرف C در MVC هستند)، اما با مسئولیت‌های محدود و از پیش تعریف شده.

اصول

  • کنترلرها نباید چیزی درباره بیزنس لاجیک یا هر آبجکت بیزنسی دیگری بدانند.
  • یک کنترلر تنها باید کارهای زیر را انجام دهد: خواندن داده‌های یک ریکوئست (ورودی کاربر)، فراخوانی یک یک اکشن (و ارسال داده‌های ریکوئست به آن) و ساخت یک رسپانس (معمولاً ریسپانس را بر اساس داده‌های جمع آوری شده از اکشن فراخوانی شده می‌سازد)
  • کنترلرها نباید حاوی هیچ شکلی از بیزنس لاجیک باشند (برای اجرای بیزنس لاجیک باید یک اکشن را فراخوانی کنند).
  • کنترلرها نباید تسک‌های کنترلر را به صورت مستقیم فراخوانی کنند. آنها فقط می‌توانند اکشن‌ها را فراخوانی کنند (و سپس اکشن‌ها، می توانند تسک‌ها کانتینر را فراخوانی کنند).
  • کنترلرها را تنها می‌توان توسط اندپوینت‌های روت‌ها فراخوانی کرد.
  • هر پوشه UI کانتینر (Web، API، CLI) کنترلرهای خود را خواهد داشت.

ممکن است تعجب کنید که چرا وقتی می‌توانیم مستقیماً اکشن را از روت فراخوانی کنیم، به Controller نیاز داریم. لایه کنترلر کمک می‌کند تا اکشن در چندین رابط کاربری (Web & API) قابل استفاده مجدد باشد، زیرا اکشن ریسپانسی ایجاد نمی‌کند و این باعث کاهش میزان تکرار کد در رابط‌های کاربری مختلف می‌شود.

در زیر مثالی آورده شده است:

  • رابط کاربری (وب): روت W-R1 -> کنترلر W-C1 -> اکشن A1.
  • رابط کاربری (API): روت A-R1 -> کنترلر A-C1 -> اکشن A1.

همانطور که در مثال بالا می بینید، اکشن 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 باید از طریق ترنسفورمرها قالب‌بندی (فرمت) شوند.
  • هر مدلی (که با فراخوانی API برگردانده می‌شود) باید یک ترنسفورمر متناظر داشته باشد.
  • یک کانتینر به تنهایی می‌تواند چندین ترنسفورمر داشته باشد.
  • معمولاً، هر مدل دارای یک ترنسفورمر برای اطمینان از سازگاری با فرمت ریسپانس API است.

اکسپشن‌ها

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

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

اصول

  • دو نوع اکسپشن شامل اکسپشن‌های کانتینری (در کانتینرها قرار می‌گیرند) و اکسپشن‌های عمومی (در شیپ قرار می‌گیرند) وجود دارد.
  • تسک‌ها، ساب تسک‌ها، مدل‌ها، و هر کلاسی به طور کلی می‌تواند یک اکسپشن بسیار خاص ایجاد کند.
  • کلاس فراخوان باید تمام اکسپشن‌های مورد انتظار از کلاس فراخوانی شده را مدیریت کند.
  • اکشن‌ها باید همه اکسپشن‌ها را هندل کنند و مطمئن شوند که به کامپوننت‌های بالایی نشت نمی‌کنند و باعث رفتارهای غیرمنتظره نمی‌شوند.
  • اسامی اکسپشن‌ها باید تا حد امکان خاص باشد و باید دارای پیام های توصیفی واضح باشد.

ساب اکشن‌ها

ساب اکشن‌ها برای حذف کدهای تکراری در اکشن‌ها طراحی شده‌اند. ساب اکشن‌ها به اکشن‌ها اجازه می‌دهند تا دنباله ای از تسک‌ها را به اشتراک بگذارد، در حالی که تسک‌ها به اکشن‌ها اجازه می‌دهند تا بخشی از عملکرد را به اشتراک بگذارد.

ساب اکشن‌ها برای حل یک مشکل ایجاد می‌شوند. گاهی اوقات یک بخش بزرگ از بیزنس لاجیک در چندین اکشن مجددا استفاده می‌شود و آن کد قبلاً برخی از تسک‌ها را فراخوانی می‌کند. در چنین مواردی راه حل ایجاد یک ساب اکشن است.

به عنوان مثال، با فرض اینکه یک اکشن A1 در حال فراخوانی تسک ۱، تسک ۲ و تسک ۳ است و یک اکشن دیگری با نام اکشن A2، تسک ۲، تسک ۳، تسک ۴ و تسک ۵ را فراخوانی می‌کند. توجه داشته باشید که هر دو اکشن وظایف ۲ و ۳ را فراخوانی می‌کنند. برای از بین بردن تکرار کد، می‌توانیم یک ساب اکشن ایجاد کنیم که حاوی تمام کدهای مشترک بین هر دو اکشن باشد.

اصول

  • ساب اکشن‌ها باید تسک‌ها را فراخوانی کنند. اگر یک ساب اکشن تمام بیزنس لاجیک را بدون کمک حداقل یک تسک انجام می‌دهد، احتمالاً نباید یک ساب اکشن بلکه باید یک تسک باشد.
  • یک ساب اکشن می‌تواند داده‌ها را از تسک گرفته و آنها را به تسک دیگری پاس دهد.
  • یک ساب اکشن می‌تواند چندین تسک را فراخوانی کند (آنها حتی می‌توانند تسک‌ها را از کانتینرهای دیگر فراخوانی کنند).
  • ساب اکشن‌ها می‌توانند داده‌ها را به اکشن برگردانند.
  • ساب اکشن نباید هیچ ریسپانسی را برگرداند (وظیفه کنترلر این است که یک ریسپانس را برگرداند).
  • ساب اکشن نباید ساب اکشن دیگری را فراخوانی کند (تا حد امکان از آن اجتناب کنید).
  • ساب اکشن باید از اکشن‌ها استفاده کند. با این حال، آنها را می‌توان از طریق اونت‌ها، کامندها، و یا کلاس‌های دیگر استفاده کرد. اما آنها نباید از طریق کنترلرها یا تسک‌ها استفاده شوند.
  • هر ساب اکشن تنها باید یک فانکشن به نام run()داشته باشد.

کامپوننت‌های اختیاری

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

  • تست‌ها: برای تست خودکار اپلیکیشن شما
  • اونت‌ها: برای برادکست و لسنینگ اونت‌های برنامه
  • لسنرها: برای هندل کردن اونت‌‌های اپلیکیشن
  • کامندها: برای ایجاد کامندهای CLI سفارشی
  • مایگریشن‌ها: برای مدیریت تغییرات در اسکیمای دیتابیس شما
  • سیدرها: برای پر کردن دیتابیس با داده‌های تستی
  • فکتوری‌ها: برای تولید داده‌های تستی
  • میدلورها: برای هندل کردن ریکوئست‌ها و ریسپانس‌های HTTP
  • ریپازیتوری‌ها: برای ایجاد انتزاع در لاجیک پایداری داده‌ها
  • کریتریا: برای کوئری زدن به دیتابیس با معیارهای پیچیده
  • پالسی‌ها: برای تعریف پالسی‌های authorization
  • سرویس پرووایدرها: برای رجیستر کردن سرویس‌ها در کانتینر اپلیکیشن
  • کانترکت‌ها: برای تعریف اینترفیس‌هایی که باید توسط کلاس‌ها پیاده‌سازی شوند
  • تریت‌ها: برای به اشتراک‌گذاری کد بین کلاس‌ها
  • جاب‌ها: برای اجرای تسک‌های طولانی مدت در بک گراند
  • ولیوها: برای نمایش آبجکت ولیوهای ساده
  • ترنسپورترها: برای ارسال و دریافت داده‌ها بین سیستم‌ها
  • ایمیل‌ها: برای ارسال پیام‌های ایمیل
  • نوتیفیکشن‌ها: برای ارسال نوتیفیکیشن به کاربران
  • و بیشتر...: در صورت نیاز کامپوننت‌های اضافی اضافه کنید

در صورت نیاز برای بهبود عملکرد و قابلیت نگهداری آن، این کامپوننت‌ها را به برنامه خود اضافه کنید.

ساختار یک کانتینر معمولی

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های مقیاس‌پذیر برو روی لاراول

زند اکسپرسیو: اکسپرسیو پورتو

سیمفونی: سیمفونی پورتو

برای زبان پایتون در حال حاضر تنها در فریمورک جنگو پیاده سازی شده است:

جنگو: پایپورتو

پورتومعماری نرم افزار
به عنوان توسعه دهنده در صبا ایده (آپارات، فیلیمو و ...) مشغول به کارم و در باورژن آموزش می‌دهم. حل مساله و چالش رو خیلی دوست دارم و رابطه خیلی خوبی با ریاضیات، برنامه‌نویسی و اقتصاد دارم.
شاید از این پست‌ها خوشتان بیاید