معماری Onion

عبارت Onion Architecture اولین بار توسط آقای Jeffery Palermo در سال ۲۰۰۸ مطرح گردید.

معماری Onion

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

معماری لایه ای

همانطور که می دانید جهت جداسازی بخش های مختف نرم افزار و پیاده سازی اصل SoC ، نرم افزار به لایه های مختلف تقسیم می شود که هر یک بر روی یک بخش از نرم افزار تمرکز دارند. این معماری با نام معماری لایه ای (Layered Architecture) شناخته می شود. معماری لایه ای سنتی اغلب با رویکرد داده محوری (Data-Driven) طراحی و استفاده می شد. تصویر زیر یک معماری لایه ای سنتی را نشان می دهد :

Onion Architecture

تصویر از کتاب Microsoft .Net: Architecting Applications for the Enterprise

گذشت زمان و پیچیده تر شدن نرم افزارها تغییرات عمده ای در معماری لایه ای پدید آورد. رشد تفکر DDD و ایده ی تمرکز بر روی Domain و ایزوله کردن آن باعث گردید تا لایه ی Domain به عنوان قلب نرم افزار شناخته شده و جزئیات زیرساختی از دید آن مخفی بماند. هرچند DDD اجباری بر استفاده از معماری خاصی ندارد، اما بعضی معماری ها مانند معماری پیاز (Onion Architecture) توانستند موفقیت بیشتری در پیاده سازی اصول DDD و ایزوله کردن Domain داشته باشند.

معماری پیاز (Onion Architecture)

عبارت “Onion Architecture” اولین بار توسط آقای Jeffery Palermo و در سال ۲۰۰۸ مطرح گردید. این معماری همچنین با نام Clean Architecture یا معماری تمیز توسط نویسندگان دیگری نیز مطرح شده است. ساختار یک معماری لایه ای پیاز را در شکل زیر مشاهده می کنید:

Onion Architecture

تصویر از وب سایت planetgeek.ch

در این معماری هر لایه، به لایه ی داخلی تر وابسته بوده و به آن دسترسی دارد. لایه های داخلی هیچ Reference و اشاره ای به لایه های بالاتر ندارند. هرچند می توانند با ارسال Event آنها را از وقوع رویدادی باخبر کنند. همانطور که در شکل مشاهده می کنید لایه ی Domain، داخلی ترین لایه بوده و به هیچ لایه ی بیرونی وابستگی ندارد. این معماری با تکیه بر اصل Dependency Inversion تمام وابستگی های لایه ی Domain را در قالب Interface ها در اختیار آن قرار می دهد تا این لایه از جزئیات پیاده سازی و وابستگی به ابزارهای زیر ساختی در امان باشد.

Domain Layer

این لایه که به عنوان قلب نرم افزار شناخته می شود، مسئول پیاده سازی مفاهیم و قوانین Business می باشد. این لایه در قالب Entity ها، Value Object ها و سرویس ها پیاده سازی می شود. این لایه به هیچ لایه ی دیگری وابستگی ندارد و در صورت نیاز به لایه های زیرساختی (مانند دیتابیس) تنها به Interface آنها دسترسی پیدا می کند.

Application Service Layer

به طور کلی این لایه منطق نرم افزار یا به اصطلاح Application Logic را مدیریت و پیاده سازی می کند. این لایه از متدهایی تشکیل شده که بر اساس Use Case های سیستم تعریف شده اند. لایه ی Presentation درخواست های خود را به لایه ی Application ارسال کرده و این لایه با توجه به درخواست دریافت شده، از لایه های Domain و لایه های زیر ساختی برای انجام درخواست استفاده می کند. Application Service ها اغلب به عنوان نقطه ورودی (Entry Point)  در سیستم شناخته می شوند. اگر چه در بسیاری از نرم افزارها، یک لایه Service مانند سرویس های SOAP و یا RESTful بر روی Application Service ها نیز قرار می گیرد که مسئول پیاده سازی زیر ساخت جهت ارتباط با Client ها می باشد.

تفاوت Application Logic و Domain Logic چیست ؟

 Application Logic عبارت است از قدم های مشخص جهت اجرای یک Use Case در سیستم. این قدم ها می تواند شامل بازیابی چند شیء Domain از دیتابیس، استفاده از آنها برای اجرای یک فرآیند، ارسال آنها برای ذخیره شدن در دیتابیس، صدا زدن سرویس های دیگر و … باشد. Application Service ها در واقع اعمال Business را انجام نمی دهند اما قدم های مختلف جهت اجرای آنها را می دانند و لایه های دیگر را جهت انجام شدن آن مدیریت می کنند. اما Domain Logic شامل مفاهیم و قوانین Business بوده و کاملا از جزئیات فنی جدا می باشد.

Infrastructure Layer

تمام زیر ساخت های داده ای و تکنولوژی محور نرم افزار در این لایه پیاده سازی می شوند. دسترسی به داده ها (Data Access)، دسترسی به وب سرویس های متفرقه (مانند ارسال پیامک و …) و همچنین زیر ساخت های Cross-Cutting مانند  Logging و Caching در این لایه پیاده سازی می شوند.

Presentation Layer

این لایه مسئول نمایش اطلاعات و همچنین مهیا کردن ابزاری برای دریافت اطلاعات از کاربر می باشد. این لایه داده ها را اغلب از لایه ی Application Service در قالب View Model دریافت کرده و نمایش می دهد.

حال که با ساختار کلی معماری پیاز آشنا شدید، بد نیست کمی با الگوی CQRS آشنا شویم.

الگوی CQRS

آقای Betrand Meyer در کتاب Object Oriented Software Construction بر اساس اصل SoC عنوان می کند که متد های یک شیء باید فقط اجرا کننده ی دستور (Command) و یا اجرا کننده ی Query باشند. Query ها تنها داده برمی گردانند و در وضعیت فعلی سیستم تغییری ایجاد نمی کنند و در مقابل Command ها باعث ایجاد تغییرات در سیستم شده ولی مقداری بر نمی گردانند. CQRS  که اولین بار توسط آقای Greg Young مطرح گردید، از این اصل برای تعریف یک الگوی ساده استفاده می کند. پیاده سازی این الگو باعث می شود تا بسیاری از دغدغه های معماری نرم افزارتان (مانند انعطاف پذیری، مقیاس پذیری، تمرکز کامل بر فرآیندهای Domain و …) را برطرف کنید. تصویر زیر ساختار کلی الگوی CQRS را نشان می دهد :

Onion Architecture

تصویر از کتاب Exploring CQRS and Event Sourcing

همانطور که در تصویر مشاهده می کنید، نرم افزار به دو بخش خواندن (Read Side) و بخش نوشتن (Write Side) تقسیم شده است. اشیاء موجود در بخش خواندن تنها مسئول خواندن و بازیابی اطلاعات از دیتابیس بوده و اشیاء موجود در بخش نوشتن تنها مسئول اجرای Command های دریافتی می باشند.  در اغلب سیستم های اطلاعاتی تعداد خواندن اطلاعات از نرم افزار بسیار بیشتر از تعداد نوشتن است. جدا سازی این دو بخش شما را قادر می سازد که بر روی هر بخش به طور مستقل و جداگانه کار کنید و پیاده سازی هر کدام را بنا بر نیاز آن انجام دهید. برای مثال هنگام خواندن اطلاعات میتوانید از یک پایگاه داده ی نرمال نشده جهت افزایش سرعت استفاده نمایید و یا نوشتن اطلاعات را در یک پایگاه داده ی NoSQL انجام دهید.

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

عموما Command ها با منطق پیچیده ای جهت چک و اطمینان از صحت فرآیندهای سیستم درگیر هستند و این در حالی است که Query ها تنها عمل ساده ی خواندن را انجام می دهند. پیاده سازی یک مدل جهت برآورده ساختن نیازهای هر دو بخش سیستم اغلب صد در صد موفقیت آمیز نخواهد بود و ممکن است باعث شود تا گاهی مدل Domain  را بنابر نیاز یک Query تغییر دهید. در رویکرد DDD هر Bounded Context می تواند معماری مخصوص به خود را داشته باشد. با توجه به این رویکرد، CQRS را نباید الگویی برای کل سیستم در نظر گرفت، بلکه CQRS الگویی است که به یک و یا چند Bounded Context با توجه به نیاز اعمال می گردد.

نقش دستورات و رویدادها (Commands Events) در CQRS

استفاده از Command ها امری ضروری در پیاده سازی CQRS می باشد. هر Command در واقع درخواستی برای انجام عملیاتی در سیستم می باشد. برای مثال “مشتری با کد X را از سیستم حذف کن” و یا “رمز عبور کاربر Y به عبارت Z تغییر یابد” . Command ها معمولا یک بار و توسط یک شیء پردازش می شوند.

رویدادها (Events) نیز جنبه ی اطلاع رسانی دارند و از اتفاقی که در سیستم افتاده است خبر می دهند. برای مثال “سفارش با کد X در سیستم ثبت گردید” و یا “تعداد X از کالای Y از انبار خارج گردید” . Event ها می توانند چندین بار و توسط چندین شیء پردازش شوند.

هم رویدادها و هم دستورات در قالب پیام (Message) هستند و جهت تبادل اطلاعات بین اشیاء استفاده می شوند. در رویکرد DDD این پیام ها در قالب پیام های معنی دار برای Business بوده و بخش های مختلف سیستم را قادر می سازد تا با دریافت آنها، عمل مخصوصی را جهت تکمیل فرآیند های Business انجام دهند. در مدل CQRS می توان از پایگاه های داده ی مختلفی در بخش Read و Write استفاده کرد. بخش Write دستورات را از UI دریافت کرده و پس از انجام آنها، با ارسال Event بخش Read را جهت به روز رسانی پایگاه داده (و یا هر عمل متناسب دیگری) مطلع می سازد :

Onion Architecture

تصویر از کتاب Exploring CQRS and Event Sourcing

اگرچه پیاده سازی CQRS در پروژه های غیر DDD نیز امکان پذیر است اما نوع ماهیت پروژه های DDD و پیچیدگی آنها باعث شده است تا این الگو اغلب در پروژه های Domain-Driven پیاده سازی شود.

Onion Architecture معماری onion آموزش معماری نرم افزار CQRS آموزش Onion Architecture آموزش معماری تمیز