گشتی در معماری (N-layered, DDD, Hexagon, Onion, Clean Architecture)

درود!? پس میخوای معمار بشی، نه؟ به من دروغ نگو، می دانم که می گویی. حتی اگر این کار را نکنید، باز هم می خواهید توسعه دهنده بهتری باشید. در غیر این صورت وقت صرف خواندن این مقاله نمی کردید ?


قابل تقدیر است. به هر حال، همه ما می خواهیم در کاری که انجام می دهیم، اگر بهترین نباشیم، بهتر باشیم. من اینجا هستم تا در این مورد به شما کمک کنم.


بنابراین، چگونه می توان یک معمار شد؟ بدیهی است با یادگیری تمامی معماری ها! ? البته این درست نیست. لازم نیست همه چیز را بدانید همچنین لازم نیست با همه آنها تجربه داشته باشید. با این حال، دانستن حداقل محبوب ترین آنها، مانند N-Layered، DDD، Hexagon، Onion و Clean؛ درک تاریخچه آنها، استفاده از آنها و تفاوت بین آنها قطعاً شما را از سایر توسعه دهندگان متمایز می کند.

امیدوارم این کلمات مقدمه کافی بوده باشد تا شما را درگیر کند. اگر هنوز علاقه دارید، بیایید شروع کنیم ?


همه چیز از کجا شروع شد؟

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

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

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

این برای مدتی کمک کرد، اما نه چندان. اگر شما هم مثل من از جامعه سی شارپ هستید، احتمالاً به اشتباه فکر می‌کنید که جعبه زرد رنگی به نام Model روی آن نمودارها فقط DTO است. همه اینها به خاطر مایکروسافت است. آنها ما را با چارچوب ASP MVC خود گیج کردند. لعنت به تو، مایکروسافت! فکر می کنی از من باهوش تر هستی فقط به این دلیل که من احمق تر هستم؟ ?

در واقع، Model در اینجا مخفف Domain Model است که به عنوان منطق تجاری نیز شناخته می‌شود، که در هر برنامه‌ای کاملاً حیاتی است.

آیا می توانید قمار کنید، کدام یک از آن سه جزء بالا بیشترین مشکل را ایجاد می کند؟ در حالی که View فقط تصاویر و دکمه‌های ساده است، کنترل‌کننده‌ها در وسط قرار دارند و تمام پیچیدگی‌ها در مدل متمرکز شده است.

آن دوره ای بود که الگوهای GoF به سادگی کافی نبودند. بنابراین باید ایده های جدیدی ظاهر می شد. چگونه پیچیدگی را مدیریت کنیم؟ درست! تفرقه بینداز و حکومت کن. ما قبلاً این کار را با MVC انجام دادیم، پس بیایید یک بار دیگر این کار را انجام دهیم.


2002 - N-Layered

معماری ایده آل از هیچ ظاهر نشد. مثل همه چیز، با سعی و خطا راه خودش را رفت.


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

او "الگوهای معماری کاربردی سازمانی" را منتشر کرد که در آن معماری N-Layered توضیح داده شد.

ایده در اینجا ساده است، گروه بندی همه کدهای مرتبط با هم و فراخوانی آن لایه ها.

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

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

این به توسعه دهندگان کمک کرد تا نه تنها از شر تکرار کد خلاص شوند، بلکه در نهایت به ساختار کد خود کمک کنند. اگرچه، این قوانین بسیار چابک هستند، در عمل، 3 لایه برای اکثر پروژه ها بیش از اندازه کافی بود.

  • رابط کاربری (UI) - مسئول تعامل با کاربران است.
  • لایه منطق کسب و کار (BLL) - مفاهیم کسب و کار را نشان می دهد. این برنامه کاری را که برنامه شما انجام می دهد را کنترل می کند و آن را در مقایسه با سایرین بسیار منحصر به فرد می کند.
  • لایه دسترسی به داده (DAL) - داده ها را در حافظه نگه می دارد و وضعیت برنامه شما را حفظ می کند

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

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

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

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


هر لایه, لایه زیر را با API خود فراخوانی می کند که معمولاً به شکل یک interface نمایش داده می شود. Access modifiers در هر کلاس به اندازه آن لایه ها مهم هستند:

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


2003 — Domain Driven Design

در سال 2003، اریک ایوانز، توسعه‌دهنده جوان 46 ساله از بوستون، کتاب خود را با عنوان «طراحی دامنه محور: مقابله با پیچیدگی در قلب نرم‌افزار» منتشر کرد که حداقل یکی از مارتین‌ها را در جهان واقعاً غمگین کرد.

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


ایوانز با تمام ایده های فاولر موافق بود که وابستگی های پروژه باید در یک جهت قرار گیرند. با این حال، او همچنین اشاره کرد که برای ماژول های سطح پایین خوب است که ماژول های بالا را فراخوانی کنند، مگر اینکه dependency direction rule را زیر پا بگذارند. می توان با callbacks، observer patterns و غیره به آن دست یافت.

او همچنین دید که کنترلرها منطق زیادی دارند، بنابراین آن را به لایه دیگری به نام Application منتقل کرد.


اما مهم‌ترین کاری که ایوانز انجام داد این بود که گفت: «F*uckبه پایگاه داده، منطق تجاری مهم‌تر است». اینو گفت و بعد هیچ کاری نکرد?‍♂️. بله، بله، بله می دانم... DDD and so on. با این حال، از نقطه نظر معماری، او تغییر چندانی ایجاد نکرده است.

در معماری او، لایه ها اینطور تعریف شده است:

لایه ارائه Presentation Layer- مسئول تعامل با کاربران است.

لایه برنامه Application Layer- وظایف را هماهنگ می کند و کار را به اشیاء دامنه واگذار می کند.

لایه دامنه Domain Layer- مفاهیم کسب و کار را نشان می دهد. این برنامه کاری را که برنامه شما انجام می دهد را کنترل می کند و آن را در مقایسه با سایرین بسیار منحصر به فرد می کند.

لایه زیرساخت Infrastructure Layer- داده ها را در حافظه نگه می دارد و وضعیت برنامه شما را حفظ می کند

همینطورکه می بینید، او تغییر نام داد.


رابط کاربری به این معنی است که شما کاربرانی دارید، که همیشه اینطور نیست. گاهی اوقات GUI (رابط کاربری گرافیکی) برای کاربران، گاهی اوقات CLI (واسط خط فرمان) برای توسعه دهندگان و اغلب API (رابط برنامه نویسی برنامه) برای برنامه ها است. لایه Presentation فقط یک نام عمومی تر و مناسب تر است.

منطق کسب و کار برای برخی از توسعه دهندگان، به خصوص برای کسانی که اصلاً تجارتی ندارند، گیج کننده بود، بنابراین نام جدیدی معرفی شد - Domain.

پایگاه داده تنها ابزار خارجی نیست که ما استفاده می کنیم، بنابراین همه فرستنده های ایمیل، اتوبوس های رویداد، SQL و سایر cr?p ها به زیرساخت منتقل شدند.

اساساً همین است. برخی تغییر نام در اینجا. به علاوه یک لایه جدید وجود دارد. تلاش زیادی برای دامنه انجام شد. اما همان معماری با همان وابستگی هاست. اگر از اصل dependency inversion خبر داشت ?.


2005 — Hexagon (Ports and Adapters)

قبلاً، ماژول‌ها باید به مورد بعدی در ردیف ارجاع دهند. با کشف dependency inversion، همه چیز تغییر کرده است.

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


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

آلیستر از مستطیل ها خسته شد، بنابراین یک شش ضلعی کشید، برای همه چیز دو نام آورد، سعی کرد آن را ترسناک کند. اما همکار توسعه دهنده من نترس. در واقع، این معماری بسیار پیچیده تر از معماری N-layered نیست:

چرخیدن و جابجایی زیادی وجود داشت، پس بیایید ببینیم در آنجا چه اتفاقی افتاده است.

کاکبرن رویای ایوانز را محقق کرد. اکنون Domain جزء مرکزی سیستم است، نه تنها در کلمات، بلکه در عمل. به هیچ پروژه دیگری ارجاع نمی دهد.

برای تأکید بر این که واقعاً قلب است، منطق تجاری به Core تغییر نام داد.

ماژول های زیرساخت به نصف تقسیم شدند - abstraction (interfaces) و پیاده سازی. انتزاع ها بخشی از منطق تجاری شدند و به پورت تغییر نام دادند. پیاده سازی در لایه زیرساخت باقی ماند. اکنون به آنها آداپتور می گویند. در عمل، UI همان framework layer بعنوان DB بود، بنابراین به همان سرنوشت دچار شد.


وجود رابط های زیرساخت در منطق کسب و کار شما، باعث می شود دامنه مستقل و بدون وابستگی باشد. در نتیجه منطق کسب و کار می تواند در هر محیطی و با هر ابزاری کار کند. آیا می خواهید DB را تغییر دهید؟ فقط پیاده سازی را تغییر دهید، آداپتور مورد نیاز را پیاده سازی کنید و آن را به پورت موجود "وصل کنید".

تغییرات در هر آداپتور (پایگاه داده، فرستنده ایمیل، رابط کاربری) بر منطق تجاری تأثیر نمی گذارد. رابط ها ثابت می مانند.

هر جزء را می توان به طور جداگانه deploy کرد. اگر دسترسی به داده را تغییر دهید، فقط دسترسی به داده باید بازسازی شود. اگر UI را تغییر دهید، فقط UI تغییر می کند.

از آنجایی که ماژول ها را می توان به طور جداگانه deploy کرد، به این معنی است که می توان آنها را به طور جداگانه توسعه داد.

فراموش نکنید، آداپتورهایی در سیستم ما به نام primary (driving) نامیده میشود. آنهایی که توسط سیستم ما فراخوانی می شوند secondary (driven) نامیده می شوند. دانستن آن مهم نیست، اما دانستن آن باعث می شود که شما تحصیل کرده به نظر برسید ?


از نظر solution structure، این برای من بهتر کار میکند:

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

فقط منابع را دنبال کنید و مطمئن شوید که از جایی که نباید عبور کنند:



2008 — Onion

این یکی ترسناک خواهد بود، پس خودتان را آماده کنید


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

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

این معماری از پورت ها و آداپتورها بسیار بهبود می یابد. همچنان شامل dependency inversion است. کد را به انتزاع و پیاده سازی تقسیم می کند. پورت ها هنوز بخشی از منطق تجاری هستند. فقط این بار پالرمو لایه Application را از طرح Evans اضافه می‌کند که می‌تواند حاوی چند پورت نیز باشد.

بزرگترین چالش با این معماری، که باعث سردرگمی بسیار می شود، وابستگی بین ماژول ها است.

با این حال، قانون ساده است: هر لایه بیرونی فقط و فقط می تواند به لایه های داخلی بستگی داشته باشد.

خیلی ساده نیست، ها؟ ? من اینطور فکر می کردم. پس بیایید آن پیاز را خلال کنیم ?.

دامنه در وسط قرار دارد. هیچ لایه داخلی درون آن وجود ندارد، بنابراین نباید به هیچ لایه دیگری وابسته باشد.

برنامه فقط دامنه را پوشش می دهد، بنابراین این دقیقاً تنها وابستگی است که باید داشته باشد.

لایه‌های Infrastructure و Presentation در یک سطح هستند، نمی‌توانند به یکدیگر وابسته باشند، اما می‌توانند به Application و Domain وابسته باشند، جایی که تمام اینترفیس‌های مورد نیاز تعریف شده‌اند.


همچنین می توانید ببینید که تمام ماژول های معماری DDD را دارد اما آنها را به گونه ای متفاوت مدیریت می کند. در واقع کار بزرگی می کند! کلید در اینجا، داشتن اجزای وسط است که به ندرت تغییر می کنند و اغلب در لبه ها تغییر می کنند. تغییرات در Application یا هر لایه دیگری روی Domain تأثیر نمی گذارد، فقط لایه های وابسته را تحت تأثیر قرار می دهد. تنها دلیل تغییر دامنه شما زمانی است که منطق کسب و کار تغییر می کند، و این موردی است که به هر حال کل سیستم را تحت تاثیر قرار می دهد.


این که چگونه به نظر می رسد در عمل، composition root شما (تابع Main که در آن همه وابستگی ها ثبت می شوند و ماژول ها با هم ترکیب می شوند) بخشی از لایه ارائه (ASP، WPF، CLI) خواهد بود، بنابراین نمودار شکل زیر را خواهد داشت:

برای شما آشنا به نظر می رسد؟ این معماری N-layered است، اما اجزا به ترتیب متفاوتی هستند.

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


2012 — Clean Architecture

???
There’s a man named Uncle Bob,
He’s the cleanest coder on the job,
With his agile moves and architecture too,
He’ll make your code shine like brand new,
Uncle Bob
???


منظورم اینه که نمیشه بدون ذکر روبرت سی مارتین به سادگی مقاله ای در مورد معماری نوشت.

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

فقط به شوخی، این روزها ایده های اصلی زیادی وجود ندارد، همه از یکدیگر دزدی می کنند.?

دامنه ما یک بار دیگر نام خود را تغییر داد. اکنون Entities است. با این حال، این تنها نیست. این نشاندهنده آن است که شما domain services و مدل های anemic ندارید، اما rich class ها با داده ها و رفتار دارید.

کلاسهای repository و سایر پورت ها از Domain به لایه Application منتقل می شوند. که به نوبه خود نام مناسب تری داشت - Use Cases.

لایه های ارائه و زیرساخت ثابت می مانند. با این حال، مارتین همچنین یک لایه اضافی در بالا اضافه می کند که شامل Frameworks، DLL و سایر وابستگی های خارجی است. این لزوماً به این معنی نیست که DB شما به Entities ارجاع خواهد داشت، بلکه فقط مانع از ارجاع شما از لایه‌های داخلی به آن ابزارهای خارجی می‌شود.


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

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

این نشان می دهد که کاربران با فعال کردن endpoint کنترلر که یک use case را فراخوانی می کند، با یک سیستم ارتباط برقرار می کنند و سپس داده ها را از طریق ارائه دهنده (خطوط سیاه) برمی گرداند. Use case می تواند هر پورت مورد علاقه خود را با رابط (خطوط سبز) فراخوانی کند. در حالی که اجرای واقعی بخشی از لایه های بیرونی (خطوط نارنجی) است.

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

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

معمولاً در ASP ما کامپوننت جداگانه ای برای ارائه دهنده نداریم. این کار توسط کنترلر نیز انجام می شود. بنابراین کل نمودار را می توان در کد به شکل زیر نشان داد:


Other forms of isolation

تمام آن معماری هایی که هدفشان جداسازی یک کد از کد دیگر با تقسیم مسئولیت هاست. با این حال، اشکال دیگری از جداسازی وجود دارد: برش های عمودی، بافت محدود، ماژول ها، میکروسرویس ها و غیره. هدف در اینجا تقسیم کد بر اساس ویژگی ها است.


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

نتیجه

در این مقاله به معماری های N-layer، DDD، Hexagon، Onion و Clean پرداختیم. اینها تنها معماری موجود نیستند. با این حال، مواردی که شرح داده شد، معروف ترین آنها هستند. ممکن است در مورد BCE، DCI و غیره نیز شنیده باشید.


صرف نظر از تفاوت جزئی در جزئیات، تمام معماری ها تقریباً یکسان هستند. همه آنها به یک هدف عمل می کنند - تقسیم مسئولیت ها. همه آنها این کار را با تقسیم کد در لایه های جداگانه انجام می دهند. کل تفاوت در این است که چه اجزایی تعریف شده اند و چه وابستگی هایی بین آن لایه ها وجود دارد.


اکنون که تصویر کامل را دارید، شدیداً شما را تشویق می کنم که این مقاله را یک بار دیگر بخوانید. تفاوت بین معماری را خودتان برجسته کنید. همچنین می توانید سعی کنید و به تنهایی با پروژه ها بازی کنید. چند کلاس با اینترفیس بنویسید، به ارجاعات بین پروژه های خود توجه کنید، اینترفیس ها و پیاده سازی ها در کجا قرار می گیرند، چه access modifier استفاده می شود.

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