كيف لطهي العصيدة من الخدمات الدقيقة

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

وأود أن أتحدث عن الأنماط والمناهض المختلفة لتقسيم المسؤوليات إلى خدمات ميكروية.

كيان الخدمة كما Antipattern


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

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

على سبيل المثال ، خذ متجر على الإنترنت. قررنا تسليط الضوء على الخدمات "المنتج" ، "النظام" ، "العميل".

ما هي التغييرات والخدمات التي يجب عليّ إضافتها لإضافة خدمة التوصيل إلى المنازل؟
على سبيل المثال ، يمكنك القيام بذلك:

  • في خدمة "النظام" إضافة عنوان التسليم ، والوقت المطلوب ورجل التسليم
  • في خدمة العميل ، أضف قائمة بعناوين التسليم المحددة للعميل
  • في الخدمة "المنتج" إضافة قائمة كيان البضائع

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

أو ما هي التغييرات وما الخدمات التي أحتاج إليها من أجل إضافة خصومات على الشفرة الترويجية؟
تحتاج كحد أدنى:

  • أضف رمزًا ترويجيًا إلى خدمة "الطلب"
  • في خدمة "المنتج" ، أضف ما إذا كانت الخصومات تنطبق على الكود الترويجي لهذا المنتج
  • في خدمة العميل ، أضف قائمة بالرموز الترويجية التي تم إصدارها للعميل

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

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

بمعنى آخر ، فإن التغيير في منطق العمل الذي ينتشر عبر العديد من الخدمات يؤدي إلى تغييرات تابعة في العديد من الخدمات. وفي الوقت نفسه في خدمة واحدة هو رمز غير متصل مع بعضها البعض.

خدمات التخزين


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

إذا تم تخزين البيانات في قواعد بيانات مختلفة ، على أجهزة مختلفة ، فإننا

  • نفقد الأداء لأننا لا نقدم بيانات مباشرة من قاعدة البيانات ، ولكن من خلال طبقة الخدمة
  • نفقد المرونة لأن واجهة برمجة التطبيقات للخدمة عادةً ما تكون أقل مرونة من SQL أو أي لغة استعلام أخرى
  • نفقد المرونة ، لأنه من الصعب إجراء دمج البيانات من خدمات مختلفة

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

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

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

الفصل حسب المناطق المشكلة


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

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

التغييرات التي ستؤثر على العديد من مناطق المشكلات في نفس الوقت أقل من التغييرات التي قد تؤثر على عدة كيانات.

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

على سبيل المثال ، يمكننا تقسيم الخدمات إلى:

  • خدمة أو مجموعة من الخدمات "تسليم" ، والتي سوف تخزن منطق العمل مع تسليم طلب محدد ، وتنظيم عمل الموردين ، وتقييم جودة عملهم ، وتطبيقات الهاتف المحمول للمورد ، إلخ.
  • خدمة أو مجموعة من الخدمات "الفوترة والدفع" ، والتي ستخزن منطق العمل مع الدفع وحسابات الدفع للكيانات القانونية وتوليد العقود والوثائق الختامية.
  • الخدمة أو مجموعة الخدمات "Order Process" ، التي تخزن منطق اختيار العميل للمنتجات ، والفهرسة ، والعلامات التجارية ، ومنطق السلة ، إلخ.
  • خدمة "الترخيص والمصادقة".
  • قد يكون من المنطقي حتى فصل خدمة الخصم.

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

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

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

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

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

والسؤال هو في فصل المسؤوليات وفي ذروة الحواجز أمام التجريد.

تصميم خدمة API


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

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

واجهات CRUD للخدمات ذات المنطق التجاري المعقد


واجهة واسعة جدًا وغير محددة تسهم في تآكل المسؤولية أو التعقيد المفرط.

على سبيل المثال ، واجهة برمجة التطبيقات (API) لـ CRUD للخدمات ذات المنطق التجاري المعقد. فهي لا تعمل فقط على تمكين منطق الأعمال من التسرب إلى الخدمات الأخرى وتآكل مسؤولية الخدمة ، بل إنها تثير انتشار منطق الأعمال - فالقيود ، والمتغيرات وطرق التعامل مع البيانات موجودة الآن في خدمات أخرى. يجب أن تنفذ خدمات مستخدمي الواجهة (APIs) المنطق بأنفسهم.

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

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

دع واجهة برمجة التطبيقات تبدو كما يلي: طرق POST / PATCH / GET ، url /api/v1/tickets/{ticket_idasket.json

لذلك ، يمكنك تحديث التذكرة

PATCH /api/v1/tickets/{ticket_id}.json { "type": "bug", "status": "closed", "description": "   " } 

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

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

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

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

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

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

بدلاً من المورد العالمي /api/v1/tickets.json أضف المزيد من الموارد:

/api/v1/tickets/{ticket_id}/migrate.json - الترحيل من نوع إلى آخر
/api/v1/tickets/{ticket_id}/status.json - إذا كان هناك نموذج حالة

ثانياً ، يمكنك تخيل أي عملية كمورد في إطار REST. هل هناك عملية ترحيل تذكرة من نوع إلى آخر (أو من مشروع إلى آخر؟). حسنا ، لذلك سيكون هناك مورد
/api/v1/tickets/migration.json

هل هناك عملية تجارية لإنشاء اشتراك تجريبي؟
/api/v1/subscriptions/trial.json

هل هناك عملية تحويل أموال؟
/api/v1/money_transfers.json

إلخ

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

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

نموذج الحدث


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

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

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

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

الخدمات الدقيقة والأداء. CQRS


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

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

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

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

من الواضح أن التقسيم الساذج إلى خدمات القراءة والتعديل قد لا يكون جيدًا دائمًا.

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

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


All Articles