كيف قمنا بترجمة المشروع القديم إلى GraphQL

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

مقدمة موجزة


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

صورة

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

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

النقاط الفنية


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

تحلل المهمة


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

  • GameInfo - يحتوي على معلومات حول ألعاب اليانصيب وتوجه.
  • DrawInfo - يحتوي على بيانات على التداول

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

في المجموع ، يمكن تمييز ثلاث مراحل من تنفيذ GraphQL:

  • خلق العملاء
  • إنشاء مُهيئ لنموذج Legacy ؛
  • استبدال طلبات API بـ GraphQL.

GraphQLClient


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

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

enum GraphQLRequest { case image(ImageId?) } 

يجب أن يحتوي عميل GraphQL المخصص لدينا على ApolloClient المخصص للميزات الحالية ، وكذلك طريقة تحميل الجلب المذكورة أعلاه.

 func fetch<Response>(requestType: GraphQLRequest, completion: @escaping (QLRequestResult<Response>) -> Void) 

ما هو QLRequestResult؟ انها عادية

  typealias QLRequestResult<Response> = Result<Response, APIError> 

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

  private func fetchGameInfo<Response>(gameId: String? = "", completion: @escaping (QLRequestResult<Response>) -> Void) { //   Apollo  let query = GetLotteriesQuery(input: gameId) //       apolloClient.fetch(query: query, cachePolicy: .returnCacheDataAndFetch, queue: .global()) { result in switch result { case .success(let response): guard let gamesQL = response.data?.games, let info = gamesQL.info else { completion(.failure(.decodingError)) return } //    let infos: [Games.Info] = info.compactMap({ gameInfo -> Games.Info? in guard let gameIdRaw = gameInfo?.gameId, let gameId = GameId(rawValue: gameIdRaw), let bonusMultiplier = gameInfo?.bonusMultiplier, let maxTicketCost = gameInfo?.maxTicketCost, let currentDraws = gameInfo?.currentDraws else { return nil } let currentDrawsInfo = Games.Info.CurrentDrawsInfo(currntDraw: currentDraws) let gameInfo = Games.Info(gameId: gameId, bonusMultiplier: bonusMultiplier, maxTicketCost: maxTicketCost, currentDraws: currentDrawsInfo) return gameInfo }) //    let games = Games(info: infos) guard let response = games as? Response else { completion(.failure(.decodingError)) return } completion(.success(response)) case .failure(let error): … } } } 

نتيجة لذلك ، حصلنا على نموذج قديم جاهز تم الحصول عليه من GraphQL.

نوع العددية


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

عند كتابة عميل ، واجهت مشكلة في خططي كان هناك نوع طويل ، والذي كان في الأساس

 typealias Long = Int64 

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

  • أضفت في البرنامج النصي codegen -passthroughCustomScalars
  • إنشاء ملف منفصل لل typealias
  • تمت الإضافة إلى الملف
     public typealias Long = Int64 

في المستقبل ، لكل نوع قياسي ، تحتاج إلى إضافة typealias جديد. مع الإصدار 14.0 من Apollo ، أضافوا "دعمًا لقواعد مخصصة Int." إذا كنت تريد تجنب استخدام هذا النهج والعلم في codegen ، ثم تحقق من حل هذه المشكلة في بوابة Apollo .

إيجابيات وسلبيات


لماذا هذا النهج جيد؟ انتقال سريع إلى حد ما إلى استخدام GraphQL ، فيما يتعلق بالنهج مع إزالة جميع النماذج القديمة.

في المستقبل ، عندما نحتاج إلى الحصول على بيانات لأي شاشة / نموذج ، يمكننا الانتقال إلى GraphQLClient والحصول على البيانات اللازمة.

من السلبيات:

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

النتائج


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

إذا كان لديك معلم GraphQL بينك ، أقترح عليك مشاركة تجربتك وإخبار مدى ملاءمة استخدام GraphQL + Apollo في المشروعات الكبيرة. أم أن اللعبة لا تستحق كل هذا العناء؟

شكرا لاهتمامكم!

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


All Articles