الگوی CQRS چیست؟
تفکیک مسئولیت فرمان کوئری (Command Query Responsibility Segregation)، الگویی است و وظایف سرویس خواندن و نوشتن را از هم جدا میکند که اولین بار توسط گرگ یانگ مطرح شد که میتوان از مدلی متفاوت برای بروزرسانی اطلاعات نسبت به مدلی که برای خواندن اطلاعات استفاده میشود، استفاده کرد و عملکرد، مقیاسپذیری و امنیت را به حداکثر میرساند. CQRS مدل مفهومی را به مدل جداگانه برای بروزرسانی و نمایش معرفی میکند و عملیات خواندن و بروزرسانی برای ذخیره دادهها را جداسازی میکند. در برخی از حالات جداسازی میتواند ارزشمند باشد، اما برای بیشتر سیستمهای CQRS پیچیدگی خطرناکی را ایجاد میکند.
این مدل یک رویکرد اصلی است که افراد برای تعامل با یک سیستم اطلاعاتی استفاده میکنند که به عنوان یک انبار داده رفتار میکند. در واقع یک مدل ذهنی از ساختار رکورد است که میتوان در آن جا اعمالی مانند ایجاد رکورد جدید، خواندن رکوردها، بروزرسانی رکوردهای موجود حذف رکوردها را انجام داد و همه تعاملات در مورد ذخیره و بازیابی رکوردها هستند. انعطافپذیری حاصل از migrate شدن CQRS سبب میشود سیستم در طول زمان بهتر تکامل یابد و از ایجاد تداخلهای ادغام در سطح دامنه توسط بروزرسانی دستورات جلوگیری میکند. معمولا فعالیت خواندن بیشتر از فعالیت نوشتن است و فعالیت خواندن تغییر ناپذیر است. بنابراین نسخههای مربوط به خواندن داده را میتوان در نقاط مختلف منتشر کرد. این رویکرد سبب میشود تا کاربران دادههایی که به آنها نزدیکتر هست را دریافت کنند و سبب ایجاد یک برنامه سازمانی کاراتر میگردد.
منظور از مدلهای جداگانه، مدلهای شی متفاوت است که احتمالا در فرایندهای منطقی متفاوتی و روی سختافزارهای جداگانه ایجاد میشوند. به عنوان مثال میتوان یک صفحه وب را در نظر گرفت که با استفاده از مدل کوئری ارائه شده است. اگر در ابتدا تغییری ایجاد شود که تغییر به مدل فرمان جداگانه برای پردازش هدایت شود، تغییر حاصل به مدل کوئری داده میشود تا حالت بروزرسانی شده را ارائه دهد. مدلهای درون حافظه ممکن است پایگاه داده یکسانی را به اشتراک بگذارند. در برخی موارد پایگاه داده به عنوان ارتباط بین دو مدل عمل میکند. اگر چه ممکن است آنها از پایگاه دادههای جداگانه استفاده کنند، آنها پایگاه داده سمت کوئری را به صورت بلادرنگ به پایگاه داده گزارشی تبدیل میکنند. در این موارد باید مکانیزمهای ارتباطی بین دو مدل یا پایگاه دادههای آنها وجود داشته باشد.
مسئله
در معماری سنتی از یک مدل داده یکسان برای کوئری و بروزرسانی پایگاه داده استفاده میشود که رویکردی آسان است و برای عملیات CRUD به خوبی کار میکند. در برنامههای پیچیدهتر این رویکرد دشوار است. مثلا در سمت خواندن برنامه ممکن است کوئریهای مختلفی را انجام دهد و اشیای انتقال داده را با اشکال مختلف برگرداند و نگاشت اشیا ممکن است پیچیده و دشوار شود. در سمت نوشتن مدل ممکن است اعتبارسنجی و منطق کسب و کار پیچیده را پیادهسازی کند. اغلب یک عدم تطابق بین خواندن و نوشتن داده وجود دارد، مانند ستونها یا ویژگیهای اضافی که باید به درستی بروزرسانی شوند حتی اگر به عنوان بخشی از یک عملیات مورد نیاز نباشند.
زمانی که عملیات به صورت موازی روی یک مجموعه داده یکسان انجام میشوند، ممکن است اختلاف داده رخ دهد. رویکردهای سنتی به دلیل بارگذاری روی ذخیره داده و لایه دسترسی به داده و کوئریهای پیچیده مورد نیاز برای بازیابی اطلاعات میتوانند تاثیر منفی بر عملکرد داشته باشند و مدیریت امنیت و مجوزها میتواند پیچیده باشد زیرا هر موجودیتی که تحت هر دو عمل خواندن و نوشتن است، ممکن است دادهها را با محتوای نادرستی نمایش دهد.
راه حل
مدل CQRS خواندنها و نوشتنها را در مدلهای مختلف با استفاده از دستورات بروزرسانی داده و کوئری برای خواندن داده جدا میکند. دستورات باید مبتنی بر وظیفه باشند و داده محور نباشند. دستورات ممکن است در یک صف برای پردازش ناهمگام قرار گیرند و به صورت همگام پردازش نمیشوند. کوئریها هرگز پایگاه داده را تغییر نمیدهند. یک کوئری یک شی انتقال داده را برمیگرداند که هیچ اطلاعی از دامنه را کپسوله نمیکند.
داشتن کوئری و مدلهای بروزرسانی جداگانه طراحی و پیادهسازی را آسان میسازد. برای جداسازی بیشتر میتوان خواندن داده را از نوشتن داده به صورت فیزیکی جدا کرد. در این صورت پایگاه داده خوانده شده میتواند از طرح داده خودش برای بهینهسازی کوئری استفاده کند یا ممکن است از نوع متفاوتی از ذخیره داده استفاده کند. مثلا ممکن است پایگاه داده نوشته شده یک پایگاه داده رابطه ای باشد در حالی که پایگاه داده خوانده شده یک سند پایگاه داده باشد.
اگر از پایگاه دادههای خواندن و نوشتن مجزا استفاده شود، باید به صورت هماهنگ نگهداری شوند که معمولا با انتشار مدل نوشتن یک رویداد، هر زمان که پایگاه داده را بروزرسانی میکند انجام میشود. ذخیره خواندن میتواند یک نسخه فقط خواندنی از مخزن نوشتن باشد یا مخزن های خواندن و نوشتن میتوانند ساختارهای متفاوتی با یکدیگر داشته باشند. با استفاده از چند نسخه فقط خواندنی میتوان عملکرد کوئری را افزایش داد. به ویژه در نسخههای توزیع شده که نسخههای فقط خواندنی نزدیک نمونههای برنامه قرار دارند. جداسازی مخزنهای خواندن و نوشتن همچنین سبب مقیاسبندی مناسب و متناسب با بار میگردد. مثلا معمولا مخازن خواندن بار بیشتری نسبت به مخازن نوشتن دارند.
انواع معماری CQRS
مدل CQRS با یک پایگاه داده: در یک پایگاه داده CQRS هر دو سمت کوئری و فرمان به یک پایگاه داده مرتبط هستند. دستورات use-caseها را در دامنه اجرا میکنند که وضعیت موجودیت را تغییر میدهد. سپس موجودیت در پایگاه داده ذخیره میشود. کوئریها به طور مستقیم از طریق دسترسی به لایه داده اجرا میشوند.
مدل CQRS با دو پایگاه داده: دو پایگاه وجود دارد که یکی برای عملیات نوشتن و دیگری برای عملیات خواندن است. سمت فرمان از پایگاه داده نوشتن بهینه شده برای عملیات نوشتن استفاده میکند و سمت کوئری از پایگاه داده خواندن بهینه شده برای عملیات خواندن استفاده میکند. با تغییر هر حالت توسط فرمان، داده تغییر یافته باید از پایگاه داده نوشتن به پایگاه داده خواندن به عنوان یک تراکنش هماهنگ واحد در میان هر دو پایگاه داده یا با استفاده از الگوی سازگاری احتمالی منتقل میشود. این معماری سبب بهبود عملکرد سمت کوئری نرمافزار میشود زیرا کاربران وقت بیشتری را برای خواندن دادهها نسبت به نوشتن دادهها صرف می کنند.
مدل CQRS برای یافتن منبع رویداد: پیچیدهترین معماری CQRS است و وضعیت برنامه به صورت یک توالی از رویدادها ذخیره میشود که هر رویداد نمایانگر یک مجموعه تغییر برای داده است. رویداد فعلی با اجرای دوباره رویدادها ایجاد میشود. میتوان از یک رویداد برای اطلاعرسانی سایر اجزا به ویژه برای اطلاع رسانی مدل خواندن استفاده کرد. در این رویکرد فقط موجودیتهای وضعیت فعلی ذخیره نمیشوند و هر حالتی که برای موجودیت رخ داده است به عنوان تصاویر لحظهای ذخیره میشوند. مدل خواندن از رویدادها برای ایجاد یک تصویر لحظهای وضعیت فعلی استفاده میشود که برای کوئری کاراتر است. موجودیتها به عنوان دادههای نرمال ذخیره نمیشوند بلکه به عنوان تغییرات مستقیم با زمان مهر یک رویداد ذخیره میشوند. فرمانها میتوانند موجودیت فعلی را تغییر دهند. تغییرات رویداد جدید را تولید میکنند که در منبع رویداد ذخیره میشوند. بنابراین وضعیت فعلی موجودیت در یک پایگاه داده خواندن قرار داده میشود تا خواندن بسیار سریع صورت گیرد. یافتن منبع رویداد سبب افزایش پیچیدگی میگردد.
مؤلفههای مورد استفاده در مدل CQRS
سمت Query: باید مسئول و بهینه شده برای خواندن داده باشد. کوئریها داده را میخوانند و سپس آنها را در فرم مورد نیاز به لایه presentation نگاشت میکنند. این فرمها به عنوان اشیای انتقال داده شناسایی میشوند.
سمت Commands: مسئول و بهینه شده برای نوشتن داده است. دستورات موارد مورد استفاده ر اجرا میکنند، وضعیتهای موجودیتها را تغییر میدهند و آنها را ذخیره میکنند.
موارد استفاده از مدل CQRS
موارد عدم استفاده از مدل CQRS
چالشهای پیادهسازی مدل CQRS
پیچیدگی: مدل CQRS برای پایه سادگی بنا نهاده شده است ولی ممکن است منجر به طراحی برنامه پیچیدهتر منجر میشود. به خصوص اگر شامل الگوی منبع رویداد باشد.
پیامرسانی: اگر چه مدل CQRS نیاز به پیامرسانی ندارد، استفاده از پیامرسان برای پردازش دستورات و انتشار رویدادهای بروزرسانی شده مرسوم است. برنامه باید از پیامهای خراب و تکراری جلوگیری کند.
ثبات احتمالی: اگر پایگاه داده خواندن و نوشتن از یکدیگر جدا شوند، داده خوانده شده ممکن است قدیمی شود. مدل خواندن ذخیره شده باید بروزرسانی شود تا تغییرات در مدل نوشتن ذخیره شده منعکس شود. زمانی که کاربر درخواستی را بر اساس داده خوانده شده قدیمی میدهد، تشخیص دشوار میباشد.
مزایای مدل CQRS
معایب مدل CQRS
«این مطلب، بخشی از تمرینهای درس معماری نرمافزار در دانشگاه شهیدبهشتی است»
منابع و مراجع: