أبولو: 9 شهور - رحلة عادية

الصورة


مرحباً بالجميع ، اسمي سيميون ليفنسون ، أعمل كرئيس فريق في مشروع Stream من مجموعة Rambler Group وأريد التحدث عن تجربتنا مع Apollo.


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


تُظهر لقطة الشاشة إحدى خطوات إنشاء صفحة مقصودة.



ما هي البداية؟


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


لقد اكتسب قسمنا بالفعل خبرة كافية في استخدام React. لم أرغب في قضاء أسبوعين في إعداد حزمة الويب ، لذلك قررت استخدام CRA (Create React App). بالنسبة للأنماط ، تم أخذ المكونات ذات النمط ، وأين بدون كتابة - أخذوا التدفق . لقد أخذوا Redux لإدارة الدولة ، ولكن نتيجة لذلك تبين أننا لسنا بحاجة إليها على الإطلاق ، ولكن أكثر من ذلك لاحقًا.


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


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


المشكلة الثانية هي كيفية تنظيم الدعم لإصدار API.


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



شكرا لك Sashko Stubailo على التوضيح .


الحل


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


الطلب:


{ me { id isAcceptedFreeOffer balance } } 

الجواب:


 { "me": { "id": 1, "isAcceptedFreeOffer": false, "balance": 100000 } } 

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


مثال على طفرة نستخدم فيها عرضًا مجانيًا:


 mutation { acceptOffer (_type: FREE) { id isAcceptedFreeOffer } } 

ردًا على ذلك ، نحصل على نفس الهيكل المطلوب


 { "acceptOffer": { "id": 1, "isAcceptedFreeOffer": true } } 

يمكن إجراء التفاعل مع الواجهة الخلفية GraphQL باستخدام الجلب المنتظم.


 fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: '{me { id balance } }' }) }); 

ما هي مزايا GraphQL؟


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



كما ذكر أعلاه ، لدى RESTful مشكلة في الإصدار. نفذ GraphQL حلًا أنيقًا جدًا لهذا - تم إيقافه.



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


نقرر بشكل مستقل ما نحتاج إليه.


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



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


 { landings(_page: 0, limit: 20) { nodes { id title } totalCount } } 

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


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


 { album(id: 42) { songs { title artists } } } 

 { song(id: 1337) { title album { title } } } 

وبالتالي ، نحصل على استعلام تكراري ، والذي يضعنا أيضًا بشكل أساسي.


 query evil { album(id: 42) { songs { album { songs { album { 

هذه المشكلة معروفة أيضًا ، الحل لـ Node JS هو حد عمق GraphQL ، للغات الأخرى هناك أيضًا حلول.


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


لذا لدينا لاعبين رئيسيين - ريلاي وأبولو.


تتابع


Relay هو تطوير Facebook ، يستخدمونه بأنفسهم. مثل Oculus و Circle CI و Arsti و Friday.


ما هي إيجابيات Relay؟


الإضافة الفورية هي أن المطور هو Facebook. React و Flow و GraphQL عبارة عن تطورات على Facebook ، وكلها أحجيات بانورامية مصممة مع بعضها البعض. أين نحن بدون نجوم على Github ، فإن Relay لديها ما يقرب من 11000 ، وأبولو لديه 7600 للمقارنة. الشيء الرائع الذي يحتوي عليه Relay هو Relay-compiler ، وهي أداة تعمل على تحسين وتحليل استعلامات GraphQL الخاصة بك على مستوى بناء مشروعك . يمكننا أن نفترض أن هذا هو uglify فقط ل GraphQL:


 #  Relay-compiler foo { # type FooType id ... on FooType { # matches the parent type, so this is extraneous id } } #  foo { id } 

ما هي سلبيات Relay؟


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



مرة أخرى ، التتابع هو أحد المواصفات. والحقيقة هي أن GraphQL هي بالفعل مواصفات ، و Relay هي مواصفات فوق المواصفات.



على سبيل المثال ، يتم تطبيق ترقيم الصفحات بشكل مختلف ، تظهر المؤشرات هنا.


 { friends(first: 10, after: "opaqueCursor") { edges { cursor node { id name } } pageInfo { hasNextPage } } } 

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


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



أبولو


مرشحنا الثاني هو أبولو . تم تطويره من قبل فريقه Meteor . يستخدم Apollo مثل هذه الأوامر المعروفة مثل: AirBnB و Tickmaster و Opentable وما إلى ذلك.


ما هي مميزات أبولو؟


أول إضافة مهمة هي أن Apollo تم تطويره كمكتبة ملهمة لإطار العمل. على سبيل المثال ، إذا أردنا الآن إعادة كتابة كل شيء على Angular ، فلن تكون هذه مشكلة ، يعمل Apollo مع هذا. ويمكنك حتى كتابة كل شيء بالفانيليا.


يحتوي Apollo على وثائق رائعة ، وهناك حلول جاهزة للمشكلات الشائعة.



إضافة أخرى إلى Apollo - واجهة برمجة تطبيقات قوية. من حيث المبدأ ، سيجد أولئك الذين عملوا مع Redux مناهج شائعة هنا: هناك ApolloProvider (مثل Provux for Redux) ، وبدلاً من تخزين Apollo ، يُطلق على هذا العميل:


 import { ApolloProvider } from 'react-apollo'; import { ApolloClient } from './ApolloClient'; const App = () => ( <ApolloProvider client={ApolloClient}> ... </ApolloProvider> ); 

على مستوى المكون نفسه ، لدينا HOCOLQL HOC المقدمة كتوصيل. ونكتب استعلام GraphQL الموجود في الداخل ، مثل MapStateToProps in Redux.


 import { graphql } from 'react-apollo'; import gql from 'graphql-tag'; import { Landing } from './Landing'; graphql(gql` { landing(id: 1) { id title } } `)(Landing); 

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


 function Landing({ data, loading, error, refetch, ...other }) { ... } 

هذا:
• البيانات ؛
• حالة التنزيل ؛
• خطأ إذا حدث ؛
وظائف المساعد مثل الجلب لإعادة تحميل البيانات أو الجلب أكثر لترقيم. هناك أيضًا إضافة ضخمة لكل من Apollo و Relay ، وهي واجهة المستخدم المتفائلة. يسمح لك بتنفيذ التراجع / الإعادة على مستوى الطلب:


 this.props.setNotificationStatusMutation({ variables: { … }, optimisticResponse: { … } }); 

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


يتم تنفيذ العرض الجانبي للخادم بشكل جيد ، ونضع علامة واحدة على العميل وكل شيء جاهز.


 new ApolloClient({ ssrMode: true, ... }); 

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


 <script> window.__APOLLO_STATE__ = client.extract(); </script> const client = new ApolloClient({ cache: new InMemoryCache().restore(window.__APOLLO_STATE__), link }); 

ولكن ليس لدينا عرض من جانب الخادم ، وتدفع الواجهة الخلفية استعلام GraphQL محدد إلى المتغير العام. هنا تحتاج إلى عكاز صغير ، تحتاج إلى كتابة وظيفة التحويل التي ستتحول استجابة GraphQL من الواجهة الخلفية بالفعل إلى التنسيق المطلوب لـ Apollo.


 <script> window.__APOLLO_STATE__ = transform({…}); </script> const client = new ApolloClient({ cache: new InMemoryCache().restore(window.__APOLLO_STATE__), link }); 

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



أود أن أشير بشكل منفصل إلى رابطين : حالة apollo-link-state ، وهي ضرورية لتخزين الحالة المحلية في حالة عدم وجود Redux ، و apollo-link-rest ، إذا أردنا كتابة استعلامات GraphQL إلى Rest API. ومع ذلك ، مع هذا الأخير تحتاج إلى توخي الحذر الشديد ، لأنه قد تنشأ مشاكل معينة.


لدى أبولو سلبيات أيضًا


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


أيضا ، تبين أن الجلب غير واضح للغاية ...


 function Landing({ loading, refetch, ...other }) { ... } 

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


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


أبولو مقابل. تتابع


وهكذا ، حصلنا على مثل هذه الطاولة ، وكلنا وزننا وعدنا ، وكان لدينا 76٪ خلف أبولو.



لذلك اخترنا المكتبة وذهبنا للعمل.


ولكن أود أن أقول المزيد عن سلسلة الأدوات.


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


عنوان "الأيدي المجنونة" أو ما فعلناه في المنزل


أول شيء واجهناه هو أننا في الأساس نحتاج إلى طلب البيانات بطريقة أو بأخرى.


 graphql(BalanceQuery)(BalanceItem); 

لدينا شروط شائعة: التحميل ومعالجة الخطأ. لقد كتبنا صقرنا الخاص (asyncCard) ، والذي يتم توصيله عبر تكوين graqhql و asyncCard.


 compose( graphql(BalanceQuery), AsyncCard )(BalanceItem); 

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


 const LandingItem = ({ content }: Props) => ( <LandingItemStyle></LandingItemStyle> ); LandingItem.fragment = gql` fragment LandingItem on Landing { ... } `; 

الآن ، على مستوى استخدام المكون ، نستخدم جزأه في الطلب النهائي.


 query LandingsDashboard { landings(...) { nodes { ...LandingItem } totalCount } ${LandingItem.Fragment} } 

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


 const LandingItem = ({ content }: Props) => ( <LandingItemStyle><LandingItemStatus … /> </LandingItemStyle> ); LandingItem.fragment = gql` fragment LandingItem on Landing { ... status } `; 

ما المشكلة الأخرى التي لدينا؟


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



أثناء الاختبار ، اتضح أن كل هذا يتباطأ. لدينا فحوصات أمنية طويلة جدًا ، وكل طلب مكلف للغاية. هذا تبين أيضًا أنه لا توجد مشكلة ، هناك Apollo-link-batch-http


 new BatchHttpLink({ batchMax: 10, batchInterval: 10 }); 

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


أود أن أقول بشكل منفصل أنه في الخريف الماضي كان هناك تحديث من أبولو الأول إلى الثاني


في البداية كان أبولو وريدوكس


 'react-apollo' 'redux' 

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


 'react-apollo' 'apollo-client' 'apollo-link-batch-http' 'apollo-cache-inmemory' 'graphql-tag' 

تجدر الإشارة إلى أن Redux ليست كذلك ، وكما اتضح ، فهي ليست من حيث المبدأ غير ضرورية.


الاستنتاجات:


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

ملاحظة: يمكنك أيضًا مشاهدة مقطع فيديو من عرضي التقديمي على Rambler Front & Meet up # 4


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


All Articles