CQRS: مبدأ "فرق تسد" في خدمة مبرمج

العمارة النفخة هي الخلاص في عالم تطوير المشاريع. من خلال مساعدتها ، يمكنك تفريغ الحديد وموازنة العمليات واستعادة النظام في الكود. حاولنا استخدام نمط CQRS عند تطوير مشروع شركة. أصبح كل شيء أكثر منطقية وأكثر تعقيدًا. لقد تحدثت مؤخرًا عن ما اضطررت إلى مواجهته في اجتماع Panda-Meetup C # .Net ، وأنا الآن أشارككم.



هل سبق لك أن لاحظت كيف يبدو تطبيق شركتك؟ لماذا لا يمكن أن يكون مثل أبل وجوجل؟ نعم ، لدينا نقص دائم في الوقت. تتغير المتطلبات بشكل متكرر ، عادة ما يكون مصطلح تغييراتها "بالأمس". وما هو أكثر من غير سارة ، والأعمال التجارية حقا لا يحب الأخطاء.



من أجل التعايش مع هذا بطريقة ما ، بدأ المطورين في تقسيم تطبيقاتهم إلى أجزاء. كل شيء بدأ بسيط - مع البيانات. يتعرف الكثيرون على المخطط عندما تكون البيانات منفصلة ، ويكون العميل منفصلًا ، بينما يتم تخزين المنطق في نفس مكان البيانات.



مخطط جيد. تحتوي أكبر نظم إدارة قواعد البيانات على امتدادات إجرائية كاملة الوظائف لـ SQL. هناك مثل عن أوراكل: "حيث يوجد Oracle ، هناك منطق". من الصعب الجدال حول راحة وسرعة هذا التكوين.

ولكن لدينا تطبيق للشركات ، وهناك مشكلة: من الصعب توسيع نطاق المنطق. ومن غير المعقول تحميل سعة نظم إدارة قواعد البيانات ، التي تعاني بالفعل من مشاكل في استخراج البيانات وتحديثها ، وكذلك مهام الأعمال البسيطة.

حسنًا ، أدوات البرمجة المنطقية للأعمال المضمنة في نظام إدارة قواعد البيانات (DBMS) ، لنكون صادقين ، ضعيفة لإنشاء تطبيقات الشركات العادية. الحفاظ على منطق العمل في T-SQL / PL-SQL هو ألم. ليس من دون سبب أن لغات OOP واسعة الانتشار بين تطبيقات الشركات: C # ، Java ، ليس عليك أن تذهب بعيدًا على سبيل المثال.



يبدو حلا منطقيا: نحن نسلط الضوء على منطق الأعمال. سوف تعيش على الخادم الخاص بها ، والقاعدة الخاصة بها ، والعميل بمفردها.

ما الذي يمكن تحسينه في هذا الهيكل ثلاثي الطبقات؟ تشارك الهندسة المعمارية في طبقة منطق الأعمال ، وأود أن تجنب هذا. لا يريد منطق العمل معرفة أي شيء عن تخزين البيانات. تعد UI أيضًا عالمًا منفصلاً حيث توجد كيانات ليست خاصة بمنطق الأعمال.

زيادة الطبقات سوف تساعد. يبدو هذا الحل مثاليًا تقريبًا ، فهو يحتوي على نوع من الجمال الداخلي.



لدينا DAL (طبقة الوصول إلى البيانات) - يتم فصل البيانات عن المنطق ، وعادة ما يكون مستودع CRUD باستخدام ORM ، بالإضافة إلى الإجراءات المخزنة للاستعلامات المعقدة. يسمح لك هذا الخيار بالتطور بسرعة كافية والحصول على أداء مقبول.

يمكن أن يذهب منطق العمل كجزء من الخدمات أو أن يكون طبقة منفصلة. يمكن تنفيذ التفاعل بين الطبقات من خلال كائنات النقل (DTO).

يذهب طلب واجهة المستخدم إلى الخدمة ، ويتواصل مع منطق العمل ، ويتسلق إلى DAL للوصول إلى البيانات. هذا النهج يسمى N- الطبقة ، ولها مزايا واضحة.

كل طبقة لها أهدافها وغاياتها الواضحة التي نحبها نحن كمبرمجين. كل طبقة خرسانية تعمل فقط في أعمالها الخاصة. يمكن تحجيم الخدمات أفقياً. النهج واضح حتى بالنسبة للمطور المبتدئ ، فالشخص يفهم بسرعة كيف يعمل النظام. من السهل جدًا تتبع جميع التفاعلات مع استمرار الطلب من البداية إلى النهاية.

تناسق آخر: تعمل جميع الأنظمة الفرعية للمشروع مع نفس البيانات ، ولا داعي للقلق من أننا قمنا بتسجيل البيانات في مكان واحد ولا يراها المستخدم في جزء آخر.

طبقة كعكة 1. N- الطبقة


فيما يلي مثال على جزء نموذجي من تطبيق مبني على هذه المبادئ. لدينا مطلب نقدي ، لقد درست نموذج فقر الدم هنا. وهناك مستودع كلاسيكي ، يعمل به ORM.



هذه خدمة نموذجية ، تسمى أيضًا المديرين. يعمل مع المستودع ، ويتلقى الطلبات ويعطي إجابات للعملاء. في هذه الخدمة ، نرى بعض الالتباس: لدينا عملية معالجة ، وعملية للعمل مع واجهة المستخدم وعملية لبعض وحدات التحكم الداخلية ، فهي مترابطة بشكل ضعيف.

إليك ما تبدو عليه الطريقة النموذجية لهذه الخدمة. على سبيل المثال ، تسجيل مطالبة نقدية.



نتلقى البيانات وننفذ بعض الأعمال التجارية. ثم هناك تحديث ، وبعده بعض الإجراءات اللاحقة ، على سبيل المثال ، إرسال إشعار أو كتابة إلى سجل المستخدم.

في هذا النهج ، رغم كل جمالها ، هناك مشاكل. في كثير من الأحيان في تطبيقات الشركات ، يكون الحمل غير متماثل: عمليات القراءة هي أمر أو أكثر من عمليات الكتابة. هناك بالفعل مشكلة في توسيع نطاق قاعدة البيانات نفسها. بالطبع ، يتم ذلك ، وحتى عن طريق DBMS على مقياس قاعدة البيانات ، يتم استدعاء التقسيم. لكنه صعب. إذا تم ذلك باستخدام مؤهلات خاطئة أو تم في وقت أبكر من اللازم ، فسيفشل التقسيم.

على سبيل المثال ، في أحد أنظمتنا بلغ حجم البيانات 25 تيرابايت ، ظهرت مشاكل. لقد حاولنا نحن أنفسنا توسيع نطاق اللاعبين الصغار من شركة معروفة. نظروا وقالوا: نحن بحاجة إلى 14 ساعة من التوقف الكامل للقاعدة. فكرنا وقلنا: أيها الشباب ، إنها لن تنجح ، ولن تقبلها الشركة.

بالإضافة إلى حجم قاعدة البيانات ، يتزايد عدد الأساليب في الخدمات والمستودعات. على سبيل المثال ، في خدمة للمطالبات النقدية هناك أكثر من مائة طريقة. من الصعب الحفاظ عليها ، فهناك تعارض مستمر عند طلب الدمج ، ومراجعة التعليمات البرمجية صعبة التنفيذ. وإذا أخذنا في الاعتبار أن العمليات مختلفة ، فإن مجموعات مختلفة من المطورين تعمل عليها ، تصبح مهمة تتبع جميع التغييرات المرتبطة بالمشكلة بمثابة صداع حقيقي.

نفخة المعجنات 2. CQRS


ماذا تفعل؟ هناك حل تم اختراعه في روما القديمة: الانقسام والحكم.



كما يقولون ، كل ما هو جديد قديم طي النسيان. في عام 1988 ، صاغ برتراند ماير مبدأ برمجة CQS الضرورية - الفصل بين الاستعلام والأوامر - للتعامل مع الأشياء. جميع الطرق تنقسم بوضوح إلى نوعين. الأول - الاستعلام - الاستعلامات التي ترجع النتيجة دون تغيير حالة الكائن. أي عندما تنظر إلى المتطلبات النقدية للعميل ، لا ينبغي لأحد أن يكتب إلى قاعدة البيانات التي نظر إليها العميل من هذا القبيل ، وينبغي ألا يكون هناك أي آثار جانبية في الطلب.

الأوامر الثانية - الأوامر - التي تغير حالة الكائن دون إرجاع البيانات. هذا هو ، لقد طلبت تغيير شيء ، وفي المقابل لا تنتظر تقريرا من 10 آلاف سطر.



هنا ، يتم فصل نموذج بيانات القراءة بوضوح عن نموذج الكتابة. معظم منطق الأعمال يعمل على عمليات الكتابة. يمكن أن تعمل القراءة على تمثيلات ملموسة أو بشكل عام على أساس مختلف. يمكن تقسيمها ومزامنتها من خلال الأحداث أو بعض الخدمات الداخلية. هناك العديد من الخيارات.

CQRS ليست معقدة. يجب أن نميز بوضوح الفرق التي تغير حالة النظام ، لكن لا تعيد أي شيء. هنا قد يكون النهج أكثر توازنا. لن يكون الأمر مخيفًا بشكل خاص إذا أعاد الأمر نتيجة التنفيذ: خطأ أو ، على سبيل المثال ، معرف الكيان الذي تم إنشاؤه ، فلا توجد جريمة في هذا. من المهم ألا يعمل الفريق مع الطلب ، ولا ينبغي له البحث عن البيانات وإرجاع الكيانات التجارية.

طلبات - كل شيء بسيط هناك. لا يغير الحالة بحيث لا توجد آثار جانبية. هذا يعني أنه إذا اتصلنا بالطلب مرتين على التوالي ، ولم تكن هناك أوامر أخرى ، فيجب أن تظل حالة الكائن في كلتا الحالتين متطابقة. هذا يسمح لك بموازنة الاستعلامات. ومن المثير للاهتمام ، ليست هناك حاجة إلى نموذج منفصل للاستعلامات للعمل ، لأنه لا يوجد أي نقطة في رسم منطق العمل من نموذج المجال لهذا الغرض.

مشروع CQRS لدينا


هنا هو ما أردنا القيام به في مشروعنا:



يعمل تطبيقنا الحالي منذ عام 2006 ، ولديه بنية كلاسيكية ذات طبقات. الطراز القديم ولكن لا تزال تعمل. لا أحد يريد تغييره ولا يعرف حتى استبداله. جاءت اللحظة عندما كان من الضروري تطوير شيء جديد ، من الصفر عملياً. في 2011-2012 ، كانت Event Sourcing و CQRS موضوعًا عصريًا للغاية. لقد اعتقدنا أنه كان رائعًا ، حتى نتمكن من تخزين الحالة الأصلية للكائن والأحداث التي أدت إليه.

هذا هو ، نحن لسنا ، كما كان ، تحديث الكائن. هناك حالة أصلية وبجانبها ما تم تطبيقه عليها. في هذه الحالة ، هناك علامة زائد ضخمة - يمكننا استعادة حالة كائن في أي لحظة في التاريخ. في الواقع ، لم تعد هناك حاجة للمجلة. أثناء تخزين الأحداث ، نفهم ما حدث بالضبط. أي أنه لم يتم تحديث قيمة العميل في خلية "العنوان" فحسب ، بل سنسجل الحدث الدقيق ، على سبيل المثال ، نقل العميل.

من الواضح أن مثل هذا المخطط يعمل ببطء عند تلقي البيانات ، لذلك لدينا قاعدة بيانات منفصلة مع تمثيلات المواد للاختيار. حسنًا ، ومزامنة الحدث: مع كل وصول للأحداث على تغيير الحالة ، يحدث منشور. من الناحية النظرية ، يبدو أن كل شيء على ما يرام ، لكن ... لم أقابل أبدًا أشخاصًا أدركوا ذلك تمامًا في الإنتاج ، بأحمال كبيرة بتناسق أعمال مقبول.



يمكن تطوير المخطط إذا تم فصل المعالجات والأوامر / الطلبات. هنا ، على سبيل المثال ، لدينا فريق - مطالبة نقدية مسجلة: هناك تاريخ ومبلغ وعميل ومجالات أخرى.

نضع قيودًا على معالج تسجيل المطالبة النقدية بأنه لا يمكنه قبول سوى فريقنا (حيث TCommand: ICommand). يمكننا كتابة معالجات دون تغيير القديمة ، ببساطة عن طريق إضافة متطلبات معقدة. على سبيل المثال ، قم أولاً بتحديث التاريخ ، ثم اكتب القيمة ، وهنا ترسل إشعارًا إلى العميل - كل هذا مكتوب في معالجات مختلفة لكل أمر.

كيف نتسبب في كل هذا؟ هناك المرسل الذي يعرف مكان تخزين كل معالجاته هذه.



يتم تمرير المرسل (على سبيل المثال ، عبر حاوية DI) إلى API. وعندما يصل الأمر ، يتم تنفيذه فقط. إنه يعرف مكان وجود الحاوية ، ومكان وجود الفرق ، ويقوم بتنفيذها. مع الطلبات - بالمثل.

ما هي المشكلة في مثل هذا المخطط: تصبح جميع التفاعلات أقل وضوحًا. نقوم ببناء تسلسل هرمي على الأنواع المسجلة في حاويات ، ثم نرد على الأوامر / الطلبات الخاصة بنا. يتطلب تصميمًا واضحًا جدًا للهندسة المعمارية. أي إجراء مع طريقة واحدة مع معلمة واحدة لم تعد محدودة. تكتب أمرًا ، تكتب معالجًا ، تسجل في حاوية. كمية النفقات العامة في تزايد. في مشروع كبير ، تنشأ مشاكل في التنقل الأولي. قررنا الذهاب بطريقة أكثر كلاسيكية.

للاتصال غير المتزامن ، تم استخدام حافلة خدمة Rebus.



بالنسبة للمهام البسيطة ، إنها أكثر من كافية.

يجعلك CQRS من التعامل مع الكود بطريقة مختلفة قليلاً ، والتركيز على العملية ، لأن كل الإجراءات تولد كجزء من العملية. خصصنا مستودعًا للطلبات والأوامر ذات الصلة بمعالجة منفصلة ، ومعالجة الطلبات ذات الصلة بشكل منفصل. للقراءة ، لم نستخدم مستودعًا منفصلًا ، فنحن نعمل فقط مع ORM في فرق.



هنا ، على سبيل المثال ، طريقة يتم من خلالها طرح كل شيء غير ضروري. في فريق تسجيل المطالبة بالأموال ، نسجل الطلب وننشر الحدث على الحافلة التي يتم تسجيل المطالبة المالية بها.



أي شخص مهتم بهذا سيرد عليه. على سبيل المثال ، ستعمل مصادقة المستخدم وتسجيل الدخول هناك.

هنا طلب مثال. أصبح كل شيء بسيطًا أيضًا: لقد قرأنا ونودع المستودع.



أريد أن أبقى بشكل منفصل في Rebus.Saga. هذا نمط يسمح لك بتقسيم معاملة تجارية إلى إجراءات ذرية. هذا يسمح لك بحظر ليس كل شيء في وقت واحد ، ولكن تدريجيا وبدوره.



العنصر الأول يتخذ إجراءً ويرسل رسالة ، ويستجيب المشترك الثاني لها ، ويلبيها ، ويرسل رسالته ، التي يستجيب لها بالفعل الجزء الثالث من النظام. إذا انتهى كل شيء بشكل جيد ، تنشئ Saga رسالتها الخاصة من النوع المحدد ، والتي سيستجيب لها المشتركون الآخرون بالفعل.

لنرى كيف يبدو الفصل لمعالجة مطالبة المال في هذه الحالة. كل شيء واضح: هناك أوامر ، وهناك طلبات تتعلق بعملية التسجيل ، حسنًا ، حافلة بها سجلات.



في هذه الحالة ، هناك معالج واحد. عندما يحدث حدث ويصل فريق لتسجيل المطالبات النقدية ، فإنه يستجيب لذلك. في الداخل ، كل شيء هو نفسه كما كان من قبل ، ولكن الخصوصية هي أن هناك مجموعة حسب العملية.



وبسبب هذا ، أصبح من الأسهل قليلاً ، تغييرات أقل في كل ملف.

الاستنتاجات


ما الذي تحتاج إلى تذكره عند العمل مع CQRS؟ تحتاج إلى نهج أفضل للتصميم ، لأن إعادة كتابة العملية أكثر تعقيدًا بعض الشيء. هناك نفقات صغيرة ، وأصبح عدد أكبر قليلاً من الفصول ، ولكن هذا ليس بالأمر الحاسم. لقد أصبح الكود أقل اتصالًا ، ومع ذلك ، ليس بسبب CQRS ، ولكن بسبب الانتقال إلى الناقل. لكن CQRS هي التي دفعتنا لاستخدام تفاعل الحدث هذا. لقد تمت إضافة الرمز في كثير من الأحيان أكثر من التغيير. هناك المزيد من الفصول ، لكنها الآن أكثر تخصصًا.

هل يحتاج الجميع إلى إسقاط كل شيء والتحول بشكل كبير إلى CQRS؟ لا ، أنت بحاجة إلى النظر إلى السيناريو الذي يعمل بشكل أفضل لمشروع معين. على سبيل المثال ، إذا كان النظام الفرعي الخاص بك يعمل مع الدلائل ، فإن CQRS ليست ضرورية ، فإن الطريقة الكلاسيكية للطبقات تعطي نتيجة أكثر بساطة وأكثر ملاءمة.

النسخة الكاملة للأداء في Panda Meetup متاحة أدناه.


إذا كنت ترغب في الخوض في الموضوع بشكل أعمق ، فمن المنطقي أن تدرس هذه الموارد:

نمط العمارة CQRS - من Microsoft

مدونة الكسندر بنديو

أمثلة لجامعة Contoso مع CQRS و MediatR و AutoMapper والمزيد - بواسطة Jimmy Bogard

CQRS - مارتن فاولر

ريبوس

Source: https://habr.com/ru/post/ar440172/


All Articles