امین باقری
امین باقری
خواندن ۱۰ دقیقه·۳ سال پیش

الگوی معماری CQRS

مقدمه

CQRS یک الگوی معماری است که از کنار هم قرار دادن حروف اول عبارت Command Query Responsibility Segregation تشکیل شده است. این الگو، عملیات سیستم را به دو قسمت دستورات (Commands) و کوئری ها (Queries) تقسیم بندی می کند. CQRS با CQS که مخفف Command Query Separation است مرتبط بوده و از آن مشتق شده است.

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


CQS چیست؟

CQS یک الگوی طراحی است که از کنار هم قرار دادن حروف اول عبارت Command Query Separation تشکیل شده است. CQS یک مفهوم اولیه و اساسی است که برای عملیات انجام شده بر روی سیستم، دو نوع را تعریف می کند: دستوری که یک تسک را اجرا می کند و کوئری که اطلاعاتی را برمی‌گرداند، و تابعی نباید وجود داشته باشد که هر دوی این کارها را انجام دهد.

این اصطلاح توسط Bertrand Mayer و در کتاب «ساخت نرم افزار شی گرا» در سال ۱۹۸۸ خلق شد؛ او آن را به عنوان بخشی از کار خود بر روی زبان برنامه نویسی Eiffel ایجاد کرد.

CQRS اصل تعیین کننده CQS را می گیرد و آن را به اشیاٰء خاصی در سیستم گسترش می دهد، یکی در بازیابی داده و دیگری در تغییر داده. CQRS یک الگوی معماری گسترده و CQS، یک اصل عمومی رفتاری است.


CQRS چیست؟

CQRS تفکیک مسئولیت های دستورات و کوئری ها در یک سیستم است. این بدان معناست که ما منطق برنامه خود را به صورت عمودی برش می دهیم. علاوه بر این، پرش حالت (دستورالعمل فرمان) را از بازیابی اطلاعات (کنترل کوئری) جدا می کنیم.

CQRS توسط Greg Young تعریف شد.

Command and Query Responsibility Segregation از همان تعریف دستور (Command) و کوئری که Mayer استفاده کرد، استفاده می کند و این دیدگاه را حفظ می کند که آن دو باید خالص باشند. تفاوت اساسی این است که در CQRS، اشیا به دو شی تقسیم می شوند، یکی دستورات، و دیگری کوئری ها را شامل می شود.

«اشیا» در تعریف اصلی، با ذخیره سازی (Storage) مرتبط نبوده بلکه با کنترل کننده ها (Handlers) مرتبط هستند. ما خطوط لوله (Pipelines) مختلفی را برای رفتار های تجاری متفاوت ایجاد می کنیم، نه برای ذخیره سازی جداگانه.


اصول اصلی

اصل اساسی CQRS، جداسازی دستورات و کوئری ها و کارهایی است که آن ها انجام می دهند. دستورات و کوئری ها، نقش‌های بسیار متفاوتی را در یک سیستم انجام می‌دهند و تفکیک آنها به این معنی است که هر کدام می‌توانند در صورت نیاز بهینه شوند که می‌تواند برای سیستم‌های توزیع‌شده بسیار مفید باشد.

Alexey Zimarev، چگونگی تفاوت دستورات و کوئری ها را به صورت زیر تعریف کرده است:

در سطح بالا، CQRS این حقیقت را بیان می‌کند که عملیات‌هایی که انتقال حالت را راه‌اندازی می‌کنند باید به‌عنوان دستور (Command) توصیف شوند، و هر بازیابی داده‌ای که به اجرای دستور نیاز نداشته باشد، باید یک پرس و جو (Query) نامیده شود. از آنجایی که مقررات عملیاتی برای اجرای دستورات و پرس و جوها اغلب متفاوت است، توسعه دهندگان باید استفاده از تکنیک های ماندگاری مختلف را برای رسیدگی به دستورات و پرس و جوها در نظر بگیرند؛ بنابراین باید آنها را از هم جدا کنند.

در ادامه، به دستورات، کوئری ها و ارتباط آن ها با مدل های خواندن (Read) و نوشتن (Write)، می پردازیم.


دستورات (Commands)

به گفته برتراند مایر، Command یک فرمان است؛ دستوری برای انجام یک کار خاص که قصد دارد چیزی را تغییر دهد.

یک دستور، یک کار را انجام می دهد اما نتیجه ای را بر نمی گرداند.

یک دستور باید هدف کاربر را منتقل کند. طبق توضیح الکسی زیمارف، اجرای یک دستور باید منجر به یک تراکنش روی یک جمع شود. اساساً، هر دستور باید به وضوح یک تغییر کاملاً تعریف شده را بیان کند.

دستورات، مدل نوشتن (Write Model) را تشکیل می دهند و مدل نوشتن باید تا حد امکان به فرآیندهای تجاری نزدیک باشد.


کوئری ها (Queries)

به گفته برتراند مایر، کوئری، یک درخواست برای اطلاعات است.

یک کوئری، یک نتیجه بر می گرداند اما حالت را تغییر نمی دهد.

کوئری، درخواستی برای دریافت اطلاعات و یا وضعیت اطلاعات از یک مکان مشخص است. هیچ چیزی در داده ها نباید با درخواست تغییر کند. از آنجایی که کوئری ها چیزی را تغییر نمی دهند، نیازی به درگیر کردن مدل دامنه (Domain Model) ندارند.

کوئری ها مدل خواندن (Read Model) را تشکیل می دهند. مدل خواندن باید از مدل نوشتن مشتق شود. همچنین لازم نیست دائمی باشد. مدل‌های خواندن جدید را می‌توان بدون تأثیرگذاری بر مدل‌های موجود، به سیستم معرفی کرد. مدل های خواندن را می توان بدون از دست دادن منطق تجاری و یا اطلاعات، حذف و دوباره ایجاد کرد؛ زیرا در مدل نوشتن ذخیره می شوند.


مزایای CQRS

استفاده از اصول CQRS در معماری، مزایای بسیاری را به همراه خواهد داشت:

  • از بین بردن مرز های بین رفتار سیستم: راهنمایی به خصوصی در مورد ساخت برنامه در رابطه با رفتار، ارائه می دهد.
  • نزدیک بودن به منطق تجاری: کد و معماری، بر اساس عملیات تجاریشان تفکیک شده اند و این کار، تمرکز بر روی مورد تجاری را آسان تر می کند.
  • اتصال سست: منطق، مسنجم بوده و بخش های مختلف آن، کمتر به هم مرتبط هستند و در نتیجه ، ایجاد برنامه های ماژولار و قابل نگهداری را آسان تر می شود.
  • کاهش بار شناختی: به دلیل جداسازی عمودی، کد های مرتبط،‌ در کنار هم نگه داشته می شوند. برای تغییر کد موجود و یا ایجاد کد جدید، نیازی به فهم تمام معماری و منطق تجاری نیست و در نتیجه، تمرکز بر روی یک وظیفه خاص، آسان تر می شود.
  • مقیاس پذیری، بهینه سازی و تغییر در معماری به طور آسان تر: از آن جایی که کد ما در سیلو ها نگهداری می شود، تنظیم دقیق تنها یک پایپ لاین و دست نزدن به بقیه آن ها، آسان تر است و در نتیجه، تمرکز بر روی بهینه سازی های دقیق، در مکان هایی که Business-critical هستند را آسان تر می کند.
  • قابل پیش بینی بودن: به علت جداسازی، شما قوانین عمومی ای برای رفتار عملیات دارید و از تغییر حالت برنامه به وسیله یک کوئری، متعجب نمی شوید؛ حفظ جداسازی بین منطق نوشتن و خواندن، احتمال تولید یک کد اسپاگتی را کاهش می دهد.

موارد استفاده از CQRS

از الگوی CQRS در موارد زیر استفاده می شود:

  • رابط های کاربری مبتنی بر وظیفه (Task-based user interfaces) که در آن کاربران از طریق یک فرآیند پیچیده به عنوان یک سری مراحل یا با مدل های دامنه پیچیده هدایت می شوند. مدل نوشتن دارای یک پشته پردازش کامل فرمان (full command-processing stack) با منطق تجاری، اعتبارسنجی ورودی و اعتبارسنجی تجاری است. مدل نوشتن ممکن است مجموعه‌ای از اشیاء مرتبط را به‌عنوان یک واحد یکتا برای تغییر دیتا در نظر بگیرد و اطمینان حاصل کند که این اشیاء همیشه در یک حالت ثابت هستند. مدل خواندن، هیچ منطق تجاری یا پشته اعتبار سنجی ندارد و فقط یک DTO را برای استفاده در یک view model برمی گرداند. مدل خواندن در نهایت با مدل نوشتن سازگار است.
  • دامنه های مشارکتی که در آن بسیاری از کاربران به طور موازی به داده های مشابه دسترسی دارند؛ CQRS به شما اجازه می دهد تا دستوراتی را با جزئیات کافی برای به حداقل رساندن تضادهای ادغام (Merge conflicts) در سطح دامنه، تعریف کنید و تداخل هایی که به وجود می آیند را با دستور (Command) ادغام کنید.
  • سناریوهایی که در آن ها عملکرد خواندن داده ها باید جدا از عملکرد نوشتن داده ها تنظیم شود، به خصوص زمانی که تعداد خواندن ها بسیار بیشتر از تعداد نوشتن ها باشد؛ در این سناریو می توانید مدل خواندن را کوچک کرده و مدل نوشتن را فقط در چند نمونه اجرا کنید. تعداد کمی از نمونه های مدل نوشتن نیز به حداقل رساندن وقوع تضادهای ادغام کمک می کنند.
  • سناریوهایی که در آن یک تیم از توسعه دهندگان می تواند بر روی مدل دامنه پیچیده که بخشی از مدل نوشتن است تمرکز کرده و تیم دیگری می تواند بر روی مدل خواندن و رابط های کاربری تمرکز کند.
  • سناریوهایی که در آن ها انتظار می‌رود سیستم در طول زمان تکامل یابد و ممکن است چندین نسخه از مدل را شامل شود یا سناریو هایی که در آن ها، قوانین تجاری به طور منظم تغییر می‌کنند.
  • ادغام با سایر سیستم ها به ویژه در ترکیب با الگوی منبع رویداد که در آن خرابی موقت یک زیرسیستم نباید بر در دسترس بودن سایر زیر سیستم ها تأثیر بگذارد.

الگوی CQRS برای موارد زیر، توصیه نمی شود:

  • دامنه یا قوانین تجاری ساده هستند.
  • یک رابط کاربری ساده به سبک CRUD: عملیات دسترسی به داده کافی است.

استفاده از CQRS را به بخش هایی از سیستم خود محدود کنید که در آن ها بیشترین ارزش را خواهد داشت.

چالش ها

برخی از چالش های استفاده از این الگو، عبارتند از:

  • پیچیدگی: ایده اصلی CQRS ساده است، اما می تواند منجر به پیچیدگی طراحی برنامه شود، به خصوص اگر شامل الگوی Event Sourcing باشد.
  • پیام رسانی: اگرچه CQRS به پیام رسانی نیازی ندارد، استفاده از پیام رسانی برای پردازش دستورات و انتشار به روز رسانی ها معمول است. در این صورت، برنامه باید با دو مشکل خرابی پیام ها و پیام های تکراری مقابله کند.
  • سازگاری نهایی: اگر پایگاه های داده خواندن و نوشتن را از هم جدا کنید، داده های خوانده شده ممکن است کهنه شود. مخزن مدل خواندن باید به روز شود تا تغییرات را در مخزن مدل نوشتن منعکس کند و تشخیص این که کاربر بر اساس داده های خوانده شده قدیمی، یک درخواست را انجام داده است، می تواند دشوار باشد.

تصورات غلط در مورد CQRS

  • «دستورات و کوئری ها باید در پایگاه های داده جداگانه اجرا شوند و همه چیز باید در پایگاه های داده جداگانه ذخیره شود.»

این عبارت، لزوما درست نیست؛ فقط نیاز است که رفتار ها و مسئولیت های آن دو را از هم جدا کرد. این می
تواند داخل کد، در ساختار یک پایگاه داده و یا پایگاه های داده مختلف باشد.

CQRS حتی نیازی به استفاده از پایگاه داده ندارد؛ ممکن است از اکسل و یا هر چیز دیگر که حاوی داده است،
استفاده شود.

CQRS، برای رفتار یک سیستم کاربرد دارد، نه برای مکان ذخیره سازی داده. به خاطر داشته باشید که جمله
کلیدی «CQRS به رفتار اشاره دارد و نه ذخیره سازی»، کلید استفاده موثر از CQRS است.

  • «CQRS مشکلات سازگاری نهایی (Eventual Consistency) را ایجاد می کند.»

جداسازی دستورات و کوئری ها و مدیریت متفاوت آنها می تواند یک سیستم در نهایت سازگار را تولید کند.
این تصور غلط وجود دارد که سیستم های درنهایت سازگار به دلیل تاخیرهای زمانی، دقیق نبوده و با جداسازی
دستورات و کوئری ها در CQRS، باید تاخیر ها و در نتیجه مشکلات سازگاری نهایی وجود داشته باشد.

  • «سیستم های CQRS همیشه به صف های پیام (Messaging Queues) نیاز دارند.»

صف های پیام مانند Kafka و RabbitMQ، بسته به شرایط به شما امکان ارسال پیام بین مدل های خواندن
و نوشتن را می دهند. اگر مدل شما ساده بوده و با نماهای مختلف پایگاه داده شروع می شود، به صف های
پیام نیازی نیست؛ به طور کلی، استفاده از آن، به نیازمان در آن زمینه تجاری خاص، بستگی دارد. ساخت
سیستم با در نظر گرفتن CQRS به شما این امکان را می دهد که انواع مختلف پایگاه های داده را در تعامل با
یکدیگر داشته باشید. صف های پیام رسانی برای نگه داری به روز مدل های خواندن در صورت تغییر مدل
نوشتن، مفید خواهند بود.

  • «فقط می تواند با DDD استفاده شود.»

CQRS و DDD نسبت به هم، دو مفهوم مجزا و متعامد هستند و برای استفاده از DDD نیازی به استفاده از CQRS نیست و برعکس. DDD در اصل با CQRS هماهنگ است؛ اما این دو قطعاً به یکدیگر وابسته نیستند.


جمع بندی

الگوی معماری CQRS، روشی برای جداسازی رفتار نوشتن و خواندن داده‌ها از یکدیگر است. این روش، برای سیستم‌هایی که با تعداد درخواست‌های بسیار زیادی روی پایگاه‌های داده خود مواجه هستند مناسب است. CQRS در شرایط مناسب، باعث افزایش کارایی سیستم‌ و مقیاس پذیری بهتر آن می شود.

لازم به ذکر است که استفاده از CQRS آسان نبوده و نیاز به نیروی متخصص دارد. جداسازی مدل‌های خواندن و نوشتن از یکدیگر، خطر ایجاد ناهماهنگی میان داده‌ها را بالا می برد؛ بنابراین از CQRS باید در موارد کاربردی خود و به طور سنجیده استفاده شود.

«این مطلب، بخشی از تمرینهای درس معماری نرم‌افزار در دانشگاه شهیدبهشتی است.»

مراجع

https://bertrandmeyer.com

https://www.eiffel.com/resources/faqs/eiffel-language

https://www.eventstore.com/cqrs-pattern

https://martinfowler.com/bliki/CQRS.html

https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs

معماری نرم افزار بهشتیمعماری نرم افزار
شاید از این پست‌ها خوشتان بیاید