وبلاگ روزنوشت های من

از سیر تا پیاز معماری پیازی. یک معماری تمیز!

معماری پیازی یک نوع معماری لایه ای ست که قابلیت نگهداری، تست پذیری و توسعه پذیری نرم افزارها را به آسانی برای شما فراهم می‌کند. این معماری تاکید زیادی روی وابستگی ها و جدایی منطق، عملیات ها، سرویس ها و رابط کاربری دارد. در این نوع معماری، هسته سیستم که پایین ترین و اساسی ترین لایه در این معماری ست، به هیچ کدام از لایه های دیگر وابستگی ندارد، و این لایه های دیگر هستند که به هسته سیستم از طریق اینترفیس ها وابستگی دارند. تعریف این معماری اولین بار در سال 2008 توسط Jeffrey Palermo در وبلاگش ارائه شد، که البته مفهموم عجیبی نبود! ما قبلاً از معماری های لایه ای زیادی برای پیاده سازی نرم افزارها استفاده کرده بودیم، اما چیزی که در این معماری روی آن تاکید زیادی می‌شد، عدم وابستگی لایه های درونی به لایه های بالاتر با استفاده از اینترفیس ها بود. این معماری به شدت به مفهوم Dependency Inversion principle و تزریق وابستگی ها تکیه دارد.

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

هسته سیستم شما را تشکیل می‌دهد. در این لایه موجودیت های Domain را تعریف کنید. مدل های اساسی سیستم که نرم افزار برای حل مسائل آن ها ساخته می‌شود در این قسمت قرار می‌گیرند. مفاهیم Domain-Driven Design در این معماری هم کاربرد دارند. Entityهایی که در اینجا تعریف می‌کنید نباید هیچ گونه وابستگی خارجی داشته باشند. کلاس های مدل باید به صورت POCO و تا جایی که می‌شود بدون کٌدهای سنگین و پیچیده نوشته شوند.

تعریف Entity : موجودیتی در سیستم که شامل قسمتی از داده های با ارزش ماست و با دیگر موجودیت های سیستم رابطه دارد.

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

لایه Repository

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

لایه Services

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

  • لایه Services.Validation : در اینجا شما باید قوانین بیزنسی موجودیت های سیستم را بررسی کنید. هر عملیاتی که موجب تغییر در داده های یکی از موجودیت های لایه Domain شود، باید برای اطمینان از ورود داده های درست، در این لایه اعتبارسنجی شده و بعد برای Service مورد نظر فرستاده شود.
  • لایه Services.Dto : اینجا کلاس های که مسئولیت جابجایی دیتا بین لایه سرویس و لایه های بیرونی تر دارند، قرار می‌گیرند. DTOها کلاس هایی ساده شامل پراپرتی های مورد نیاز برای انجام یک نوع عملیات خاص در سرویس ها هستند. مثلاً اگر قرار است متد Create برای ایجاد موجودیت Customer بنویسید، یک CreateCustomerDto می‌سازید که فقط شامل پراپرتی هایی ست که در زمان ایجاد مشتری، مورد نیاز هستند، نه کمتر و نه بیشتر.
  • لایه Services.Factory : در این کارخانه ها، DTOهای سرویس ها را تبدیل به Entityهایی می‌کنیم که لایه Repository آن ها را می‌شناسد! داده هایی که کاربر وارد کرده، ممکن است برای ایجاد یا بروزرسانی یک Entity کافی نباشد، برای همین Factoryها را می‌نویسیم تا متادیتاهایی مثل CreateDate یا هر نوع اطلاعات اضافی و موردنیاز دیگر را به Entityها اضافه کنیم تا پس از آماده سازی در سرویس مورد نظر استفاده شود.

لایه Presentation

این لایه همان پوست پیاز است! لایه ای که کاربر مستقیماً با آن سر و کار دارد. البته این لایه حتماً UI نیست. مثلا در معماری ماکروسرویس، این لایه می‌تواند یک Web API باشد که در حال استفاده از لایه Services است. نکته ای باید در نظر بگیرید، عدم وابستگی این لایه به لایه های درونی تر، جز لایه Services است. در واقع این لایه، فقط باید به اینترفیس های لایه Services و البته DTOها وابستگی داشته باشد.

ساختار یک پروژه پیازی!

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

در ریشه پروژه خود، پوشه ای به نام core ایجاد کنید. این پوشه باید شامل Domain Models و اینترفیس های هسته سیستم شما باشد که هر کدام، در پوشه های جداگانه در پوشه core قرار خواهند گرفت. در کنار پوشه core یک پوشه دیگر در روت پژه خود بسازید به نام infrastructure که داخل این پوشه لایه services با زیرمجموعه های آن، و لایه Repository را در پوشه های مجزا بسازید. در نهایت برای ایجاد UI یا APIهای خود پوشه ای به نام presentation در روت پروژه بسازید و APIها یا UIهایی که اپلیکیشن شما ارائه می‌دهد در آن قرار دهید.

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