GraphQL، مثل أي تقنية، لها مشاكلها، بعضها ناتج بشكل مباشر عن البنية وبعضها مطابق لما نراه في أي تطبيق آخر. ومع ذلك، فإن الحلول مختلفة تمامًا.
لعرض المشكلة، دعنا نفترض بنية التطبيق التالية:
وهنا الاستعلام المقابل في GraphQL لتحميل البيانات. نقوم بجلب جميع الروابط، إلى جانب الملصق وروابطه المضافة إلى النظام,
{
جميع الروابط {
المعرف
url
الوصف
تم الإنشاء في
تم النشر بواسطة {
المعرف
الاسم
الروابط {
المعرف
}
}
}
}
كما هو موضح أدناه، يمكننا أن نرى هنا مشكلة n + 1 الكلاسيكية في العلاقات.
تحميل الروابط (0.4 مللي ثانية) حدد "الروابط".* من "الروابط" ترتيب حسب تم الإنشاء_في_تصنيف
↳ التطبيق/المتحكمون/جرافكل_كونترولر.rb:5:في 'تنفيذ'
تحميل المستخدم (0.3 مللي ثانية) حدد تحديد "المستخدمين".* من "المستخدمين" حيث "المستخدمون"."id" = ؟ LIMIT ? [[["المعرف"، 40]، ["LIMIT"، 1]]
↳ التطبيق/المتحكمون/جرافكل_كونترولر.rb:5:في 'تنفيذ'
تحميل الروابط (0.3 مللي ثانية) حدد "الروابط".* من "الروابط" حيث "الروابط"."user_id" = ؟ [["User_id"، 40]]
↳ التطبيق/المتحكمون/جرافكل_كونترولر.rb:5:في 'تنفيذ'
تحميل المستخدم (0.1 مللي ثانية) حدد تحديد "المستخدمين".* من "المستخدمين" حيث "المستخدمون"."id" = ? LIMIT ? [[["المعرف"، 38]، ["LIMIT"، 1]]
↳ التطبيق/المتحكمون/جرافكل_كونترولر.rb:5:في 'تنفيذ'
تحميل الروابط (0.1 مللي ثانية) حدد "الروابط".* من "الروابط" حيث "الروابط"."user_id" = ؟ [["User_id"، 38]]
↳ التطبيق/المتحكمون/جرافكل_كونترولر.rb:5:في 'تنفيذ'
تحميل المستخدم (0.2 مللي ثانية) حدد تحديد "المستخدمين".* من "المستخدمين" حيث "المستخدمون"."id" = ? LIMIT ? [[["المعرف"، 36]، ["LIMIT"، 1]]
↳ التطبيق/المتحكمون/جرافكل_كونترولر.rb:5:في 'تنفيذ'
تحميل الروابط (0.1 مللي ثانية) حدد "الروابط".* من "الروابط" حيث "الروابط"."user_id" = ؟ [["User_id"، 36]]
↳ التطبيق/المتحكمون/جرافكل_كونترولر.rb:5:في 'تنفيذ'
تحميل المستخدم (0.1 مللي ثانية) حدد تحديد "المستخدمين".* من "المستخدمين" حيث "المستخدمون"."id" = ? LIMIT ? [[["المعرف"، 34]، ["LIMIT"، 1]]
↳ التطبيق/المتحكمون/جرافكل_كونترولر.rb:5:في 'تنفيذ'
تحميل الروابط (0.2 مللي ثانية) حدد "الروابط".* من "الروابط" حيث "الروابط"."user_id" = ؟ [["User_id"، 34]]
↳ التطبيق/المتحكمون/جرافكل_كونترولر.rb:5:في 'تنفيذ'
تحميل المستخدم (0.1 مللي ثانية) حدد تحديد "المستخدمين".* من "المستخدمين" حيث "المستخدمون"."id" = ? LIMIT ? [[["المعرف"، 32]، ["LIMIT"، 1]]
في هذه الحالة، يعمل تمامًا مثل هذه القطعة من الكود: link.all.all.map(&:user).map(&:links).
يبدو أننا نعرف حل المشكلة: يتضمن الرابط(المستخدم: ::الروابط).map(&:user).map(&:user).map(&:links)ولكن هل ستنجح حقًا؟ دعنا نتحقق من ذلك!
للتحقق من الإصلاح، قمتُ بتغيير GraphQL استعلام لاستخدام حقول قليلة فقط ولا علاقة لها.
{
جميع الروابط {
المعرف
url
الوصف
تم إنشاؤه في
}
}
لسوء الحظ، تُظهر النتيجة أنه على الرغم من عدم وجود روابط فيما يتعلق بالمستخدم وروابطه، إلا أننا ما زلنا نرفق هذه البيانات باستعلام قاعدة البيانات. ولسوء الحظ، فإنها زائدة عن الحاجة، ومع وجود بنية أكثر تعقيدًا، يتبين أنها ببساطة غير فعالة.
في GraphQL،يتم حل مثل هذه المشاكل بشكل مختلف، ببساطة عن طريق تحميل البيانات على دفعات، بافتراض أن البيانات مطلوبة عند وضعها في الاستعلام. إنه مثل هذا التحميل البطيء. واحدة من أكثر المكتبات شيوعًا هي https://github.com/Shopify/graphql-batch/.
لسوء الحظ، فإن تركيبه ليس خاليًا من المتاعب كما قد يبدو. وتتوفر أدوات تحميل البيانات هنا: https://github.com/Shopify/graphql-batch/tree/master/examples، أعني محمل السجل وفئةمحمل الرابطة الفصل. دعونا نثبت بشكل كلاسيكي جوهرة 'Graphql-batch' ثم إضافتها إلى مخططنا، وكذلك أدوات التحميل:
# graphql-ruby/app/graphqql/graphql_tutorial_schema.rb
فئة GraphqlTutorialSchema < GraphQL::Schema
أنواع الاستعلام::QueryType
أنواع الطفرات::نوع الطفرات
استخدام GraphQL::دفعة
...
نهاية
وأنواعنا:
# graphql-ruby/app/graphql/types/link_type.rb
الوحدة النمطية أنواع
صنف نوع الرابط <العقدة الأساسية
الحقل :تم الإنشاء_في, DateTimeType, فارغ: خطأ
الحقل :url، سلسلة، فارغ: خطأ
الحقل :الوصف، سلسلة، فارغة: خطأ
الحقل :posted_by، UserType، فارغ: خطأ، الطريقة: :مستخدم
الحقل :أصوات، [الأنواع::نوع التصويت]، فارغ: خطأ
تعريف المستخدم
اللوادر::RecordLoader.for(المستخدم).load(object.user_id)
النهاية
النهاية
النهاية
# graphql-ruby/app/graphqql/types/user_type.rb
الوحدة النمطية أنواع
صنف UserType <العقدة الأساسية
الحقل :تم الإنشاء_في, DateTimeType, فارغ: خطأ
الحقل :اسم، سلسلة، فارغة: خطأ
الحقل :البريد الإلكتروني، خيط، فارغ: خطأ
الحقل :أصوات، [نوع التصويت]، فارغ: خطأ
الحقل :روابط، [نوع الرابط]، فارغ: خطأ
تعريف الروابط
اللوادر::AssociationLoader.for(المستخدم، :روابط).load(كائن)
نهاية
النهاية
النهاية
نتيجة لاستخدام أدوات التحميل، نقوم بتجميع البيانات والاستعلام عن البيانات في استعلامي sql بسيطين:
N + 1 الاستعلامات ليست كل شيء، في GraphQL يمكننا نقل السمات التالية بحرية. بشكل افتراضي، يتم تعيينه على 1. قد يكون هذا في بعض الأحيان أكثر من اللازم بالنسبة للخادم، خاصةً في حالة يمكننا فيها تداخل البيانات بحرية. كيف نتعامل معها؟ يمكننا الحد من تعقيد الاستعلام، ولكن للقيام بذلك، نحتاج أيضًا إلى تحديد تكلفتها في السمات. بشكل افتراضي يتم ضبطها على 1. نضبط هذه التكلفة باستخدام التعقيد: حيث يمكننا إدخال البيانات: الحقل: الروابط، [نوع الرابط]، باطل: خطأ، التعقيد: 101. إذا كان التقييد هو العمل الفعلي، فلا يزال عليك إدخال الحد الأقصى لمخططك:
فئة GraphqlTutorialSchema < GraphQL::Schema
أنواع الاستعلام::نوع الاستعلام
أنواع الطفرات::نوع الطفرات
استخدام GraphQL::دفعة
الحد الأقصى للتعقيد 100
...
نهاية
التتبع
GraphQL يعالج الاستعلامات بشكل مختلف، والتتبع ليس بهذه البساطة إذا ما قورن بما يمكننا القيام به محليًا. لسوء الحظ، لن يخبرنا المحلل المصغر للرف أو سجل SQL العادي بكل شيء ولن يشير إلى أي جزء من الاستعلام مسؤول عن شريحة زمنية معينة. في حالة GraphQL-Ruby، يمكننا استخدام الحلول التجارية المتاحة هنا: https://graphql-ruby.org/queries/tracingأو محاولة إعداد التتبع الخاص بنا. أدناه، يبدو المقتطف أدناه كمتتبع محلي.
# lib/my_custom_tracer.rb
صنف MyCustomTracer 'graphql.lex',
'تحليل' => 'graphql.parse',
'التحقق من الصحة' => 'graphql.validate',
'analy_query' => 'graphql.analy_query',
'analy_multiplex' => 'graphql.analy_multiplex',
'execute_multiplex' => 'graphql.execute_multiplex',
'execute_query' => 'graphql.execute_query',
'execute_query_query_lazy' => 'graphql.execute_query_lazy'
}
def platform_trace(platform_key, key, _data, &block)
البدء = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
النتيجة =: block.call
المدة = ::Process.clock_gettime(:::Process::CLOCK_MONONOTONIC) - البداية
مراقبة(platform_key، مفتاح، مفتاح، المدة)
النتيجة
النهاية
تعريف platform_field_key(النوع، الحقل)
"Graphql.#{type.Graphql_name}.#{field.Graphql_name}"
نهاية
def platform_authorized_key (النوع)
"graphql.authorized.#{type.Graphql_name}"
النهاية
ديف platform_resolve_type_key(النوع)
"graphql.resol_type.#{type.graphql_name}"
النهاية
def observe(platform_key, key, duration)
العودة إذا كان المفتاح = = 'مصرح به'
يضع "platform_key: #{platform_key}، المفتاح: #{key}، المدة: #{(المدة * 1000).round (5)} مللي ثانية".
النهاية
النهاية
التثبيت بسيط للغاية أيضًا، تحتاج إلى تضمين معلومات التتبع في المخطط متتبع (MyCustomTracer.new) التكوين. كما في المثال أدناه:
# graphql-ruby/app/graphqql/graphql_tutorial_schema.rb
فئة GraphqlTutorialSchema < GraphQL::Schema
أنواع الاستعلام::QueryType
أنواع الطفرات::نوع الطفرات
استخدام GraphQL::دفعة
المتتبع(MyCustomTracer.new)
...
نهاية
يبدو الناتج من هذا التتبع بهذا الشكل:
تم بدء POST POST "/graphql" ل ::1 في 2021-06-17 22:02:44 +0200
(0.1 مللي ثانية) حدد sqlite_version (*)
تتم المعالجة بواسطة GraphqlController#execute باسم */*
المعلمات: {"الاستعلام"=>"{n allLinks {n allLinks {n idn urln urln الوصفn createdAtn نشرت بواسطة {n idn n n n n n اسم الروابط {n idn }n }n }n }n}"، "Graphql"=>{"الاستعلام"=>"{n allLinks {n idn urln الوصفn engedAtn نشرت بواسطة {n idn n n n n n n n روابط {n idn }n }n }n }n }n}"}}}}}}
platform_key: Graphql.lex، المفتاح: lex، المدة: 0.156 مللي ثانية
النظام الأساسي_المفتاح: graphql.parse، المفتاح: parse، المدة: 0 156 مللي ثانية: 0.108 مللي ثانية
النظام الأساسي_المفتاح: Graphql.validate، المفتاح: تحقق من الصحة، المدة: 0.537 مللي ثانية: 0.537 مللي ثانية
النظام الأساسي_المفتاح: graphqql.analy_query، المفتاح: analy_query، المدة: 0.123 مللي ثانية: 0.123 مللي ثانية
النظام الأساسي_المفتاح: graphql.analy_multiplex، المفتاح: analy_multiplex، المدة: 0.159 مللي ثانية: 0.159 مللي ثانية
تحميل الروابط (0.4 مللي ثانية) حدد "روابط".* من "روابط"
↳ التطبيق/الجرافكل/الجرافكل_توتوريال_شيما.rb:21:في 'تتبع_المنصة'
النظام الأساسي_المفتاح: Graphql.execute_query، المفتاح: execute_query، المدة: 15.562 مللي ثانية
↳ التطبيق/الجرافكل/المحملون/record_loader.rb:12:في 'perform'
↳ التطبيق/الجرافكل/المحمّلون/محمّل_الارتباط.rb:46:في 'تحميل_الارتباط المسبق'
النظام الأساسي_المفتاح: Graphql.execute_query_lazy، المفتاح: execute_query_lazy، المدة: 14.12 مللي ثانية
النظام الأساسي_المفتاح: Graphql.Graphql.execute_multiplex، المفتاح: execute_multiplex، المدة: 14.12 مللي ثانية: 31.11 مللي ثانية
تم الإكمال 200 موافق في 48 مللي ثانية (المشاهدات: 1.2 مللي ثانية | ActiveRecord: 2.0 مللي ثانية | التخصيصات: 40128)
الملخص
GraphQL ليست تقنية جديدة بعد الآن، ولكن حلول مشاكلها ليست موحدة تمامًا إذا لم تكن جزءًا من المكتبة. إن تطبيق هذه التكنولوجيا في المشروع الكثير من الفرص للتفاعل مع الواجهة الأمامية، وأنا شخصيًا أعتبرها نوعية جديدة فيما يتعلق بما تقدمه REST API.