مرتضی دلیل
مرتضی دلیل
خواندن ۸ دقیقه·۵ سال پیش

آشنایی با Clean Architecture : اجزای Clean Architecture (قسمت چهارم)

قسمت اول : مقدمات
قسمت دوم : مبانی معماری
قسمت سوم : نگاهی به معماری سنتی سه لایه
قسمت چهارم : اجزای Clean Architecture (شما در حال خواندن این مقاله هستید)
قسمت پنجم : پیاده سازی بر اساس سرویس ها
قسمت ششم : پیاده سازی بر اساس UseCase ها
قسمت هفتم : آشنایی با CQRS

رویکرد clean architecture این است که دیگر لایه اصلی یا پایین ترین لایه، data نیست.
لایه مرکزی Entityها یا دامین برنامه است.

یا به شکل زیر هم قابل ساده سازی است.

لایهدامین شامل Logic Enterprise و تایپ های آن است و کمتر لاجیک دارد.

لایه اپلیکیشن شامل Business loginc و تایپ های آن است.

آنچه در Domain است میتواند برای چندین سیستم مورد استفاده قرار گیرد. ولی آنچه در application است فقط برای یک سیستم طراحی شده است.

اما Entity ها یا همان لایه Domeain models دقیقا چه هستند؟در زمینه CA همگی مفاهیم مشابهی را منتقل می‌کنند، فقط شاید نامگذاری ها متفاوت باشد.

اما Entityها یا همان لایه Domain models دقیقا چه هستند؟

مشخصا به اجزاء بنیادین سیستمی که طراحی میکنید و مثلا به شکل کلاس، قالب آن را تعیین میکنید تا بعدا به آن موجودیت ببخشید entity میگویند.

رابرت سی مارتین ( معروف به آنکل باب) و بخاطر مانیفست اجایل و پایه ریزی اصول طراحی نرم افزار معروف است گفته

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

پس خانه میتواند از چوب و سنگ و ... باشد و اینها همه جزئیات است.

در CA نیز چنین رویکردی وجود دارد.

تعیین entityهای یک سیستم، یک موضوع الزامی است، چون مسئله ذهنی توسعه دهنده با همین Entity ها (domain models) در سیستم مشخص میشود.

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

اما پرزنتیشن یک دیتیل است. اینکه آنچه به استفاده کننده نمایش میدهیم نتیجه لایبرری react است یا با razor و الکترون و ... اینکار را میکنیم تنها جزئیات هستند.

همینطور Persisntence Layer یا همان لایه دیتا هم یک دیتیل است.

این لایه وظیفه ماندگار کردن یک وضعیت را دارد برای همین این نام را دارد.

این موضوع از جزئیات یک مهماری است که دیتا چطور ذخیره میشود اینکه روی دیسک به شکل فایل است یا در یک دیتابیس ریلیشنال یا نوریلیشنال ذخبره میشود همه دیتیل هستند.

اما نباید اشتباه کرد؛ Presentation و Persistence خیلی مهم هستند اما اینها چیزهایی نیستد که توسعه دهنده برای حل مسائل پیش رو به عنوان موضوع اصلی به آن نگاه کند.

ما باید نگاهمان به معماری نرم افزار به این شکل باشد که بخشی را ضروری و بخشی را جزئیات بدانیم.

تا الان توضیح دادیم چرا مرکزیت دیتابیس را رها کردیم و مرکزیت دامین را به عنوان Clean Architecture بررسی می‌کنیم.

از دید دیگر وقتی دیتابیس مرکز یا پایه اصلی معماری باشد (database centeric architecture) دیتابیس از ضروریات این معماری محسوب شده است و همه لایه ها یا بخش های دیگر به دیتابیس اشاره میکنند.

اما در حالتی که مدل ها مرکز باشد ، مدل ها از ضروریات محسوب میشود و دیتابیس و پرزنتیشن دیتیل محسوب میشوند. در این حالت تمامی لایه ها و بخش ها به domain model اشاره میکنند.

اما Use case یا Interactor یا سرویس که در لایه اپلیکیشن وجود دارند چیست؟

لاجیک برنامه است؛ مثلا میخواهیم پست های نوشته شده را بیرون بکشیم، یا کامنت های مربوط به یک پست را خارج کنیم و مثلا کار محاسباتی انجام دهیم یا یوزری را از جدول کاربران بیرون بکشیم.

کدهای این پروسه منطقی در لایه اپلیکیشن قرار خواهد گرفت.

این لایه بطور مستقیم به لایه انتیتی ها یا همان domain models متصل است. اما از لایه های دیگر بیخبر است.

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

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

مثلا اگر از Repository Pattern استفاده کرده باشیمT اینترفیس های مربوط به کار با کاربران دیتابیس در این لایه وجود دارد. یعنی IRepository ها.

پیاده سازی این اینترفیس در لایه Persistence است.

این پروژه به کمک DI کار میکند، یعنی در استارتاپ پروژه یا هر لایه، بطور جدا میگوییم که فلان اینترفیس ها را با فلان کلاس ها معادل کن.

این روش کمک میکند که به راحتی لایه Persistence تغییر کند.

مثلا اگر روزی به جای SQL بخواهیم دیتا روی یک دیتابیس دیگر مثل mongo ذخیره شود لایه اپلیکیشن دست نمیخورد، بلکه بر اساس اینترفیس های تعریف شده در لایه اپلیکیشن، پیاده سازی های جدیدی برای آنها در لایه Persistence ساخته خواهد شد.

بر اساس این توضیح Dependency Inversion بهتر قابل توجیه است.

در روش های مدرن تولید نرم افزار، بر خلاف روش سنتی، به جای اینکه لایه اپلیکیشن وابسته به لایه مربوط به data یعنی Persistence باشد. لایه پرسیستنس وابسته لایه اپلیکیشن خواهد بود.

یعنی جزئیات به اصول وابستگی دارند.

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

با اینکار نگهداری و انعطاف پذیری معماری بیشتر خواهد بود.

لایه های دیتیل میتواند با هر نوع داده بیرونی دیگر مثل دیتابیس در ارتباط باشد. مثلا یک سرویس دیگر را صدا بزند و اطلاعاتی به آن بفرستد(فرض که سرویس در یک لایه دیگر است که وظیفه آن لایه برقراری ارتباط Http است) و اطلاعاتی بگیرد.(مثل ارسال کد ملی و دریافت اطلاعات شناسنامه ای فرد)

میتوانیم لایه های دیگر داشته باشیم مثلا یک لایه بنام common داشته باشیم که تاریخ سیستم را به ما میدهد یا یک کد منحصر بفرد بر اساس مشخصات سخت افزاری تولید میکند و کارهایی مثل این. جدا کردن لایه ای این مزیت را دارد که در آینده برای تست میتوانیم به سادگی mock کردن را انجام دهیم.

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

  • تمرکز توسعه دهنده در این لایه فقط روی لاجیک برنامه خواهد بود.
  • فهم و درک منطق برنامه ساده تر خواهد شد.
  • از DIP یا dependency inversion principle باید استفاده کنیم که یکی از اصول SOLID است و کد را انعطاف پذیر و قابل نگهداری میکند.

معایب این پیاده سازی این لایه :

  • برای ایجاد و نگهداری این لایه، نیاز به هزینه زمانی و انسانی است.
  • ما لایه بیزنس لاجیک سنتی را به دو لایه دامین ها و اپلیکیشن تقسیم کردیم ، پس باید تشخیص دهیم که هر کدی در کدام بخش باید باشد.
  • دپندنسی اینجکشن بین لایه اپلیکیشن و لایه infrastructure یا persistence برای توسعه دهندگان تازه کار درک مشکلی دارد.

ما در این لایه میتوانیم سرویس های معمولی یا use case تعریف کنیم.

بطور خلاصه سرویس های معمولی کلاس هایی هستند که شامل متدهای مرتبط با یک موضوعند.

مثلا سرویس product مرتبط به کار با محصولات است و متدهایی مثل getProduct وgetTopProductByPrice و ... در این کلاس هستند.

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

یوزکیس ها ولی شباهت ها و تفاوت هایی با کلاس های سرویس دارند:

  • مثل سرویس شامل قوانین بیزنسی هستند.
  • کپسوله کردن و پیاده سازی همه use case های سیستم و هر کدام در یک کلاس جدا که به لحاظ استقلال مسئولیت از سرویس بهتر هست.
  • تنظیم جریان داده به و از انتیتی ها برای رسیدن به اهداف تعریف شده در use case به کمک الگوی ریکوئست و ریسپانس به نسبت سرویس حالت کپسوله بهتری دارد.
  • هیچ نوع وابستگی به فریم ورک یا UI یا دیتابیس ندارند و UI هم به دلیل وجود لایه presenter با خروجی use case ها ارتباط مستقیم ندارد.

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

پیاده سازی اینترفیس IRequestHandler را خواهیم دید. این اینترفیس یک مثال از پترن mediator است که به پیاده ساز دیکته میکند از رفتار ریکوئستی و ریسپانسی برای پیاده سازی استفاده کند.

ما فقط یک متد به نام Handle در اینترفیس تعریف میکنیم که همه کار مربوط به use case را انجام میدهد.

TResponse Handle(TRequest message)

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

یعنی هندل ریترن خاصی ندارد، آرگومان دوم ریترن آن است.

هر دو تایپ آرگومان ها توسط IRequestHandler دیکته شده است.

همه لاجیک های مرتبط با این use case در سطح این اپلیکیشن به این متد میرسند.

درک این موضوع با توضیح به شکل تئوری سخت است. در آینده با یک مثال این موضوع قابل درک تر خواهد شد.

وظیفه لایه presenter چیست؟

این لایه وظیفه آماده سازی دیتای تحویل داده شده از use case را دارد.

یعنی تغییرات ساختاری لازم را روی دیتا انجام میدهد و آن را به شکل مورد نظر برای خواندن انسان یا دیوایسِ متناسب آماده میکند.

مثلا ساختار خروجی به شکل JSON است و دارای سه پراپرتی data,status,message است. این لایه پرزنتر است که مسئولیت دارد این آبجکت را بسازد. در حقیقت در حالت web api که پیاده سازی آن به شکل Restful است اکشن های کنترلر دیتای نهایی را از Presenter دریافت و بدون تغییر Return میکنند.

پس میتوان گفت این لایه مسئول یکسان سازی و فرمت دهی خروجی است.

تصویر فوق Data Flow را نشان میدهد؛ فرض کنید میخواهیم دیتایی را در جدول postدیتابیس ذخیره کنیم.

  • رابط کاربری(UI) متد مرتبط را از لایه Presenter یا همان View model صدا میزند و دیتا را به آن میفرستد.
  • لایه Presenter از لایه Use caseها(لایه اپلیکیشن) متد متناسب را اجرا میکند.
  • لایه Use case دیتای دریافت شده از Presenter را آماده سازی کرده و توسط متدهای متناسب موجود در ریپازیتوری(یا ORM) در دیتابیس ذخیره میکند و نتیجه را از همین مسیر به UI برمیگرداند.

در مورد نحوه مطالعه کدهای جدید که با معماری جدید هستند سعی کنید از بخشی UI شروع کنید که درک بهتری از روند داشته باشید. همه ما یک تصویر ساده از Request/Response داریم، این تصور به ما کمک میکند که خط سیر یک ریکوئست را با تمرکز بیشتری دنبال کنیم.

نکته مهم در مورد جداسازی لایه ها این است که صرفا ایجاد پروژه های جدا از هم و ارتباط آنها لایه بندی محسوب نمیشه. باید فهمید دقیقا این جدا سازی به چه دلیل بوده است.

در مقاله آتی یک مثال برای Clean Architecture خواهیم دید.

clean architectureuse casepersistence layerpresentation layerآموزش clean architecture
برنامه نویس و علاقمند به برنامه نویسی، سینما، فلسفه و هر چیزی که هیجان انگیز باشد. در ویرگول از روزمرگیهای مرتبط با علاقمندیهام خواهم نوشت. در توئیتر و جاهای دیگر @mortezadalil هستم.
شاید از این پست‌ها خوشتان بیاید