نمط النشر والاشتراك على ريلز: برنامج تعليمي للتنفيذ
نشرت: 2022-03-11نمط النشر - الاشتراك (أو pub / sub ، باختصار) هو نمط مراسلة Ruby on Rails حيث لا يقوم مرسلو الرسائل (الناشرون) ببرمجة الرسائل لإرسالها مباشرة إلى مستلمين محددين (مشتركين). بدلاً من ذلك ، "ينشر" المبرمج رسائل (أحداث) ، دون معرفة أي مشترك قد يكون هناك.
وبالمثل ، يعبر المشتركون عن اهتمامهم بحدث واحد أو أكثر ، ولا يتلقون سوى الرسائل التي تهمهم ، دون معرفة أي ناشر.
ولتحقيق ذلك ، يتلقى الوسيط ، المسمى "وسيط الرسائل" أو "ناقل الحدث" ، الرسائل المنشورة ، ثم يعيد توجيهها إلى المشتركين المسجلين لاستلامها.
بعبارة أخرى ، يعد pub-sub نمطًا يستخدم لتوصيل الرسائل بين مكونات النظام المختلفة دون أن تعرف هذه المكونات أي شيء عن هوية بعضها البعض.
نمط التصميم هذا ليس جديدًا ، لكنه غير مستخدم بشكل شائع من قبل مطوري ريلز. هناك العديد من الأدوات التي تساعد في دمج نمط التصميم هذا في قاعدة التعليمات البرمجية الخاصة بك ، مثل:
- Wisper (الذي أفضله شخصيًا وسأناقشه أكثر)
- EventBus
- EventBGBus (شوكة EventBus)
- الأرنب
- ريديس
تحتوي كل هذه الأدوات على تطبيقات عمومية فرعية أساسية مختلفة ، لكنها تقدم جميعها نفس المزايا الرئيسية لتطبيق ريلز.
مزايا تطبيق Pub-Sub
نموذج مخفض / سخام تحكم
من الممارسات الشائعة ، ولكن ليس من أفضل الممارسات ، أن يكون لديك بعض النماذج أو وحدات التحكم في تطبيق ريلز الخاص بك.
يمكن أن يساعد نمط الحانة / الفرعي بسهولة في تحلل النماذج الدهنية أو وحدات التحكم.
عدد أقل من عمليات الاسترجاعات
إن وجود الكثير من عمليات الاسترجاعات المتشابكة بين النماذج هي رائحة كود معروفة ومعروفة ، وتدرج شيئًا فشيئًا بإحكام بين النماذج معًا ، مما يجعل من الصعب صيانتها أو تمديدها.
على سبيل المثال ، يمكن أن يبدو نموذج Post كما يلي:
# app/models/post.rb class Post # ... field: content, type: String # ... after_create :create_feed, :notify_followers # ... def create_feed Feed.create!(self) end def notify_followers User::NotifyFollowers.call(self) end end وقد تبدو وحدة التحكم Post كما يلي:
# app/controllers/api/v1/posts_controller.rb class Api::V1::PostsController < Api::V1::ApiController # ... def create @post = current_user.posts.build(post_params) if @post.save render_created(@post) else render_unprocessable_entity(@post.errors) end end # ... end كما ترى ، يحتوي نموذج Post على عمليات استرجاعات تربط النموذج بإحكام بكل من نموذج Feed وخدمة User::NotifyFollowers أو مخاوفهم. باستخدام أي نمط pub / sub ، يمكن إعادة تحليل الكود السابق ليكون شيئًا مشابهًا لما يلي ، والذي يستخدم Wisper:
# app/models/post.rb class Post # ... field: content, type: String # ... # no callbacks in the models! endينشر الناشرون الحدث مع كائن حمولة الحدث الذي قد يكون مطلوبًا.
# app/controllers/api/v1/posts_controller.rb # corresponds to the publisher in the previous figure class Api::V1::PostsController < Api::V1::ApiController include Wisper::Publisher # ... def create @post = current_user.posts.build(post_params) if @post.save # Publish event about post creation for any interested listeners publish(:post_create, @post) render_created(@post) else # Publish event about post error for any interested listeners publish(:post_errors, @post) render_unprocessable_entity(@post.errors) end end # ... endيشترك المشتركون فقط في الأحداث التي يرغبون في الرد عليها.
# app/listener/feed_listener.rb class FeedListener def post_create(post) Feed.create!(post) end end # app/listener/user_listener.rb class UserListener def post_create(post) User::NotifyFollowers.call(self) end endيقوم Event Bus بتسجيل المشتركين المختلفين في النظام.
# config/initializers/wisper.rb Wisper.subscribe(FeedListener.new) Wisper.subscribe(UserListener.new) في هذا المثال ، ألغى نمط pub-sub تمامًا عمليات الاسترجاعات في نموذج Post وساعد النماذج على العمل بشكل مستقل عن بعضها البعض مع الحد الأدنى من المعرفة ببعضها البعض ، مما يضمن اقترانًا سائبًا. إن توسيع السلوك إلى إجراءات إضافية هو مجرد مسألة ربط بالحدث المطلوب.
مبدأ المسؤولية الفردية (SRP)
مبدأ المسؤولية الفردية مفيد حقًا في الحفاظ على قاعدة رمز نظيفة. مشكلة التمسك به هي أنه في بعض الأحيان تكون مسؤولية الفصل غير واضحة كما ينبغي. هذا شائع بشكل خاص عندما يتعلق الأمر بـ MVCs (مثل Rails).
يجب أن تتعامل النماذج مع المثابرة والجمعيات وليس أي شيء آخر.
يجب أن تتعامل وحدات التحكم مع طلبات المستخدم وأن تكون ملتفة حول منطق الأعمال (كائنات الخدمة).
يجب أن تلخص كائنات الخدمة إحدى مسؤوليات منطق الأعمال ، أو توفر نقطة دخول للخدمات الخارجية ، أو تعمل كبديل لمخاوف النموذج.
بفضل قدرتها على تقليل الاقتران ، يمكن دمج نمط التصميم العام الفرعي مع كائنات خدمة المسؤولية الفردية (SRSOs) للمساعدة في تغليف منطق الأعمال ، ومنع منطق الأعمال من التسلل إلى النماذج أو وحدات التحكم. هذا يحافظ على قاعدة التعليمات البرمجية نظيفة وقابلة للقراءة وقابلة للصيانة وقابلة للتطوير.
فيما يلي مثال لبعض منطق الأعمال المعقد الذي تم تنفيذه باستخدام نمط pub / sub وكائنات الخدمة:
الناشر
# app/service/financial/order_review.rb class Financial::OrderReview include Wisper::Publisher # ... def self.call(order) if order.approved? publish(:order_create, order) else publish(:order_decline, order) end end # ...مشتركين
# app/listener/client_listener.rb class ClientListener def order_create(order) # can implement transaction using different service objects Client::Charge.call(order) Inventory::UpdateStock.call(order) end def order_decline(order) Client::NotifyDeclinedOrder(order) end endباستخدام نمط النشر والاشتراك ، يتم تنظيم قاعدة الشفرة في SRSOs تلقائيًا تقريبًا. علاوة على ذلك ، فإن تنفيذ التعليمات البرمجية لعمليات سير العمل المعقدة يتم تنظيمه بسهولة حول الأحداث ، دون التضحية بقابلية القراءة أو قابلية الصيانة أو قابلية التوسع.

اختبارات
من خلال تحليل النماذج الدهنية ووحدات التحكم ، وامتلاك الكثير من SRSOs ، يصبح اختبار قاعدة الشفرة عملية أسهل بكثير. هذا هو الحال بشكل خاص عندما يتعلق الأمر باختبار التكامل والاتصال بين الوحدات. يجب أن يضمن الاختبار ببساطة نشر الأحداث واستلامها بشكل صحيح.
يحتوي Wisper على جوهرة اختبار تضيف أدوات مطابقة RSpec لتسهيل اختبار المكونات المختلفة.
في المثالين السابقين (مثال Post ومثال Order ) ، يجب أن يتضمن الاختبار ما يلي:
الناشرون
# spec/service/financial/order_review.rb describe Financial::OrderReview do it 'publishes :order_create' do @order = Fabricate(:order, approved: true) expect { Financial::OrderReview.call(@order) }.to broadcast(:order_create) end it 'publishes :order_decline' do @order = Fabricate(:order, approved: false) expect { Financial::OrderReview.call(@order) }.to broadcast(:order_decline) end endمشتركين
# spec/listeners/feed_listener_spec.rb describe FeedListener do it 'receives :post_create event on PostController#create' do expect(FeedListner).to receive(:post_create).with(Post.last) post '/post', { content: 'Some post content' }, request_headers end endومع ذلك ، هناك بعض القيود على اختبار الأحداث المنشورة عندما يكون الناشر هو المتحكم.
إذا كنت ترغب في الذهاب إلى أبعد من ذلك ، فإن اختبار الحمولة الصافية أيضًا سيساعد في الحفاظ على قاعدة أكواد أفضل.
كما ترى ، فإن اختبار نمط تصميم pub-sub بسيط. إنها فقط مسألة ضمان نشر الأحداث المختلفة واستلامها بشكل صحيح.
أداء
هذا أكثر من ميزة محتملة . نمط تصميم الاشتراك-النشر نفسه ليس له تأثير جوهري كبير على أداء الكود. ومع ذلك ، كما هو الحال مع أي أداة تستخدمها في التعليمات البرمجية الخاصة بك ، يمكن أن يكون لأدوات تنفيذ pub / sub تأثير كبير على الأداء. في بعض الأحيان يمكن أن يكون له تأثير سيء ، ولكن في بعض الأحيان يمكن أن يكون جيدًا جدًا.
أولاً ، مثال على التأثير السيئ: Redis هو "ذاكرة تخزين مؤقت لقيمة المفتاح ومخزن متقدم. غالبًا ما يشار إليه بخادم بنية البيانات ". تدعم هذه الأداة الشائعة نمط pub / sub وهي مستقرة جدًا. ومع ذلك ، إذا تم استخدامه على خادم بعيد (وليس نفس الخادم الذي تم نشر تطبيق ريلز عليه) ، فسيؤدي ذلك إلى خسارة كبيرة في الأداء بسبب الحمل الزائد للشبكة.
من ناحية أخرى ، لدى Wisper محولات مختلفة للتعامل مع الأحداث غير المتزامنة ، مثل Wisper-celluloid و Wisper-sidekiq و Wisper-activejob. تدعم هذه الأدوات الأحداث غير المتزامنة وعمليات التنفيذ المترابطة. والتي إذا تم تطبيقها بشكل مناسب ، يمكن أن تزيد بشكل كبير من أداء التطبيق.
الخط السفلي
إذا كنت تهدف إلى الميل الإضافي في الأداء ، فيمكن أن يساعدك نمط الحانة / الفرع في الوصول إليه. ولكن حتى إذا لم تجد تعزيزًا في الأداء باستخدام نمط تصميم ريلز هذا ، فسيظل يساعد في الحفاظ على الكود منظمًا وجعله أكثر قابلية للصيانة. بعد كل شيء ، من يمكنه القلق بشأن أداء الكود الذي لا يمكن صيانته ، أو الذي لا يعمل في المقام الأول؟
عيوب تطبيق Pub-Sub
كما هو الحال مع كل الأشياء ، هناك بعض العيوب المحتملة في نمط pub-sub أيضًا.
اقتران فضفاض (اقتران دلالي غير مرن)
إن أعظم نقاط القوة في نمط الحانة / الفرعي هي أيضًا أكبر نقاط ضعفها. يجب تحديد بنية البيانات المنشورة (حمولة الحدث) جيدًا ، وسرعان ما تصبح غير مرنة إلى حد ما. لتعديل بنية بيانات الحمولة المنشورة ، من الضروري معرفة جميع المشتركين ، وإما تعديلهم أيضًا ، أو التأكد من توافق التعديلات مع الإصدارات الأقدم. هذا يجعل إعادة هيكلة كود Publisher أكثر صعوبة.
إذا كنت تريد تجنب ذلك ، يجب أن تكون أكثر حذرًا عند تحديد حمولة الناشرين. بالطبع ، إذا كان لديك مجموعة اختبار رائعة ، تختبر الحمولة كما ذكرنا سابقًا ، فلا داعي للقلق كثيرًا بشأن تعطل النظام بعد تغيير حمولة الناشر أو اسم الحدث.
استقرار ناقل الرسائل
الناشرون ليس لديهم معرفة بحالة المشترك والعكس صحيح. باستخدام أدوات pub / sub بسيطة ، قد لا يكون من الممكن ضمان استقرار ناقل الرسائل نفسه ، والتأكد من وضع جميع الرسائل المنشورة في قائمة الانتظار وتسليمها بشكل صحيح.
يؤدي العدد المتزايد من الرسائل التي يتم تبادلها إلى عدم الاستقرار في النظام عند استخدام أدوات بسيطة ، وقد لا يكون من الممكن ضمان التسليم لجميع المشتركين دون بعض البروتوكولات الأكثر تعقيدًا. اعتمادًا على عدد الرسائل التي يتم تبادلها ، ومعلمات الأداء التي تريد تحقيقها ، قد تفكر في استخدام خدمات مثل RabbitMQ أو PubNub أو Pusher أو CloudAMQP أو IronMQ أو العديد من البدائل الأخرى. توفر هذه البدائل وظائف إضافية ، وهي أكثر استقرارًا من Wisper للأنظمة الأكثر تعقيدًا. ومع ذلك ، فإنها تتطلب أيضًا بعض العمل الإضافي للتنفيذ. يمكنك قراءة المزيد حول كيفية عمل وسطاء الرسائل هنا
حلقات الأحداث اللانهائية
عندما يكون النظام مدفوعًا بالكامل بالأحداث ، يجب أن تكون أكثر حذرًا من عدم وجود حلقات أحداث. هذه الحلقات تشبه تمامًا الحلقات اللانهائية التي يمكن أن تحدث في الكود. ومع ذلك ، يصعب اكتشافها مسبقًا ، ويمكن أن تؤدي إلى توقف نظامك. يمكن أن تتواجد دون إشعارك عندما يكون هناك العديد من الأحداث المنشورة والمشتركة عبر النظام.
خاتمة دروس ريلز
لا يُعد نمط الاشتراك - النشر حلًا سحريًا لجميع مشكلات Rails ورائحة التعليمات البرمجية ، ولكنه نمط تصميم جيد حقًا يساعد في فصل مكونات النظام المختلفة ، وجعلها أكثر قابلية للصيانة ، وقابلة للقراءة ، وقابلة للتطوير.
عند دمجه مع كائنات خدمة المسؤولية الفردية (SRSOs) ، يمكن أيضًا أن يساعد pub-sub حقًا في تغليف منطق الأعمال ومنع مخاوف العمل المختلفة من التسلل إلى النماذج أو وحدات التحكم.
يعتمد مكاسب الأداء بعد استخدام هذا النمط في الغالب على الأداة الأساسية المستخدمة ، ولكن يمكن تحسين مكاسب الأداء بشكل ملحوظ في بعض الحالات ، وفي معظم الحالات لن يضر ذلك بالأداء بالتأكيد.
ومع ذلك ، يجب دراسة استخدام نمط pub-sub والتخطيط له بعناية ، لأنه مع القوة الكبيرة للاقتران السائب ، تأتي المسؤولية الكبيرة لصيانة وإعادة بناء المكونات المقترنة بشكل غير محكم.
نظرًا لأن الأحداث يمكن أن تخرج عن نطاق السيطرة بسهولة ، فقد لا تضمن مكتبة عامة / فرعية بسيطة استقرار وسيط الرسائل.
وأخيرًا ، هناك خطر إدخال حلقات أحداث لا نهائية تمر دون أن يلاحظها أحد حتى فوات الأوان.
لقد كنت أستخدم هذا النمط منذ ما يقرب من عام الآن ، ومن الصعب بالنسبة لي تخيل كتابة التعليمات البرمجية بدونه. بالنسبة لي ، فإن الغراء هو الذي يجعل الوظائف الخلفية ، وكائنات الخدمة ، والمخاوف ، ووحدات التحكم والنماذج تتواصل جميعها مع بعضها البعض بشكل نظيف وتعمل معًا مثل السحر.
آمل أن تكون قد تعلمت بقدر ما تعلمت من مراجعة هذا الرمز ، وأنك تشعر بالإلهام لمنح نمط Publish-Subscribe فرصة لجعل تطبيق Rails الخاص بك رائعًا.
أخيرًا ، شكراً جزيلاً لـ krisleech على عمله الرائع في تنفيذ Wisper.
