مقدمه
مخفف Command and Query Responsibility Segregation است، الگویی که عملیات خواندن و به روز رسانی را برای یک ذخیره داده جدا می کند. پیاده سازی CQRS در برنامه شما می تواند عملکرد، مقیاس پذیری و امنیت آن را به حداکثر برساند. انعطافپذیری ایجاد شده با مهاجرت به CQRS به سیستم اجازه میدهد در طول زمان بهتر تکامل یابد و از ایجاد تداخل ادغام در سطح دامنه توسط دستورات بهروزرسانی جلوگیری میکند[1].
زمینه و مشکل
در معماری های سنتی، از همان مدل داده برای پرس و جو و به روز رسانی پایگاه داده استفاده می شود. این ساده است و برای عملیات اولیه CRUD به خوبی کار می کند. با این حال، در برنامه های پیچیده تر، این رویکرد می تواند دشوار باشد. به عنوان مثال، در سمت خواندن، برنامه ممکن است پرس و جوهای مختلفی را انجام دهد و اشیاء انتقال داده (DTO) را با اشکال مختلف برگرداند. نقشه برداری شی می تواند پیچیده شود. در سمت نوشتن، مدل ممکن است اعتبار سنجی پیچیده و منطق تجاری را پیاده سازی کند. در نتیجه، می توانید با یک مدل بیش از حد پیچیده مواجه شوید که بیش از حد کار می کند.حجم کار خواندن و نوشتن اغلب نامتقارن است، با عملکرد و مقیاس بسیار متفاوت الزامات[1].
اغلب یک عدم تطابق بین نمایش خواندن و نوشتن داده ها وجود دارد، مانند ستون ها یا ویژگی های اضافی که باید به درستی به روز شوند، حتی اگر به عنوان بخشی از یک عملیات مورد نیاز نباشند.
زمانی که عملیات به صورت موازی روی یک مجموعه از داده ها انجام شود، اختلاف داده ها ممکن است رخ دهد.
رویکرد سنتی می تواند به دلیل بارگذاری روی ذخیره داده ها و لایه دسترسی به داده ها و پیچیدگی پرس و جوهای مورد نیاز برای بازیابی اطلاعات، تأثیر منفی بر عملکرد داشته باشد.
مدیریت امنیت و مجوزها میتواند پیچیده شود، زیرا هر موجودی تابع عملیات خواندن و نوشتن است، که ممکن است دادهها را در زمینه نادرست نمایش دهد[1].
راه حل
CQRS خواندن و نوشتن را به مدلهای مختلف جدا میکند، با استفاده از دستورات برای بهروزرسانی دادهها و پرس و جو برای خواندن دادهها.
دستورات باید مبتنی بر وظیفه باشند، نه داده محور. ("رزرو اتاق هتل"، نه "تنظیم وضعیت رزرو روی رزرو شده").
دستورات ممکن است در یک صف برای پردازش ناهمزمان قرار گیرند، نه اینکه به صورت همزمان پردازش شوند.
کوئری ها هرگز پایگاه داده را تغییر نمی دهند. یک پرس و جو یک DTO را برمی گرداند که هیچ دانش دامنه را کپسوله نمی کند[1].
سپس میتوان مدلها را جدا کرد، همانطور که در نمودار زیر نشان داده شده است، اگرچه این یک الزام مطلق نیست.
داشتن مدل های پرس و جو و به روز رسانی جداگانه، طراحی و پیاده سازی را ساده می کند. با این حال، یکی از معایب این است که کد CQRS نمی تواند به طور خودکار از طرحواره پایگاه داده با استفاده از مکانیسم های داربست مانند ابزارهای O/RM تولید شود. برای جداسازی بیشتر، می توانید داده های خوانده شده را از داده های نوشتن به صورت فیزیکی جدا کنید. در آن صورت، پایگاه داده خوانده شده می تواند از طرح داده های خود استفاده کند که برای پرس و جوها بهینه شده است. به عنوان مثال، می تواند یک نمای مادی از داده ها را ذخیره کند تا از اتصالات پیچیده یا نگاشت O/RM پیچیده جلوگیری کند. حتی ممکن است از نوع دیگری از ذخیره داده استفاده کند. به عنوان مثال، پایگاه داده نوشتن ممکن است رابطه ای باشد، در حالی که پایگاه داده خوانده شده یک پایگاه داده سند است. اگر از پایگاههای اطلاعاتی جداگانه خواندن و نوشتن استفاده میشود، باید آنها را هماهنگ نگه داشت. این معمولاً با انتشار یک رویداد توسط مدل نوشتن هر زمان که پایگاه داده را به روز می کند، انجام می شود. برای اطلاعات بیشتر در مورد استفاده از رویدادها، به سبک معماری رویداد محور مراجعه کنید. به روز رسانی پایگاه داده و انتشار رویداد باید در یک تراکنش انجام شود[1].
فروشگاه خواندنی میتواند یک کپی فقط خواندنی از فروشگاه نوشتن باشد، یا فروشگاههای خواندن و نوشتن میتوانند ساختار متفاوتی داشته باشند. استفاده از چند کپی فقط خواندنی می تواند عملکرد پرس و جو را افزایش دهد، به خصوص در سناریوهای توزیع شده که در آن کپی های فقط خواندنی نزدیک به نمونه های برنامه قرار دارند.
جداسازی فروشگاههای خواندن و نوشتن همچنین اجازه میدهد تا هر کدام بهطور مناسب برای مطابقت با بار، مقیاس شوند. به عنوان مثال، فروشگاههای خواندنی معمولاً با بار بسیار بالاتری نسبت به فروشگاههای نوشتن مواجه میشوند.
برخی از پیاده سازی های CQRS از الگوی Event Sourcing استفاده می کنند. با این الگو، وضعیت برنامه به عنوان یک توالی از رویدادها ذخیره می شود. هر رویداد نشان دهنده مجموعه ای از تغییرات در داده ها است. وضعیت فعلی با پخش مجدد رویدادها ساخته می شود. در زمینه CQRS، یکی از مزایای Event Sourcing این است که از همان رویدادها می توان برای اطلاع رسانی به اجزای دیگر استفاده کرد - به ویژه برای اطلاع رسانی به مدل خوانده شده. مدل خواندن از رویدادها برای ایجاد یک عکس فوری از وضعیت فعلی استفاده می کند که برای پرس و جوها کارآمدتر است. با این حال، Event Sourcing به طراحی پیچیدگی میافزاید[1].
مزایای CQRS
1- مقیاس بندی مستقل CQRS اجازه می دهد تا حجم کار خواندن و نوشتن به طور مستقل مقیاس شود و ممکن است منجر به اختلافات قفل کمتر شود.
2- طرحواره های داده بهینه شده سمت خوانده شده می تواند از طرحی استفاده کند که برای پرس و جوها بهینه شده است، در حالی که سمت نوشتن از طرحی استفاده می کند که برای به روز رسانی بهینه شده است.
3- امنیت: اطمینان از اینکه فقط موجودیت های دامنه مناسب روی داده ها نوشتن را انجام می دهند آسان تر است.
4- تفکیک نگرانی ها جداسازی دو طرف خواندن و نوشتن میتواند منجر به مدلهایی شود که قابلیت نگهداری و انعطافپذیری بیشتری دارند. بیشتر منطق پیچیده کسب و کار وارد مدل نوشتن می شود. مدل خواندن می تواند نسبتا ساده باشد.
5- پرس و جوهای ساده تر با ذخیره یک نمای مادی در پایگاه داده خوانده شده، برنامه می تواند از اتصالات پیچیده هنگام پرس و جو جلوگیری کند.
مسائل و ملاحظات اجرایی
برخی از چالش های اجرای این الگو عبارتند از:
پیچیدگی: ایده اصلی CQRS ساده است. اما می تواند منجر به طراحی برنامه پیچیده تری شود، به خصوص اگر شامل الگوی منبع رویداد باشد.
پیام رسانی: اگرچه CQRS به پیام نیازی ندارد، استفاده از پیام برای پردازش دستورات و انتشار رویدادهای به روز رسانی معمول است. در این صورت، برنامه باید با شکست پیام ها یا پیام های تکراری مقابله کند. برای برخورد با دستوراتی که اولویت های متفاوتی دارند، راهنمای صف های اولویت را ببینید.
ثبات نهایی: اگر پایگاه داده خواندن و نوشتن را از هم جدا کنید، داده های خوانده شده ممکن است کهنه شده باشند. فروشگاه مدل خوانده شده باید به روز شود تا تغییرات را در فروشگاه مدل نوشتن منعکس کند، و تشخیص زمانی که کاربر بر اساس داده های خوانده شده قدیمی درخواستی صادر کرده است، می تواند دشوار باشد[1].
زمان استفاده از الگوی CQRS
CQRS را برای سناریوهای زیر در نظر بگیرید:
- دامنه های مشارکتی که در آن بسیاری از کاربران به طور موازی به داده های مشابه دسترسی دارند. CQRS به شما اجازه می دهد تا دستوراتی را با جزئیات کافی تعریف کنید تا تضادهای ادغام را در سطح دامنه به حداقل برسانید و تداخل هایی که به وجود می آیند را می توان با دستور ادغام کرد.
- رابط های کاربری مبتنی بر وظیفه که در آن کاربران از طریق یک فرآیند پیچیده به عنوان یک سری مراحل یا با مدل های دامنه پیچیده هدایت می شوند. مدل نوشتن دارای یک پشته پردازش فرمان کامل با منطق تجاری، اعتبارسنجی ورودی و اعتبارسنجی تجاری است. مدل نوشتن ممکن است مجموعهای از اشیاء مرتبط را بهعنوان یک واحد واحد برای تغییرات دادهها (یک مجموع، در اصطلاح DDD) در نظر بگیرد و اطمینان حاصل کند که این اشیاء همیشه در یک حالت ثابت هستند. مدل خوانده شده هیچ منطق تجاری یا پشته اعتبار سنجی ندارد و فقط - یک DTO را برای استفاده در یک مدل view برمی گرداند. مدل خواندن در نهایت با مدل نوشتن سازگار است.
سناریوهایی که عملکرد خواندن داده ها باید جدا از عملکرد نوشتن داده ها تنظیم شود، به خصوص زمانی که تعداد خواندن ها بسیار بیشتر از تعداد نوشتن ها باشد. در این سناریو، می توانید مدل خوانده شده را کوچک کنید، اما مدل نوشتن را تنها در چند نمونه اجرا کنید. تعداد کمی از نمونه های مدل نوشتن نیز به به حداقل رساندن وقوع تضادهای ادغام کمک می کند[1].
- سناریوهایی که در آن یک تیم از توسعه دهندگان می توانند بر روی مدل دامنه پیچیده که بخشی از مدل نوشتن است تمرکز کنند و تیم دیگری می توانند بر روی مدل خواندن و رابط های کاربر تمرکز کنند.
- سناریوهایی که در آن انتظار میرود سیستم در طول زمان تکامل یابد و ممکن است چندین نسخه از مدل را شامل شود، یا قوانین تجاری به طور منظم تغییر میکنند.
- ادغام با سایر سیستم ها، به ویژه در ترکیب با منبع رویداد، که در آن خرابی موقت یک زیرسیستم نباید بر در دسترس بودن سایر سیستم ها تأثیر بگذارد.
این الگو زمانی توصیه نمی شودکه[3]:
1- دامنه یا قوانین تجارت ساده هستند.
2- یک رابط کاربری ساده به سبک CRUD و عملیات دسترسی به داده کافی است.
استفاده از CQRS را در بخش های محدودی از سیستم خود در نظر بگیرید که در آن بیشترین ارزش را دارد.
رویداد منبع یابی و الگوی CQRS
الگوی CQRS اغلب همراه با الگوی منبع رویداد استفاده می شود. سیستمهای مبتنی بر CQRS از مدلهای داده خواندن و نوشتن جداگانه استفاده میکنند که هر کدام برای وظایف مربوطه طراحی شده و اغلب در فروشگاههای فیزیکی مجزا قرار دارند. هنگامی که با الگوی منبع رویداد استفاده می شود، ذخیره رویدادها مدل نوشتن است و منبع رسمی اطلاعات است. مدل خواندنی یک سیستم مبتنی بر CQRS، نماهای واقعی دادهها را، معمولاً بهعنوان نماهای بسیار غیرعادیشده، ارائه میکند. این نماها بر اساس واسط ها و الزامات نمایش برنامه طراحی شده اند، که به به حداکثر رساندن عملکرد نمایش و پرس و جو کمک می کند.
استفاده از جریان رویدادها بهعنوان ذخیرهسازی نوشتن، بهجای دادههای واقعی در یک نقطه از زمان، از تضاد بهروزرسانی در یک مجموع اجتناب میکند و عملکرد و مقیاسپذیری را به حداکثر میرساند. از رویدادها می توان برای تولید ناهمزمان نماهای واقعی داده هایی که برای پر کردن فروشگاه خوانده استفاده می شود استفاده کرد.
از آنجایی که فروشگاه رویداد منبع رسمی اطلاعات است، میتوان نماهای تحققیافته را حذف کرد و همه رویدادهای گذشته را مجدداً پخش کرد تا زمانی که سیستم تکامل مییابد، یا زمانی که مدل خوانده شده باید تغییر کند، نمایش جدیدی از وضعیت فعلی ایجاد شود. نماهای تحقق یافته در واقع یک حافظه پنهان فقط خواندنی از داده ها هستند[4].
هنگام استفاده از CQRS همراه با الگوی منبع رویداد، موارد زیر را در نظر بگیرید[4]:
- مانند هر سیستمی که ذخیرههای نوشتن و خواندن مجزا هستند، سیستمهای مبتنی بر این الگو تنها در نهایت سازگار هستند. بین ایجاد رویداد و بهروزرسانی ذخیرهگاه داده تأخیر وجود خواهد داشت.
- این الگو پیچیدگی میافزاید، زیرا کد باید برای راهاندازی و مدیریت رویدادها، و جمعآوری یا بهروزرسانی نماها یا اشیاء مناسب مورد نیاز کوئریها یا یک مدل خوانده شده ایجاد شود. پیچیدگی الگوی CQRS زمانی که با الگوی منبع یابی رویداد استفاده میشود، میتواند اجرای موفقیتآمیز را دشوارتر کند و به رویکرد متفاوتی برای طراحی سیستمها نیاز دارد. با این حال، منبع رویداد میتواند مدلسازی دامنه را آسانتر کند، و بازسازی نماها یا ایجاد نماهای جدید را آسانتر میکند، زیرا هدف از تغییرات در دادهها حفظ میشود.
- ایجاد نماهای تحقق یافته برای استفاده در مدل خوانده شده یا پیش بینی داده ها با پخش مجدد و مدیریت رویدادها برای موجودیت ها یا مجموعه هایی از موجودیت ها می تواند به زمان پردازش و استفاده از منابع قابل توجهی نیاز داشته باشد. این امر به ویژه در صورتی صادق است که به جمع بندی یا تجزیه و تحلیل مقادیر در دوره های طولانی نیاز داشته باشد، زیرا ممکن است همه رویدادهای مرتبط نیاز به بررسی داشته باشند. این مشکل را با اجرای عکسهای فوری از دادهها در فواصل زمانی برنامهریزیشده، مانند شمارش کل تعداد یک عمل خاص که رخ داده است، یا وضعیت فعلی یک موجودیت، حل کنید[4].
اصول اصلی
اصل اصلی CQRS جداسازی دستورات و کوئری ها و کارهایی است که انجام می دهند. آنها نقشهای اساسی متفاوتی را در یک سیستم انجام میدهند، و تفکیک آنها به این معنی است که هر کدام میتوانند در صورت نیاز بهینه شوند، که میتواند برای سیستمهای توزیع شده بسیار مفید باشد.
Alexey Zimarev تعریف کرده است که چگونه دستورات و پرس و جوها متفاوت هستند:
در سطح بالایی، CQRS این واقعیت را بیان میکند که عملیاتهایی که انتقال حالت را راهاندازی میکنند باید بهعنوان دستورات توصیف شوند، و هر بازیابی دادهای که فراتر از نیاز اجرای دستور است، باید یک پرس و جو نامیده شود. از آنجایی که الزامات عملیاتی برای اجرای دستورات و پرس و جوها اغلب متفاوت است، توسعه دهندگان باید استفاده از تکنیک های ماندگاری مختلف را برای رسیدگی به دستورات و پرس و جوها در نظر بگیرند، بنابراین آنها را از هم جدا کنند[2].
ارتباط با طراحی دامنه محور
طراحی دامنه محور (DDD) روشی برای بهینه سازی درک تیم از یک فضای مشکل و نحوه کار در آن فضا است. این در مورد داشتن زبانی فراگیر است که توسط کاربران تجاری و تیم توسعه استفاده می شود. این یکسان سازی زبان می تواند در هنگام ترجمه مفهوم مشکل به نرم افزار کاربردی بسیار مفید باشد.
CQRS و DDD مفاهیم مجزا و متعامد هستند. برای استفاده از DDD نیازی به استفاده از CQRS ندارید یا برعکس. هنگامی که آنها با هم استفاده می شوند، DDD می تواند یک زمینه محدود، مشتق شده از مکالمات کسب و کار را فراهم کند، و CQRS می تواند نحوه انتقال کار را در سیستم تعریف کند. DDD در اصل با CQRS هماهنگ است، اما قطعاً به یکدیگر وابسته نیست[5].
در پایان
الگوی تفکیک مسئولیت پرس و جو فرمان یک تکنیک طراحی ارزشمند است که برای برنامه هایی که نیاز به پشتیبانی از حجم بالایی از درخواست های ارائه شده در برابر منابع داده بسیار بزرگ دارند، مناسب است. جداسازی رفتار خواندن از نوشتن عملکرد سیستم را به طور کلی بهبود می بخشد. و به سیستمها اجازه میدهد تا زمانی که عملکردها یا ساختارهای داده اضافه شوند، به سرعت افزایش پیدا کنند. هنگامی که CQRS در ارتباط با الگوی میانجی استفاده می شود، سیستم را انعطاف پذیرتر می کند و بازسازی آن را آسان تر می کند.
با این حال، در حالی که CQRS قدرتمند است، ساده نیست. جداسازی داده ها بر اساس هدف به این معنی است که همیشه خطر سازگاری داده ها به خطر می افتد یا به دلیل درجات مختلف تأخیر در شبکه یا خرابی سیستم اپیزودیک. بنابراین، پیاده سازی CQRS با استفاده از یک معماری پایداری داده مبتنی بر رویداد که در آن کارگزار پیام سیستم می تواند هر پیام دریافتی را برای مدت بسیار طولانی ذخیره کند، راه معقولی است.
CQRS می تواند عملکرد و کارایی سیستم را افزایش دهد. ترفند این است که مطمئن شوید که معماری برنامه کاربردی که در آن پیادهسازی میشود بهطور سست جفت شده است و در صورت خرابی فاجعهبار میتواند به آخرین وضعیت خوب شناختهشده بازسازی شود[6].
منابع
[1]https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs
[2]https://martinfowler.com/bliki/CQRS.html
[3]https://en.wikipedia.org/wiki/Command%E2%80%93query_separation
[4]https://www.eventstore.com/cqrs-pattern
[5]https://sderosiaux.medium.com/cqrs-what-why-how-945543482313
[6]https://www.baeldung.com/cqrs-event-sourcing-java
این مطلب، بخشی از تمرینهای درس معماری نرمافزار در دانشگاه شهیدبهشتی است.