مرحباً هبر! تتواصل RBKmoney مرة أخرى وتستمر في سلسلة من المقالات حول كيفية كتابة عملية الدفع الخاصة بك.

كنت أرغب في الانغماس على الفور في تفاصيل وصف عملية تنفيذ عملية الدفع كجهاز حكومي ، وإظهار أمثلة على مثل هذا الجهاز مع مجموعة من الأحداث ، وميزات التنفيذ ... ولكن يبدو أنه لا يمكنك القيام بذلك دون قراءة المزيد من مقالات المراجعة. اتضح أن موضوع الموضوع كبير جدًا. سوف يكشف هذا المنشور عن الفروق الدقيقة في العمل والتفاعل بين الخدمات المصغرة في نظامنا الأساسي ، والتفاعل مع الأنظمة الخارجية وكيفية إدارة تكوين العمل.
خدمة ماكرو
يتكون نظامنا من العديد من الخدمات المصغرة التي تتفاعل مع بعضها البعض وتنفذ معًا خدمة ماكرو من خلال تنفيذ كل جزء من الأجزاء النهائية من منطق الأعمال. في الواقع ، فإن خدمة الماكرو التي يتم نشرها في مركز البيانات ، والمتصلة بالبنوك وأنظمة الدفع الأخرى ، هي معالجة الدفع الخاصة بنا.
قالب Microservice
نحن نستخدم نهجًا موحدًا لتطوير أي خدمة ميكروية بأي لغة مكتوبة. كل microservice عبارة عن حاوية Docker تحتوي على:
- التطبيق نفسه الذي ينفذ منطق الأعمال المكتوبة في إرلانج أو جافا ؛
- RPClib - مكتبة تنفذ الاتصالات بين الخدمات الصغيرة ؛
- نحن نستخدم Apache Thrift ، ومزاياه الرئيسية هي مكتبات الخادم العميل الجاهزة والقدرة على التحديد الدقيق لوصف جميع الطرق العامة التي تقدمها كل خدمة ميكروسكس ؛
- الميزة الثانية للمكتبة هي تطبيقنا لـ Google Dapper ، والذي يسمح لنا بتتبع الطلبات بسرعة من خلال بحث بسيط في Elasticsearch. تنشئ الخدمة الأولى التي تلقت طلبًا من نظام خارجي
trace_id
فريدًا يتم حفظه بواسطة كل سلسلة طلبات لاحقة. وأيضًا ، نقوم بإنشاء وحفظ parent_id
و span_id
، مما يسمح لك بإنشاء شجرة استعلام ، ومراقبة بصرية لسلسلة كاملة من الخدمات المصغرة المتضمنة في معالجة الطلب ؛ - الميزة الثالثة - نحن نستخدم بنشاط النقل على مستوى نقل المعلومات المختلفة حول سياق الطلب. على سبيل المثال ، المواعيد النهائية (العمر المتوقع للطلب الذي تم تعيينه على العميل) ، أو نيابةً عن إجراء مكالمة إلى إحدى الطرق ؛
- قالب القنصل هو وكيل لاكتشاف الخدمة يحتفظ بمعلومات حول موقع وتوافر وحالة الخدمة المجهرية. تجد Microservices بعضها البعض عن طريق أسماء DNS ، والمنطقة TTL هي صفر ، والخدمة التي ماتت أو لم تتخط الفحص الطبي توقف عن الحل ، وبالتالي تستقبل حركة المرور ؛
- سجلات مكتوبة بواسطة التطبيق بتنسيق يمكن فهمه إلى Elasticsearch إلى ملف الحاوية المحلي و
filebeat
، الذي يعمل على الجهاز المضيف بالنسبة للحاوية ، يلتقط هذه السجلات ويرسلها إلى مجموعة Elasticsearch ؛
- نظرًا لأننا نطبق النظام الأساسي وفقًا لنموذج Event Sourcing ، فإن سلاسل السجلات الناتجة تُستخدم أيضًا للتصور في شكل لوحات معلومات Grafana مختلفة ، مما يسمح لنا بتقليل وقت تنفيذ مقاييس مختلفة (نستخدم أيضًا مقاييس منفصلة).

عند تطوير خدمات micros ، نستخدم القيود التي اخترعناها خصيصًا ، والتي صممت لحل مشكلة توفر كلٍّ كبير من النظام الأساسي وتحمله للأخطاء:
- حدود صارمة للذاكرة لكل حاوية ، عندما تتجاوز الحدود - OOM ، تعيش معظم الخدمات الميكروية ضمن 256-512 م. هذا يجعله أكثر تجزئة لتنفيذ منطق العمل ، ويحمي من الانجراف نحو المتراصة ، ويقلل من تكلفة نقطة الفشل ، ويعطي ميزة إضافية القدرة على العمل على أجهزة رخيصة (يتم نشر النظام الأساسي ويعمل على خوادم أحادية المعالج غير مكلفة) ؛
- أقل عدد ممكن من الخدمات المجهرية ذات الحالة الممكنة وكذلك أكبر عدد ممكن من تطبيقات عديمي الجنسية. هذا يسمح لنا بحل مشاكل التسامح مع الخطأ ، وسرعة الشفاء ، وبصورة عامة ، التقليل إلى الحد الأدنى من الأماكن بسلوك غير مفهوم. يصبح هذا مهمًا بشكل خاص مع زيادة عمر النظام عند تراث تراث كبير ؛
- ندعه يتعطل و "بالتأكيد سوف كسر" النهج. نحن نعلم أن أي جزء من نظامنا سوف يفشل بالضرورة ، لذلك نقوم بتصميمه بحيث لا يؤثر ذلك على صحة المعلومات المتراكمة في النظام الأساسي بشكل عام. يساعد في تقليل عدد الحالات غير المحددة في النظام.
بالتأكيد مألوفة للكثيرين الذين يتكاملون مع أطراف ثالثة ، فإن الوضع. لقد توقعنا استجابة من طرف ثالث لطلب شطب الأموال وفقًا للبروتوكول ، وجاءت إجابة مختلفة تمامًا ، لم يرد وصفها في أي مواصفات ، وهو أمر غير معروف كيفية ترجمته.
في هذه الحالة ، نقتل آلة الحالة التي تخدم هذه الدفعة ، وستتلقى أي إجراءات عليها من الخارج خطأ قدره 500. وفي الداخل نكتشف الحالة الحالية للدفع ، ونجعل حالة الجهاز متماشية مع الواقع ونعيد تنشيط آلة الحالة.
التنمية الموجه للبروتوكول

في وقت كتابة هذا التقرير ، تم تسجيل 636 شيكًا مختلفًا في خدمة اكتشاف الخدمة بحثًا عن الخدمات التي تضمن أداء النظام الأساسي. حتى مع الأخذ في الاعتبار أنه يتم إجراء العديد من عمليات الفحص على خدمة واحدة ، وأيضًا أن معظم خدمات عديمي الجنسية تعمل في حالة ثلاثية على الأقل ، لا يزال بإمكانك الحصول على خمسين تطبيقًا تحتاج إلى أن تكون قادرًا على أن تكون متصلاً بطريقة ما ولا تفشل في RPC الجحيم.
الموقف معقد بسبب حقيقة أن لدينا ثلاث لغات تطوير في المجموعة - Erlang و Java و JS ، وكلهم بحاجة إلى أن يكونوا قادرين على التواصل بشفافية مع بعضهم البعض.
كانت المهمة الأولى التي تحتاج إلى حل هي تصميم البنية الصحيحة لتبادل البيانات بين الخدمات الصغيرة. كأساس ، اتخذنا أباتشي التوفير. تتبادل جميع الخدمات الميكروية الثنائيات الثلاثية ؛ نستخدم HTTP كوسيلة نقل.
نضع مواصفات الخط في شكل مستودعات منفصلة في جيثب لدينا ، بحيث تكون متاحة لأي مطور لديه حق الوصول إليها. في البداية ، استخدموا مستودعًا مشتركًا واحدًا لجميع البروتوكولات ، ولكن مع مرور الوقت توصلوا إلى استنتاج مفاده أن هذا غير مريح - تحول العمل الموازي المشترك على البروتوكولات إلى صداع مستمر. تم إجبار الفرق المختلفة وحتى المطورين المختلفين على الاتفاق على اسم المتغيرات ، ولم تساعد محاولة الانقسام إلى مساحة الاسم أيضًا.
بشكل عام ، يمكننا أن نقول أن لدينا تطوير يحركها البروتوكول. قبل البدء في أي تطبيق ، نقوم بتطوير بروتوكول الخدمة المجهرية في المستقبل في شكل مواصفات للرفع ، والانتقال إلى 7 دوائر مراجعة ، وجذب العملاء المستقبليين لهذه الخدمة المجهرية ، وإتاحة الفرصة للبدء في وقت واحد في تطوير العديد من الخدمات المجهرية بالتوازي ، لأننا نعرف كل طرقها المستقبلية ويمكننا بالفعل كتابة معالجاتها ، اختياريا باستخدام موكي.
هناك خطوة منفصلة في عملية تطوير البروتوكول وهي المراجعة الأمنية ، حيث ينظر الرجال من وجهة نظرهم المكبوتة إلى الفروق الدقيقة في المواصفات التي يتم تطويرها.
كما اعتبرنا أنه من المناسب إبراز دور منفصل لمالك البروتوكول في الفريق. المهمة صعبة ، يجب على الشخص أن يضع في اعتباره تفاصيل جميع الخدمات الصغيرة ، لكنه يؤتي ثماره بترتيب كبير ووجود نقطة تصعيد واحدة.
بدون الموافقة النهائية على طلب السحب من قبل هؤلاء الموظفين ، لا يمكن دمج البروتوكول في فرع رئيسي. هناك وظيفة مريحة للغاية في جيثب لهذا - أصحاب الرموز ، ونحن نستخدمها بكل سرور.
وبالتالي ، قمنا بحل مشكلة التواصل بين الخدمات الصغيرة ، ومشاكل سوء الفهم المحتملة ، ما نوع الخدمة الميكروية التي ظهرت في المنصة ، والسبب في الحاجة إليها. ربما تكون هذه المجموعة من البروتوكولات هي الجزء الوحيد من المنصة حيث نختار الجودة دون قيد أو شرط مقابل تكلفة وسرعة التطوير ، لأنه يمكن إعادة كتابة تنفيذ إحدى الخدمات الميكروية دون ألم نسبيًا ، والبروتوكول الذي يكون فيه العشرات من البرامج مكلفًا ومؤلمًا بالفعل.
على طول الطريق ، يساعد التسجيل الدقيق في حل مشكلة الوثائق. توفر أسماء الطرق والمعلمات المختارة بشكل معقول ، وبعض التعليقات ، والمواصفات الموثقة ذاتيًا الكثير من الوقت!
على سبيل المثال ، هذه هي الطريقة التي يبدو بها تحديد طريقة إحدى خدماتنا المصغرة ، مما يتيح لك الحصول على قائمة بالأحداث التي حدثت في النظام الأساسي:
/** */ typedef i64 EventID /* Event sink service definitions */ service EventSink { /** * , * , , `range`. * `0` `range.limit` . * * `range.after` , * , , * `EventNotFound`. */ Events GetEvents (1: EventRange range) throws (1: EventNotFound ex1, 2: base.InvalidRequest ex2) /** * * . */ base.EventID GetLastEventID () throws (1: NoLastEvent ex1) } /* Events */ typedef list<Event> Events /** * , -, . */ struct Event { /** * . * , * (total order). */ 1: required base.EventID id /** * . */ 2: required base.Timestamp created_at /** * -, . */ 3: required EventSource source /** * , ( ) * -, . */ 4: required EventPayload payload /** * . * . */ 5: optional base.SequenceID sequence } // Exceptions exception EventNotFound {} exception NoLastEvent {} /** * , - */ exception InvalidRequest { /** */ 1: required list<string> errors }
التوفير عميل وحدة التحكم
في بعض الأحيان نواجه مهمة استدعاء أساليب معينة من الخدمات الدقيقة اللازمة مباشرة ، على سبيل المثال ، بأيدينا من المحطة. قد يكون ذلك مفيدًا لتصحيح الأخطاء أو الحصول على مجموعة بيانات أولية أو عندما تكون المهمة نادرة جدًا بحيث يكون تطوير واجهة مستخدم منفصلة غير عملي.
لذلك ، قمنا بتطوير أداة لأنفسنا تجمع بين وظائف curl
، ولكنها تسمح لك بتقديم طلبات ثلاثية في شكل هياكل JSON. اتصلنا به تبعا لذلك. الأداة مساعدة عالمية ، وهي كافية لنقل موقع أي مواصفات رفع إليها باستخدام معلمة سطر الأوامر ، وسوف تقوم بالباقي بنفسها. أداة مريحة للغاية ، يمكنك بدء الدفع مباشرة من المحطة ، على سبيل المثال.
هذه هي الطريقة التي ينظر بها النداء مباشرةً إلى microservice ، وهو المسؤول عن إدارة التطبيقات (على سبيل المثال ، لإنشاء متجر). لقد طلبت بيانات في حسابي التجريبي:

ربما لاحظ القراء الذين لاحظوا ميزة واحدة في لقطة الشاشة. نحن لا نحب ذلك أيضًا. من الضروري ربط ترخيص إجراء المكالمات الثلاثية بين الخدمات الصغيرة ، من الضروري لصق TLS بطريقة جيدة. ولكن في حين أن الموارد ، كما هو الحال دائما ، ليست كافية. لقد قصرنا أنفسنا على الضميمة الكاملة للمحيط الذي تعيش فيه خدمات المعالجة الدقيقة.
بروتوكولات للتواصل مع الأنظمة الخارجية
لنشر مواصفات الرفع للخارج ولإجبار التجار على التواصل باستخدام البروتوكول الثنائي ، اعتبرنا أنه قاسي جدًا عليهم. كان من الضروري اختيار بروتوكول يمكن قراءته من قبل الإنسان من شأنه أن يسمح لنا بالاندماج معنا بسهولة ، والتصحيح والقدرة على التوثيق بشكل ملائم. لقد اخترنا معيار API المفتوح ، المعروف أيضًا باسم Swagger .
بالعودة إلى مشكلة توثيق البروتوكولات ، يتيح لك Swagger حل هذه المشكلة بسرعة وبتكلفة منخفضة. لدى الشبكة العديد من تطبيقات التصميم الجميل لمواصفات Swagger في شكل وثائق مطور البرامج. نظرنا إلى كل شيء يمكن أن نجده واخترنا في نهاية الأمر ReDoc ، مكتبة JS التي تقبل swagger.json كمدخلات ، وتقوم بإنشاء مثل هذه الوثائق المكونة من ثلاثة أعمدة في الإخراج: https://developer.rbk.money/api/ .
النهج المتبعة لتطوير كلا البروتوكولات ، التوفير الداخلي و Swagger الخارجي ، متطابقة تمامًا بالنسبة لنا. هذا يضيف وقتًا إلى التطوير ، لكنه يؤتي ثماره على المدى الطويل.
نحتاج أيضًا إلى حل مشكلة مهمة أخرى - لا نقبل طلبات شطب الأموال فحسب ، بل نرسلها أيضًا - إلى البنوك وأنظمة الدفع.
سيكون إجبارهم على تنفيذ المصعد الخاص بنا مهمة أكثر واقعية من إرساله إلى واجهات برمجة التطبيقات العامة.
لذلك ، توصلنا إلى وتنفيذ مفهوم محولات البروتوكول. هذه مجرد خدمة ميكروية أخرى تنفذ مواصفات الرفع الداخلية الخاصة بنا على جانب واحد ، وهو نفسه بالنسبة للنظام الأساسي بأكمله ، والثاني هو بروتوكول خارجي خاص بمصرف معين أو محطة فرعية.
المشاكل التي تنشأ عند كتابة مثل هذه المحولات عندما تضطر إلى التفاعل مع أطراف ثالثة هي موضوع غني في قصص مختلفة. في ممارستنا ، التقينا بأشياء مختلفة ، إجابات النموذج: "بالطبع ، يمكنك تنفيذ هذه الوظيفة كما هو موضح في البروتوكول الذي قدمناه لك ، لكنني لا أقدم أي ضمانات. وهنا يأتي المريض الذي سنقوم بذلك كل هذا يجيب ، وتطلب منه التأكيد ". أيضًا ، مثل هذه الحالات ليست غير شائعة: "هنا هو اسم المستخدم وكلمة المرور من خادمنا ، اذهب إلى هناك وقم بتكوين كل شيء بنفسك."
أجد أنه من المثير للاهتمام بشكل خاص عندما ندمجنا مع شريك للدفع ، والذي قام بدوره ، بدمجه مسبقًا مع نظامنا الأساسي وسداد المدفوعات بنجاح من خلالنا (يحدث هذا غالبًا ، في تفاصيل أعمال قطاع الدفع). استجابةً لطلبنا الخاص ببيئة الاختبار ، أجاب الشريك بأنه لم يكن لديه بيئة اختبار على هذا النحو ، لكن يمكنه الحصول على حركة مرور للتكامل مع RBC ، أي مع نظامنا الأساسي ، حيث يمكننا المشاركة. هذه هي الطريقة التي ، من خلال شريك ، دمجنا أنفسنا مرة واحدة.
وبالتالي ، لقد قمنا ببساطة بحل مشكلة تنفيذ التوصيل المتوازي الشامل لأنظمة الدفع المختلفة والأطراف الأخرى. في الغالبية العظمى من الحالات ، لا تحتاج إلى لمس رمز النظام الأساسي لهذا ، فقط اكتب المحولات وإضافة المزيد من أدوات الدفع إلى التعداد.
ونتيجة لذلك ، حصلنا على مثل هذا المخطط من العمل - نظرنا خارج خدمات RBKmoney API الصغيرة (نسميها API المشتركة أو capi * ، التي شاهدتها في القنصل أعلاه) ، والتي تحقق من صحة بيانات الإدخال وفقًا لمواصفات Swagger العامة ، وتفويض العملاء ، والبث هذه الطرق لمكالمات الرفع الداخلية الخاصة بنا وإرسال الطلبات أسفل السلسلة إلى الخدمة المجهرية التالية. بالإضافة إلى ذلك ، تنفذ هذه الخدمات متطلبًا آخر للنظام الأساسي ، تم وضع المواصفات الفنية له على النحو التالي: "يجب أن تتاح للنظام دائمًا فرصة الحصول على قطة."
عندما نحتاج إلى إجراء مكالمة مع نظام خارجي ، فإن الخدمات الميكروية الداخلية تسحب طرق الرفع لمحول البروتوكول المقابل ، وتترجمها إلى لغة بنك معين أو نظام دفع وترسلها.
صعوبات التوافق في البروتوكول
تتطور المنصة باستمرار ، وتتم إضافة وظائف جديدة ، ويتم تغيير الوظائف القديمة. في مثل هذه الظروف ، يجب عليك إما الاستثمار في دعم التوافق مع الإصدارات السابقة أو تحديث الخدمات الصغيرة التابعة باستمرار. وإذا كان الموقف عندما يتحول الحقل المطلوب إلى اختياري بسيط ، فلا يمكنك فعل أي شيء على الإطلاق ، ثم في الحالة المعاكسة عليك إنفاق موارد إضافية.
مع مجموعة من البروتوكولات الداخلية ، تصبح الأمور أسهل. نادراً ما تتغير صناعة الدفع بحيث تظهر بعض طرق التفاعل الجديدة بشكل أساسي. خذ على سبيل المثال مهمة شائعة لنا - توصيل مزود جديد بأداة دفع جديدة. على سبيل المثال ، معالجة المحفظة المحلية ، والتي تسمح لك بمعالجة المدفوعات في كازاخستان في تنجي. هذه محفظة جديدة لمنصة التداول الخاصة بنا ، لكنها لا تختلف من حيث المبدأ عن نفس محفظة Qiwi - فهي تحتوي دائمًا على بعض المعرفات الفريدة والأساليب التي تتيح لك تسجيل الدين / إلغاء الخصم منه.
وفقًا لذلك ، تبدو مواصفات الرفع لجميع مزودي خدمات المحفظة كما يلي:
typedef string DigitalWalletID struct DigitalWallet { 1: required DigitalWalletProvider provider 2: required DigitalWalletID id } enum DigitalWalletProvider { qiwi rbkmoney }
وإضافة وسيلة جديدة للدفع في شكل محفظة جديدة تكمل ببساطة التعداد:
enum DigitalWalletProvider { qiwi rbkmoney newwallet }
الآن يبقى أن نتصدى لجميع الخدمات المجهرية باستخدام هذه المواصفات ، وتزامن مع معالج المستودع مع المواصفات وطرحها عبر CI / CD.
البروتوكولات الخارجية أكثر تعقيدًا. يكاد يكون من المستحيل تطبيق كل تحديث لمواصفات Swagger ، خاصةً دون التوافق مع الإصدارات السابقة ، ضمن إطار زمني معقول - من غير المحتمل أن يحتفظ شركاؤنا بموارد مطور مجانية على وجه التحديد لتحديث نظامنا الأساسي.
وأحيانًا يكون هذا مستحيلًا ، نواجه أحيانًا مواقف مثل: "لقد كتب لنا المبرمج وغادرنا ، وأخذنا معه الكود المصدري ، وكيف نعمل ، لا نعرف ، إنه يعمل ولا نلمسها".
لذلك ، نحن نستثمر في دعم التوافق مع الإصدارات السابقة على البروتوكولات الخارجية. في بنيتنا ، هذا الأمر أسهل قليلاً - بما أننا نستخدم محولات بروتوكولات منفصلة لكل إصدار محدد من واجهة برمجة تطبيقات Common ، فإننا نترك فقط خدمات mici القديمة القديمة للعمل ، ونغير فقط الجزء الذي يبدو وكأنه ثلاثي داخل المنصة إذا لزم الأمر. لذا تظهر microservices capi capi-v1
، و capi-v2
، و capi-v3
وما إلى ذلك ، وتبقى معنا إلى الأبد.
ماذا سيحدث عند ظهور capi-v33
سيتعين علينا إهمال بعض الإصدارات القديمة ، على الأرجح.
في هذه المرحلة ، عادة ما أبدأ في فهم الشركات جيدًا مثل Microsoft وجميع آلامها في دعم التوافق مع الإصدارات السابقة للحلول التي كانت تعمل منذ عقود.
تخصيص النظام
وإنهاء الموضوع ، سنخبرك بكيفية إدارة إعدادات النظام الأساسي الخاصة بالعمل.
إن إجراء الدفع ليس بالأمر السهل كما قد يبدو. بالنسبة لكل دفعة ، يريد عميل العمل إرفاق عدد كبير من الشروط - من العمولة إلى ، من حيث المبدأ ، إمكانية التنفيذ الناجح اعتمادًا على الوقت من اليوم. لقد حددنا لأنفسنا مهمة رقمنة مجموعة كاملة من الشروط التي يمكن لعميل العمل التوصل إليها الآن وفي المستقبل وتطبيق هذه المجموعة على كل دفعة تم إطلاقها حديثًا.
نتيجةً لذلك ، قررنا تطوير DSL الخاص بنا ، والذي قمنا فيه بتشكيل أدوات للإدارة المريحة التي تتيح لنا وصف نموذج العمل بالطريقة الصحيحة: اختيار محولات البروتوكول ، ووصف لخطة النشر ، والتي سيتم بموجبها توزيع الأموال في حسابات داخل النظام ، ووضع حدود ، وعمولات ، فئات و أشياء أخرى خاصة بنظام الدفع.
على سبيل المثال ، عندما نرغب في الحصول على عمولة بنسبة 1٪ للحصول على بطاقات المايسترو و MS وتشتيتها على حسابات داخل النظام ، فإننا نقوم بتكوين المجال مثل هذا:
{ "cash_flow": { "decisions": [ { "if_": { "any_of": [ { "condition": { "payment_tool": { "bank_card": { "definition": { "payment_system_is": "maestro" } } } } }, { "condition": { "payment_tool": { "bank_card": { "definition": { "payment_system_is": "mastercard" } } } } } ] }, "then_": { "value": [ { "source": { "system": "settlement" }, "destination": { "provider": "settlement" }, "volume": { "share": { "parts": { "p": 1, "q": 100 }, "of": "operation_amount" } }, "details": "1% processing fee" } ] } } ] } }
, , . , JSON. , , , . , , . , CVS/SVN-.
" ". , , , 1%, , , . , , . , .
cvs-like , . , — stateless, , . . .
- . , , . , , .
. , 10 , , .
, , , -, woorl-. - JSON- . - JS, , UX:

, , , .
, , .
, , SaltStack.
, !