Command and Query Responsibility Segregation که بهاختصار CQRS نامیده میشود. یعنی جداکردن وظیفه Commandها و Queryها از یکدیگر یا به عبارتی جداکردن مسئولیت Read کردن و Write کردن.
همانطور که در شکل مشاهده میکنید، client ما از سرویسهای جداگانهای برای read کردن و write کردن اطلاعات استفاده میکند و حتی databaseهای آنها نیز با یکدیگر متفاوت است. در سیستمهایی که بهصورت Event Sourcing کار میکنند این اصل بسیار لازم است. برای نمایش اطلاعات ممکن است ما به تجمیعی از رکوردهای دیتابیس که insertها در آن انجام شده است، نیاز داشته باشیم. به همین دلیل هنگام insert کردن فرایند projection روی دادهها (eventها) انجام شده و QueryModelها را برای ما میسازد که بعداً هنگام read از آنها استفاده کنیم و از سرعت خوبی برخوردار باشیم. لازم به ذکر است که EventSourcing و Projection جزئیات بسیاری دارند که در این مقاله به آنها پرداخته نمیشود. اما مطالعه آن درک بهتری در CQRS به ما خواهد داد.
اجازه دهید در ابتدا از طرح یک مشکل، بهعنوان صورتمسئله شروع کنیم تا ضرورت درک و استفاده از تعریف را بهتر درک کنیم.
فرض کنیم سایتی داریم که برای آن تبلیغات عمومی طراحی شده و کاربران سامانه میخواهند در سایت و در یک مسابقه شرکت کنند. باتوجهبه این که در اواخر مهلت پایان شرکت در مسابقه، کاربران زیادی هم زمان اقدام به مشارکت میکنند و مدیران سایت نیز میخواهند میزان مشارکت کاربران و درصد پاسخ صحیح را بررسی کنند.
وقتی کاربر در مسابقه شرکت میکند، جواب آن در جدول T ذخیره میشود. فرض کنیم جدول T اطلاعات زیادی از سالهای گذشته تا کنون داشته و تعداد رکوردهای خیلی بالایی دارد. از طرفی مدیر سیستم نیز از جدول T گزارشهای آماری میخواهد. گزارش آماری یعنی توابعی از جمله Aggregateها مثل Sum و Average و … که همگی مصرف CPU بالایی دارند. در نتیجه در بانک اطلاعاتی که وظیفه ثبت و نگهداری این اطلاعات را دارد شاهد کندی زیادی خواهیم بود.
برای این مشکل شاید راهحلهای زیادی وجود داشته باشد و به این نکته باید توجه کرد که سیستمها باتوجهبه حجم اطلاعات، اهمیت کاری که انجام میدهند و تعداد کاربران یا تراکنشهای هم زمانی که دارند، با یکدیگر کاملاً متفاوت هستند. در نتیجه ادبیات و نحوه رویکرد در هرکدام از آنها متفاوت خواهد بود. اما در این مقاله ذهن خود را بر حل مسئله توسط CQRS معطوف خواهیم کرد.
راهحل پیشنهادی این است که ما تمام پاسخها را در یک جدول ثبت کنیم و از جدول دیگری اطلاعات را به مدیر سیستم نمایش دهیم. حتی بهتر است اطلاعاتی که write میشوند در یک database و read کردن اطلاعات از یک دیتابیس دیگر باشد. اما انجام این کار چه فایدهای برای ما دارد؟
یکی از مسائل مهم در برنامهنویسی استفاده از ابزار مناسب برای اجرای کار موردنظر است. شاید برای insert کردن اطلاعات بتوان از یک دیتابیس Relational استفاده کرد که مسائل مربوط به ACID را نیز حتماً باید در نظر گرفت؛ اما ممکن است هنگام گزارشگیری در لحظه، درگیر چندین join شویم و یا به دلیل search در یک متن طولانی دچار کندی در بخش search کردن شویم و نمایش گزارشها از یک دیتابیس document base برای ما راحتتر و سریعتر باشد.
ممکن است نوشتن اطلاعات در یک دیتابیس باشد؛ اما برای خواندن اطلاعات از چندین سیستم برای حالت read-only استفاده کنیم که پردازنده سیستم write کننده اطلاعات را درگیر نکند.
حال اگر ما کلاسهایی که مسئولیت Read و Write آنها جداگانه باشند، طراحی کرده باشیم. میتوانیم بدون وابستگی به تکنولوژی این دو دیتابیس رو جدا از هم دانسته و استفاده مفیدتر و بهتری را انجام دهیم.
مزایای استفاده از CQRS
در ادامه به چند مورد از مزایای CQRS میپردازیم و با آنها بیشتر آشنا میشویم.
Scalability
برای مثال شما از بانک اطلاعاتی SQL Server استفاده کرده و چندین Node ایجاد میکنید. یکی از این Nodeها فقط برای Write کردن و همسان نگهداشتن سایر Nodeها استفاده میشود. Nodeهای درگیر نیز فقط عملیات read را پاسخ میدهند. در نتیجه برای بخشهای مختلف میتوان Nodeهای مختلفی گذاشت و میزان بار روی هر سیستم را کنترل کرد. این که برای هر کاری به کدام database وصل شویم در CQRS قابلکنترل است. همچنین اضافهکردن Nodeهای مختلف در database تغییری را سمت کد نخواهد آورد و بهراحتی قابل افرایش خواهد بود.
Performance
فرض کنید شما برای read کردن اطلاعات از یک database که مکانیزم InMemory دارد مثل Redis استفاده میکنید. سرعت خواندن از Memory بسیار بالاتر از خواندن از هارد است. حتی هاردهای SSD. در کل فرایند IO بسیار هزینه برخواهد بود. در نتیجه write کردن فایل را بهصورت async به بخش Command میسپاریم و خواندن اطلاعات را از بانک اطلاعاتی InMemory خود میخوانیم. در نتیجه سرعت خواندن اطلاعات ما بسیار بالاتر میرود.
Simplicity
سادگی یکی از مزایای این اصل است. چون شما وقتی که مسئولیت خواندن و نوشتن را جدا میکنید درواقع اصل Single Responsible بودن را رعایت کردهاید. رعایت این اصل یعنی شما برای خواندن و یا نوشتن Use-Case جداگانهای دارد و در صورت تغییرکردن یکی از آنها لازم نیست مورد دیگر تغییری داده شود یا اگر تکنولوژی یکی از آنها تغییر کند لازم نیست بخش دیگر تغییری کند.