WG Contract API: zoo of services



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

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

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

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

قليلا من التاريخ


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

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

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

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

تلبية العقد API


داخل المنصة ، نسميها العقد API. في جوهره ، إنه إطار تكامل يمثله مجموعة من مكتبات الوثائق والعملاء لكل تقنية من مكدسنا (Erlang / Elixir ، Java / Scala ، Python). يتم تطويره ، أولاً وقبل كل شيء ، من أجل تبسيط تكامل مكونات المنصة مع بعضها البعض. ثانيًا ، لمساعدتنا في حل عدد من المشكلات التالية:

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

لذا ، أول الأشياء أولاً.

الاختلافات الأسلوبية في واجهات البرنامج


في رأيي ، نشأت هذه المشكلة كنتيجة لمجموعة من العوامل:

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

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

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

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

وجود التبعيات المباشرة بين المكونات


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

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

واجهنا مجموعة من الخيارات. من هذه ، درسنا بعناية خاصة:

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

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

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

حفظ الوثائق محدثة


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

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

الاستبطان والتصحيح وظائف نهاية إلى نهاية


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

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

ما الذي قمنا بتطويره أعلى واجهة برمجة تطبيقات العقد


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

بوابة للوصول إلى وظائف النظام الأساسي


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

خدمة العمليات الجماهيرية


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

معالجة خطأ النظام الأساسي الموحدة


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

إنشاء تلقائي واجهات المستخدم


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

تسجيل النظام الأساسي


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

الغرض الرئيسي من العقد API


ومع ذلك ، فإن الغرض الرئيسي من العقد API هو خفض تكلفة دمج مكونات النظام الأساسي.

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

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

مثال عقد بيثون
from platform_client import Client client = Client(contracts_path=CONTRACTS_PATH, url=AMQP_URL, app_id='client') client.call("ban-management.create-ban.v1", { "wgid": 1234567890, "reason": "Fraudulent activity", "title": "ru.wot", "component": "game", "bantype": "access_denied", "author_id": "v_nikonovich", "expires_at": "2038-01-19 03:14:07Z" }) { u'ban_id': 31415926, u'wgid': 1234567890, u'title': u'ru.wot', u'component': u'game', u'reason': u'Fraudulent activity', u'bantype': u'access_denied', u'status': u"active", u'started_at': u"2019-02-15T15:15:15Z", u'expires_at': u"2038-01-19 03:14:07Z" } 

نفس العقد الدعوة ، ولكن باستخدام Elixir
 :platform_client.call("ban-management.create-ban.v1", %{ "wgid" => 1234567890, "reason" => "Fraudulent activity", "title" => "ru.wot", "component" => "game", "bantype" => "access_denied", "author_id" => "v_nikonovich", "expires_at" => "2038-01-19 03:14:07Z" }) {:ok, %{ "ban_id" => 31415926, "wgid" => 1234567890, "title" => "ru.wot", "conponent" => "game", "reason" => "Fraudulent activity", "bantype" => "access_denied", "status" => "active", "started_at" => "2019-02-15T15:15:15Z", "expires_at" => "2038-01-19 03:14:07Z" }} 

بدلاً من العقد "ban-management.create-ban.v1" ، يمكن أن يكون هناك أي وظائف أخرى للنظام الأساسي ، على سبيل المثال: "account-management.rename-account.v1" أو "notice-center.create-sms-alert.v1". وكل ذلك سيكون متاحًا من خلال نقطة التكامل الوحيدة مع المنصة.

لن تكون النظرة العامة مكتملة إذا لم تثبت عقد API من وجهة نظر مطور الخادم. ضع في اعتبارك موقفًا يحتاج فيه المطور إلى تنفيذ معالج لنفس عقد ban-management.create-ban.v1.

 from platform_server import BlockingServer, handler class CustomServer(BlockingServer): @handler('ban-management.create-ban.v1') def handle_create_ban(self, params, context): response = do_some_usefull_job(params) return response d = CustomServer(app_id="server", amqp_url=AMQP_URL, contracts_path=CONTRACTS_PATH) d.serve() 

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

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

على سبيل المثال:

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

وهذه ليست قائمة كاملة من السيناريوهات التي يمكن التعبير عنها من خلال نموذج الحدث للتفاعل. هذه قائمة بتلك التي نستخدمها حاليًا.

بدلا من الاستنتاج


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

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


All Articles