هل يمكنني استخدام Redux على الخادم؟

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

لماذا أحتاج إلى مكتبة Redux؟


تقول الصفحة الرئيسية لمكتبة Redux إنها "حاوية حالة يمكن التنبؤ بها لتطبيقات JavaScript". يشار عادة إلى Redux كأداة لإدارة حالة التطبيق ، وعلى الرغم من أن هذه المكتبة تستخدم بشكل رئيسي مع React ، إلا أنه يمكن استخدامها في أي مشاريع قائمة على JavaScript.

لقد ذكرنا بالفعل أن Redux يستخدم للتحكم في حالة التطبيق. الآن دعنا نتحدث عن ماهية "الشرط". من الصعب تحديد هذا المفهوم ، لكننا لا نزال نحاول وصفه.

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

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

عندما نتعامل مع البرامج ، ترتبط بعض الميزات بمفهوم "الحالة". أولاً ، يتم تمثيل حالة التطبيق بالبيانات المخزنة في مكان ما. على سبيل المثال ، يمكن تخزين هذه البيانات في الذاكرة (على سبيل المثال ، ككائن JavaScript) ، ولكن يمكن تخزينها في ملف ، في قاعدة بيانات ، واستخدام بعض آليات التخزين المؤقت مثل Redis. ثانياً ، ترتبط حالة الطلب عادةً بمثيله المحدد. لذلك ، عندما نتحدث عن حالة التطبيق ، فإننا نعني مثيل معين لهذا التطبيق ، عملية ، بيئة عمل منظمة في التطبيق لمستخدم معين. قد تتضمن حالة التطبيق ، على سبيل المثال ، المعلومات التالية:

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

إذا تحدثنا عن حالة التطبيقات على مستوى أقل ، فيمكنها أن تشمل ، على سبيل المثال ، المعلومات التالية:

  • ما المتغيرات التي تم تعيينها في البيئة الحالية التي يعمل فيها التطبيق (يشير ذلك إلى ما يسمى "متغيرات البيئة").
  • ما الملفات التي يستخدمها البرنامج حاليًا؟

عند النظر إلى "اللقطة" (التي يطلق عليها غالبًا "اللقطات" - من اللقطة) لحالة التطبيق في أي وقت ، يمكننا التعرف على الشروط التي عمل فيها التطبيق في تلك اللحظة ، وإذا لزم الأمر ، أعد إنشاء هذه الشروط عن طريق التطبيق إلى الحالة التي كان في وقت تلقي لقطة.

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

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

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


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

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

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

كيف يعمل Redux؟


في مكتبة Redux ، هناك ثلاثة مفاهيم رئيسية تهدف إلى جعل إدارة حالة التطبيق بسيطة ومباشرة:

  1. التخزين (متجر). مستودع Redux هو كائن JavaScript يمثل حالة التطبيق. إنها تلعب دور "المصدر الوحيد لبيانات موثوقة." هذا يعني أن التطبيق بالكامل يجب أن يعتمد على التخزين باعتباره الكيان الوحيد المسؤول عن تمثيل الحالة.
  2. الإجراءات متجر الدولة للقراءة فقط. هذا يعني أنه لا يمكن تعديله عن طريق الوصول إليه مباشرةً. الطريقة الوحيدة لتعديل محتويات المستودع هي استخدام الإجراءات. أي مكون يريد تغيير الحالة يجب أن يتخذ الإجراء المناسب.
  3. المخفضات (المخفضات) ، والتي تسمى أيضا "المحولات". المخفض هو وظيفة خالصة تصف كيفية تعديل الحالة من خلال الإجراءات. يأخذ المخفض الحالة والإجراء الحاليين ، والذي تم تنفيذه بواسطة مكون معين من التطبيق ، وبعد ذلك يقوم بإرجاع الحالة المحولة.

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

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

فيما يلي مثال تخطيطي يوضح كيف يمكنك تنظيم نظام إدارة حالة بسيط باستخدام Redux في تطبيقنا الخيالي:

import { createStore } from 'redux'; //  const tweets = (state = {tweets: []}, action) => {  switch (action.type) {    //     ,     .    case 'SHOW_NEW_TWEETS':      state.numberOfNewTweets = action.count;      return state.tweets.concat([action.tweets]);    default:      return state;  } }; //  ,     . SHOW_NEW_TWEETS const newTweetsAction = (tweets) => {  return {      type: 'SHOW_NEW_TWEETS',      tweets: tweets,      count: tweets.length  }; }; const store = createStore(tweets); twitterApi.fetchTweets()  .then(response => {    //  ,        ,    //    Redux.    store.dispatch(newTweetsAction(response.data));  }); //  ,    SHOW_NEW_TWEETS     //         . const postTweet = (text) => {  twitterApi.postTweet(text)  .then(response => {    store.dispatch(newTweetsAction([response.data]));  }); }; // ,  ,   WebSocket,   . //         . SHOW_NEW_TWEETS socket.on('newTweets', (tweets) => { store.dispatch(newTweetsAction(tweets)); }; //     ,  React,       , // ,         . //         , //    . store.subscribe(() => {  const { tweets } = store.getSTate();  render(tweets); }); 

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

فيما يلي المواد التي يمكنك من خلالها معرفة المزيد حول المبادئ الأساسية الثلاثة لـ Redux.

الآن دعنا نتحدث عن استخدام Redux في بيئة الخادم.

ترحيل مبادئ الإعادة إلى بيئة الخادم


استكشفنا ميزات Redux المستخدمة في تطوير تطبيقات العميل. ولكن نظرًا لأن Redux مكتبة JavaScript ، فيمكن أيضًا استخدامها نظريًا في بيئة الخادم. سوف نفكر في كيفية تطبيق المبادئ المذكورة أعلاه على الخادم.

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

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

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

يقودنا هذا إلى سبب مهم لعدم استخدام Redux على الخوادم بالشكل الذي وصفنا به إمكانياته أعلاه.

الحقيقة هي أنه تم تصميم Redux لتخزين الحالة المؤقتة للتطبيق. ولكن يجب أن تكون حالة التطبيق المخزنة على الخادم موجودة لفترة كافية. إذا كنت تستخدم مستودع Redux في تطبيق Node.js من جانب الخادم ، فسيتم مسح حالة هذا التطبيق في كل مرة تتوقف فيها عملية node . وإذا كنا نتحدث عن خادم PHP يقوم بتنفيذ خطة مماثلة لإدارة الدولة ، فسيتم مسح الحالة عند وصول كل طلب جديد إلى الخادم.

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

كيفية تطبيق مبادئ إدارة الدولة التي ناقشناها على الخادم؟ دعونا نلقي نظرة أخرى على مفاهيم Redux ونرى كيف يتم استخدامها عادة في بيئة الخادم:

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

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

CQRS ومصادر الحدث


CQRS (فصل مسؤولية استعلامات الأوامر) هو نمط تصميم ، حيث يقوم التطبيق بقراءة البيانات من المتجر فقط باستخدام الاستعلامات ، والكتابة فقط باستخدام الأوامر.

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

 const action = { type: 'CREATE_NEW_USER', payload: ... }; store.dispatch(action); //      const createUser = (state = {}, action) => { // }; 

عند استخدام CQRS ، يبدو شيء مثل هذا كما يلي:

 //      class Command { handle() { } } class CreateUserCommand extends Command { constructor(user) {   super();   this.user = user; } handle() {   //        } } const createUser = new CreateUserCommand(user); //   (   handle()) dispatch(createUser); //      CommandHandler commandHandler.handle(createUser); 

الاستعلامات هي آليات قراءة البيانات في قالب CQRS. وهي مكافئة store.getState() . في تطبيق CQRS البسيط ، ستتفاعل الاستعلامات مباشرة مع قاعدة البيانات ، مع استرداد السجلات منها.

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

فيما يلي مثال لتطبيق قالب "تحديد مصادر الأحداث":

 //    Event Sourcing function transferMoneyBetweenAccounts(amount, fromAccount, toAccount) {   BankAccount.where({ id: fromAccount.id })     .decrement({ amount });   BankAccount.where({ id: toAccount.id })     .increment({ amount }); } function makeOnlinePayment(account, amount) {   BankAccount.where({ id: account.id })     .decrement({ amount }); } //    Event Sourcing function transferMoneyBetweenAccounts(amount, fromAccount, toAccount) {   dispatchEvent(new TransferFrom(fromAccount, amount, toAccount));   dispatchEvent(new TransferTo(toAccount, amount, fromAccount)); } function makeOnlinePayment(account, amount) {   dispatchEvent(new OnlinePaymentFrom(account, amount)); } class TransferFrom extends Event {   constructor(account, amount, toAccount) {     this.account = account;     this.amount = amount;     this.toAccount = toAccount;   }     handle() {     //    OutwardTransfer        OutwardTransfer.create({ from: this.account, to: this.toAccount, amount: this.amount, date: Date.now() });         //          BankAccount.where({ id: this.account.id })       .decrement({ amount: this.amount });   } } class TransferTo extends Event {   constructor(account, amount, fromAccount) {     this.account = account;     this.amount = amount;     this.fromAccount = fromAccount;   }     handle() {     //    InwardTransfer        InwardTransfer.create({ from: this.fromAccount, to: this.account, amount: this.amount, date: Date.now() });         //          BankAccount.where({ id: this.account.id })       .increment({ amount: this.amount });   } } class OnlinePaymentFrom extends Event {   constructor(account, amount) {     this.account = account;     this.amount = amount;   }     handle() {     //    OnlinePayment        OnlinePayment.create({ from: this.account, amount: this.amount, date: Date.now() });         //          BankAccount.where({ id: this.account.id })       .decrement({ amount: this.amount });   } } 

ما يحدث هنا يشبه أيضًا العمل مع إجراءات Redux.

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

غالبًا ما تستخدم قوالب CQRS و Sourcing Event معًا. ومن المثير للاهتمام ، Redux ، في الواقع ، يعتمد جزئيا على هذه القوالب. يمكن كتابة الأوامر بحيث تقوم ، عند الاتصال ، بإرسال الأحداث. ثم تتفاعل الأحداث مع المستودع (قاعدة البيانات) وتحديث الحالة. في التطبيقات في الوقت الفعلي ، يمكن لكائنات الاستعلام أيضًا الاستماع إلى الأحداث وتلقي معلومات الحالة المحدثة من المستودع.

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

يرجى ملاحظة أن أنماط CQRS و Sourcing Event يمكن تنفيذها بطرق مختلفة ، في حين أن بعض تطبيقاتها أكثر تعقيدًا من غيرها. لقد نظرنا فقط في أمثلة بسيطة جدا لتنفيذها. إذا كنت تكتب تطبيقات الخادم في Node.js ، يمكنك إلقاء نظرة على wolkenkit . يوفر هذا الإطار ، من بين ما تم العثور عليه في هذه المنطقة ، للمطور واحدة من أبسط واجهات تنفيذ قوالب CQRS و Event Sourcing.

ملخص


Redux هي أداة رائعة لإدارة حالة التطبيق ، لجعل تغييرات الحالة قابلة للتنبؤ. في هذه المقالة ، تحدثنا عن المفاهيم الأساسية لهذه المكتبة ووجدنا أنه على الرغم من أن استخدام Redux في بيئة خادم ليس فكرة جيدة ، إلا أنه يمكنك تطبيق مبادئ مماثلة على خادم باستخدام قوالب CQRS و Event Sourcing .

أعزائي القراء! كيف تنظم إدارة الدولة لتطبيقات العميل والخادم؟

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


All Articles