اولین نگرانی یک معمار اطمینان حاصل کردن از قابل استفاده بودن یک خانه است نه اینکه آیا خانه از آجر ساخته شده یا ...
- عمو باب
آقای Betrand Meyer در کتاب (Object Oriented Software Construction) بر اساس اصل (SoC) عنوان می کند که متدهای یک شیء باید فقط اجرا کننده دستور (Command) و یا اجرا کننده Query باشندQuery .ها تنها داده برمی گردانند و در وضعیت فعلی سیستم تغییری ایجاد نمی کنند و در مقابل Command ها باعث ایجاد تغییرات در سیستم شده ولی مقداری بر نمی گردانند.
CQRSکه اولین بار توسط آقای Greg Young مطرح گردید، از این اصل برای تعریف یک الگوی ساده استفاده می کند. پیاده سازی این الگو باعث می شود تا بسیاری از دغدغه های معماری نرم افزار (مانند انعطاف پذیری، مقیاس پذیری، تمرکز کامل بر فرآیندهای Domain و.. ) را برطرف کنید. تصویر زیر ساختار کلی الگوی CQRS را نشان می دهد :
نکته : اگرچه تصویر فوق دو پایگاه داده مختلف را در الگوی CQRS نشان می دهد، اما این امر الزامی نیست. CQRS می تواند بر روی یک دیتابیس ولی با دو مدل متفاوت برای نوشتن و خواندن اعمال شود.
Martin Fowler در یکی از پست های وبلاگ خود در سال 2005 گفته است که دستیابی به چنین جدایی همیشه ممکن نیست و خب او حق دارد. مثال خوب برای آن ، برگرداندن آیدی رکوردی است که به تازگی در یک جدول درج شده است. با توجه به این موضوع ابتدا بایستی اطلاعات به کمک بخش commands در دیتابیس ذخیره شود و سپس آیدی رکورد ایجاد شده برای آن برگشت داده شود که عملاً با این معماری، انجام این کار نشدنی است.
معماری CQRS
در این معماری نرم افزار به دو بخش خواندن (Read Side) و بخش نوشتن (Write Side) تقسیم می شود. اشیاء موجود در بخش خواندن تنها مسئول خواندن و بازیابی اطلاعات از دیتابیس بوده و اشیاء موجود در بخش نوشتن، تنها مسئول اجرای Command های دریافتی می باشند. در اغلب سیستم های اطلاعاتی تعداد خواندن اطلاعات از نرم افزار بسیار بیشتر از تعداد نوشتن است. جداسازی این دو بخش شما را قادر می سازد که بر روی هر بخش به طور مستقل و جداگانه کار کنید و پیاده سازی هر کدام را بنا بر نیاز آن انجام دهید. برای مثال هنگام خواندن اطلاعات می توانید از یک پایگاه داده نرمال نشده جهت افزایش سرعت استفاده نمایید و یا نوشتن اطلاعات را در یک پایگاه داده ی NoSQL انجام دهید.
معماری CQRS یا به اصطلاح " تفکیک مسئولیت های نوشتن و خواندن (بر روی دیتابیس) " مفهوم تقسیم را در سطح معماری گسترش می دهد. اما این معماری، به عنوان یک معماری کلی برای یک سیستم نرم افزاری تعریف نمی شود.
همان طور که قبلاً به آن اشاره کردیم با جدا کردن عملیات های خواندن و نوشتن بر روی یک سیستم، می-توانیم سرعت عملکرد آن را افزایش دهیم و از اصل تفکیک نگرانی ها (Separation of Concerns principle) در سیستم های خود پشتیبانی کنیم.
در ادامه به توضیح حالات مختلف این معماری می پردازیم.
معماری CQRS با یک دیتابیس
در این معماری هر دو طرف در حال گفتگو با یک دیتابیس هستند.
از یک دیتابیس رابطه ای یا غیر رابطه ای استفاده می شود.کامندها از دامین برای تغییر وضعیت استفاده می کنند و نتیجه را از طریق لایه persistence در دیتابیس ذخیره می کنند که در php معمولا از یک ORM مثل Eloquent یاDoctrine استفاده می شود.
کوئری ها به طور مستقیم به وسیله یک لایه سبک یعنی data access، به دیتابیس وصل شده و به کمک مکانیزم هایی مثل linq یا اسکریپت های SQL یا حتی strored procedure دیتا را از دیتابیس دریافت می کنند.
این نوع CQRS که از یک دیتابیس استفاده می کند ساده ترین نوع است.
معماری CQRS به کمک دو دیتابیس
در رویکرد Two-database، ما دو پایگاه داده اختصاصی داریم ، یکی برای ذخیره اطلاعات و دیگری برای خواندن آن ها. بخش Commands برای عملیات نوشتن، پایگاه داده نوشتن را بهینه کرده و بخش Queries، پایگاه داده خواندن را برای انجام عملیات خواندن بهینه کرده است.
با هر تغییر وضعیت توسط بخش Commands ، داده های اصلاح شده بعد از نوشته شدن در دیتابیس نوشتن (Write Database) باید به دیتابیس خواندن (Read Database) فرستاده شده تا با استفاده از یک الگوی منسجم و هماهنگ در هر دو پایگاه داده ذخیره شود.
این معماری باعث بهبود عملکرد نرم افزار در قسمت جستجوهای یک نرم افزار می شود و این موضوع را می توان به عنوان یک نکته مثبت تلقی کرد زیرا کاربران یک نرم افزار معمولاً بیشتر از نوشتن، وقت خود را با خواندن داده ها می گذرانند.
معماری CQRS با استفاده از روش event-sourcing
این پیچیده ترین معماری CQRS است. روش event-sourcing (منبع یابی رویدادی) کاملاً متفاوت از ذخیره سازی داده ها نسبت به دو معماری است که قبلاً ارائه شده است.
در این حالت ما وضعیت فعلی entityها را در یک دیتابیس نرمالایز شده ذخیره نمی کنیم. ما فقط تغییرات entity ها را در طول زمان ذخیره می کنیم. تاریخچه ای از تغییرات را خواهیم داشت که به آن event store می گوییم. می توانیم با مکانیزمی وضعیت فعلی هر entity را در اختیار داشته باشیم.
این روش به ما کمک بزرگی می کند تا وضعیت یک object را در گذشته به راحتی پیدا کنیم و از آن می توان به عنوان یک Logger نیز استفاده نمود چون جزء به جزء تغییراتِ وضعیت سیستم، در آن ثبت شده است. از آنجایی که دیتا به صورت سریالایز شده ذخیره می شود، بارگذاری آن نیز با سرعت بالایی انجام خواهد شد. این حالت پیچیده ترین نوع CQRS است، ولی بهینه ترین می باشد.
نکته : در این روش فقط می توانیم Eventهای جدیدی به دیتابیس خود اضافه کنیم و قادر به ویرایش و حذف Eventها نیستیم.
استفاده از این روش مزایای زیادی را به همراه دارد که برخی از آن ها عبارتند از :
- داشتن مجموعه ای از تمامی وضعیت های مختلف یک موجودیت که می تواند در پروژه هایی که اهمیت تغییر داده ها زیاد است، به کار بیاید.
- دیگر نیازی به لایه ORM برای ذخیره سازی اطلاعات نیست.
- می توانیم وضعیت هر موجودیتی را در هر برهه از زمان بازسازی کنیم. این کار برای رفع اشکال (Debugging) بسیار مفید است.
- خطایابی سیستمی که در حال کار است، با تکرار رخدادهایی که منجر به خطا شده اند، آسان می شود.
- جهت بازیابی اطلاعات می توانیم بیش از یک دیتابیس داشته باشیم .
سخن آخر
از آنجایی که استفاده از هر معماری در کنار نکات و مزایا، معایبی هم دارد لذا به توضیح برخی از معایب آن می¬ پردازیم :
- با وجود این جداسازی بین کوئری و کامند، پیچیدگی نرم افزار زیاد می شود.
- اگر از حالت دو دیتابیس استفاده کنیم، پیچیدگی درسمت دیتابیس بیشتر می شود.
- اگر از حالت سوم یعنی event-sourcing استفاده کنیم، هزینه و نگهداری بیشتری خواهیم داشت.