Porting APIs إلى TypeScript كحل للمشاكل

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



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

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

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


ترجمة الواجهة الأمامية من JavaScript إلى TypeScript ، وترجمة الواجهة الخلفية من Ruby إلى TypeScript

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

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

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

router.handleGet(api.blog, async () => {
  return {
    posts: blog.posts,
  }
})

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

Property 'posts' is missing in type '...' but required in type '...'.

يتم تعريف كل نقطة نهاية بواسطة كائن عرض api.someNameHere. يتم مشاركة هذا الكائن من قبل العميل والخادم. لاحظ أن الأنواع غير مذكورة بشكل مباشر في إعلان المعالج. كلهم استنتجوا من الحجة api.blog.

يعمل هذا النهج مع نقاط النهاية البسيطة ، مثل نقطة النهاية الموضحة أعلاه blog. لكنها مناسبة لنقاط النهاية الأكثر تعقيدًا. على سبيل المثال ، تحتوي واجهة برمجة تطبيقات نقطة النهاية للعمل مع الدروس على مفتاح متداخل للغاية من النوع المنطقي .lesson.steps[index].isInteractive. بفضل كل هذا ، من المستحيل الآن ارتكاب الأخطاء التالية:

  • إذا حاولنا الوصول isinteractiveإلى العميل ، أو حاولنا إرجاع مثل هذا المفتاح من الخادم ، فلن يتم ترجمة الرمز. يجب أن يبدو اسم المفتاح isInteractive، برأس مال I.
  • isInteractive — .
  • isInteractive number, , , .
  • API, , isInteractive — , , , , , , , .

لاحظ أن كل هذا يتضمن إنشاء التعليمات البرمجية. يتم ذلك باستخدام io-ts ومئتي سطر من التعليمات البرمجية من جهاز التوجيه الخاص بنا.

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

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

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

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

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

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

سأخبرك عن إنشاء التعليمات البرمجية تلقائيًا. على وجه التحديد ، نستخدم المخططات لإنشاء تعريفات النوع من بنية قاعدة البيانات الخاصة بنا. يتصل النظام بقاعدة بيانات Postgres ، ويحلل أنواع الأعمدة ويكتب تعريفات نوع TypeScript المقابلة للملف العادي .d.tsالمستخدم من قبل التطبيق.

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

كل هذا معًا يخلق سلسلة موثوقة وموثوقة لنقل المعلومات ، تمتد من قاعدة البيانات إلى خصائص مكونات React في الواجهة الأمامية:

  • , ( API) , .
  • API , API, ( ) .
  • React- , API, .

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

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

هكذا يبدو المشروع بعد ترجمة الواجهة الخلفية إلى TypeScript. كما ترى ، تمت كتابة الكثير من التعليمات البرمجية منذ النقل. كان لدينا ما يكفي من الوقت لتقييم نتائج القرار.


يتم استخدام TypeScript في الواجهة الأمامية والخلفية للمشروع.

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

القراء الأعزاء! هل قمت بترجمة المشاريع المكتوبة بلغات أخرى إلى TypeScript؟


All Articles