ما هي لغة الاستعلام GraphQL؟ ما المزايا التي توفرها هذه التكنولوجيا وما هي المشاكل التي سيواجهها المطورون عند استخدامها؟ كيفية استخدام GraphQL بشكل فعال؟ عن كل هذا تحت الخفض.

تستند المقالة على تقرير المستوى التمهيدي بواسطة
فلاديمير تسوكور (
volodymyrtsukur ) من مؤتمر
جوكر 2017 .
اسمي فلاديمير ، وأقود تطوير أحد الأقسام في WIX. ينشئ أكثر من مائة مليون مستخدم WIX مواقع ويب ذات اتجاهات مختلفة - من مواقع بطاقات العمل والمتاجر إلى تطبيقات الويب المعقدة حيث يمكنك كتابة التعليمات البرمجية والمنطق التعسفي. كمثال حي على مشروع على WIX ، أود أن أريكم متجر الموقع الناجح
unicornadoptions.com ، الذي يوفر الفرصة لشراء مجموعة أدوات لترويض يونيكورن - هدية رائعة لطفل.

يمكن لزائر هذا الموقع اختيار مجموعة يرغبون في ترويض يونيكورن فيها ، مثل اللون الوردي ، ثم انظر ما هو بالضبط في هذه المجموعة: لعبة ، شهادة ، شارة. علاوة على ذلك ، فإن المشتري لديه الفرصة لإضافة السلع إلى السلة ، وعرض محتوياتها وتقديم طلب. هذا مثال بسيط على موقع المتجر ، ولدينا مئات الآلاف من هذه المواقع. كلهم مبنيون على نفس النظام الأساسي ، مع خلفية واحدة ، مع مجموعة من العملاء الذين ندعمهم باستخدام واجهة برمجة التطبيقات لهذا. حول API الذي سيتم مناقشته أكثر.
بسيطة API ومشاكلها
دعنا نتخيل أي واجهة برمجة تطبيقات للأغراض العامة (أي واجهة برمجة تطبيقات واحدة لجميع المتاجر أعلى النظام الأساسي) يمكننا إنشاؤها لتوفير وظائف المتجر. دعونا نركز فقط على الحصول على البيانات.
لصفحة المنتج على مثل هذا الموقع ، يجب إرجاع اسم المنتج والسعر والصور والوصف والمعلومات الإضافية والمزيد. في حل كامل للمتاجر على WIX ، هناك أكثر من عشرين حقل بيانات من هذا القبيل. الحل القياسي لمثل هذه المهمة أعلى واجهة HTTP API هو وصف مورد
/products/:id
، الذي يُرجع بيانات المنتج في طلب
GET
. فيما يلي مثال لبيانات الاستجابة:
{ "id": "59eb83c0040fa80b29938e3f", "title": "Combo Pack with Dreamy Eyes 12\" (Pink) Soft Toy", "price": 26.99, "description": "Spread Unicorn love amongst your friends and family by purchasing a Unicorn adoption combo pack today. You'll receive your very own fabulous adoption pack and a 12\" Dreamy Eyes (Pink) cuddly toy. It makes the perfect gift for loved ones. Go on, you know you want to, adopt today!", "sku":"010", "images": [ "http://localhost:8080/img/918d8d4cc83d4e5f8680ca4edfd5b6b2.jpg", "http://localhost:8080/img/f343889c0bb94965845e65d3f39f8798.jpg", "http://localhost:8080/img/dd55129473e04f489806db0dc6468dd9.jpg", "http://localhost:8080/img/64eba4524a1f4d5d9f1687a815795643.jpg", "http://localhost:8080/img/5727549e9131440dbb3cd707dce45d0f.jpg", "http://localhost:8080/img/28ae9369ec3c442dbfe6901434ad15af.jpg" ] }

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

لنفترض ، من أجل البساطة ، قررنا استخدام نفس نموذج بيانات المنتج للموارد
/products
و
/products/:id
. في حالة مجموعة من هذه المنتجات سيكون هناك العديد من المحتمل. يمكن تمثيل مخطط الاستجابة على النحو التالي:
GET /products [ { title price images description info ... } ]
الآن دعونا نلقي نظرة على "الحمولة" للاستجابة من الخادم لمجموعة المنتجات. فيما يلي ما يستخدمه العميل بالفعل بين أكثر من عشرين حقلاً:
{
"id": "59eb83c0040fa80b29938e3f",
"title": "Combo Pack with Dreamy Eyes 12\" (Pink) Soft Toy",
"price": 26.99,
"info": "Spread Unicorn love amongst your friends and family by purchasing a Unicorn adoption combo pack today. You'll receive your very own fabulous adoption pack and a 12\" Dreamy Eyes (Pink) cuddly toy. It makes the perfect gift for loved ones. Go on, you know you want to, adopt todayl",
" description": "Your fabulous Unicorn adoption combo pack contains:\nA 12\" Dreamy Eyes (Pink) Unicorn Soft Toy\nA blank Unicorn adoption certificate — name your Unicorn!\nA confirmation letter\nA Unicorn badge\nA Unicorn key ring\nA Unicorn face mask (self assembly)\nA Unicorn bookmark\nA Unicorn colouring in sheet\nA A4 Unicorn posters\n2 x Unicorn postcards\n3 x Unicorn stickers",
"images": [
"http://localhost:8080/img/918d8d4cc83d4e5f8680ca4edfd5b6b2.jpg",
"http://localhost:8080/img/f343889c0bb94965845e65d3f39f8798.jpg",
"http://localhost:8080/img/dd55129473604f489806db0dC6468dd9.jpg",
"http://localhost:8080/img/64eba4524a1f4d5d9f1687a815795643.jpg",
"http://localhost:8080/img/5727549e9l3l440dbb3cd707dce45d0f.jpg",
"http://localhost:8080/img/28ae9369ec3c442dbfe6901434ad15af.jpg"
],
...
}
من الواضح ، إذا كنت أرغب في الحفاظ على طراز المنتج بسيطًا من خلال إعادة البيانات نفسها ، فسينتهي بي الأمر إلى مشكلة في الجلب الزائد ، في بعض الحالات أحصل على بيانات أكثر مما أحتاج. في هذه الحالة ، ظهر هذا في صفحة كتالوج المنتج ، ولكن بشكل عام ، ستتطلب أي شاشات واجهة مستخدم مرتبطة بطريقة ما بالمنتج منه جزءًا (وليس كل) البيانات فقط.
لنلق نظرة على صفحة سلة التسوق الآن. في السلة ، بالإضافة إلى المنتجات نفسها ، هناك أيضًا الكمية الخاصة بها (في هذه السلة) ، والسعر ، بالإضافة إلى التكلفة الإجمالية للطلب بأكمله:

إذا واصلنا نهج النمذجة البسيطة لواجهة برمجة تطبيقات HTTP ، فيمكن تمثيل السلة من خلال المورد
/ عربات /: id ، الذي يشير عرضه إلى موارد المنتجات المضافة إلى هذه السلة:
{ "id": 1, "items": [ { "product": "/products/59eb83c0040fa80b29938e3f", "quantity": 1, "total": 26.99 }, { "product": "/products/59eb83c0040fa80b29938e40", "quantity": 2, "total": 25.98 }, { "product": "/products/59eb88bd040fa8125aa9c400", "quantity": 1, "total": 26.99 } ], "subTotal": 79.96 }
الآن ، على سبيل المثال ، من أجل رسم سلة مع ثلاثة منتجات في الواجهة الأمامية ، تحتاج إلى تقديم أربعة طلبات: واحد لتحميل السلة نفسها ، وثلاثة طلبات لتحميل بيانات المنتج (الاسم والسعر ورقم SKU).
المشكلة الثانية التي واجهتنا هي التقصير. أدى التفريق بين المسؤوليات بين السلة وموارد المنتج إلى الحاجة إلى تقديم طلبات إضافية. من الواضح أن هناك عددًا من العيوب هنا: نظرًا لعدد أكبر من الطلبات ، نحصل على بطارية الهاتف المحمول بشكل أسرع ونحصل على الإجابة الكاملة بشكل أبطأ. كما أن قابلية التوسع في حلنا تثير أسئلة أيضًا.
بالطبع هذا الحل غير مناسب للإنتاج. تتمثل إحدى طرق التخلص من المشكلة في إضافة دعم العرض للسلة. يمكن لإحدى هذه التوقعات ، بالإضافة إلى بيانات السلة نفسها ، إرجاع البيانات المتعلقة بالمنتجات. علاوة على ذلك ، سيكون هذا الإسقاط محددًا للغاية ، لأنه في صفحة السلة تحتاج إلى رقم المخزون (SKU) للمنتج. لم يكن هناك مكان في SKU مطلوب في أي مكان آخر.
GET /carts/1?projection=with-products

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

(بشكل عام ، في WIX Designer ، يمكنك كمستخدم تكوين بيانات المنتج التي تريد عرضها على صفحة المنتج والبيانات التي تظهر في السلة)
وهنا تنتظرنا صعوبات: نحن نؤطر الحديقة ونبحث عن حلول معقدة. هناك عدد قليل من الحلول القياسية من وجهة نظر واجهة برمجة التطبيقات لمثل هذه المهمة ، وعادة ما تعتمد بشكل كبير على إطار العمل أو مكتبة وصف موارد HTTP.
والأهم من ذلك ، أنه أصبح من الصعب العمل الآن ، لأنه عندما تتغير المتطلبات من جانب العميل ، يجب على الواجهة الخلفية "اللحاق" باستمرار وإرضائها.
بصفتنا "كرز على الكعكة" ، دعنا نلقي نظرة على قضية أخرى مهمة. في حالة وجود واجهة برمجة تطبيقات بسيطة لـ HTTP ، فإن مطور الخادم ليس لديه أي فكرة عن نوع البيانات التي يستخدمها العميل. هل السعر مستخدم؟ الوصف؟ صورة واحدة أم كلها؟

وبناء على ذلك ، تبرز عدة أسئلة. كيفية التعامل مع البيانات القديمة / المتقادمة؟ كيف أعرف البيانات التي لم تعد تستخدم؟ ما مدى سلامة إزالة البيانات من الاستجابة دون كسر معظم العملاء؟ لا توجد إجابة على هذه الأسئلة باستخدام HTTP API المعتاد. على الرغم من حقيقة أننا متفائلون ويبدو أن واجهة برمجة التطبيقات بسيطة ، إلا أن الوضع لا يبدو ساخنًا. هذا النطاق من مشاكل API ليس فريدًا لـ WIX. كان على عدد كبير من الشركات التعامل معها. الآن من المثير للاهتمام أن ننظر إلى حل محتمل.
GraphQL. ابدأ
في عام 2012 ، في عملية تطوير تطبيق الهاتف المحمول ، واجه Facebook مشكلة مماثلة. أراد المهندسون تحقيق الحد الأدنى من مكالمات تطبيقات الهاتف المحمول إلى الخادم ، بينما في كل خطوة لم يتلقوا سوى البيانات اللازمة ولا شيء غيرهم. كانت نتيجة جهودهم GraphQL ، التي تم تقديمها في مؤتمر React Conf لعام 2015. GraphQL هي لغة وصف طلب البحث ، فضلاً عن بيئة وقت التشغيل لهذه الاستعلامات.

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

بالطبع ، لكي يكون الرسم البياني مفيدًا ، هناك حاجة أيضًا إلى ما يسمى "نقاط الدخول". على سبيل المثال ، قد تكون نقطة الدخول هذه هي الحصول على مستخدم بالاسم.
طلب بيانات
دعونا نرى ما هو جوهر لغة الاستعلام GraphQL. دعنا نترجم هذا السؤال إلى هذه اللغة:
"بالنسبة لمستخدم يُدعى فانيا يونيكورن ، أريد أن أعرف أسماء أصدقائه ، بالإضافة إلى اسم وسكان المدينة التي تعيش فيها فانيا" :
{ user(name: "Vanya Unicorn") { friends { name } city { name population } } }
وهنا يأتي الجواب من خادم GraphQL:
{ "data": { "user": { "friends": [ { "name": "Lena" }, { "name": "Stas" } ] "city": { "name": "Kyiv", "population": 2928087 } } } }
لاحظ كيف أن نموذج الطلب "متوافق" مع نموذج الرد. هناك شعور بأن لغة الاستعلام هذه تم إنشاؤها لـ JSON. مع كتابة قوية. ويتم كل ذلك في طلب HTTP POST واحد - لا حاجة لإجراء عدة مكالمات إلى الخادم.
دعونا نرى كيف تبدو في الممارسة العملية. دعونا نفتح وحدة التحكم القياسية لخادم GraphQL ، والتي تسمى Graph
i QL ("الرسم البياني"). لطلب سلة ، سأفي بالطلب التالي:
"أريد الحصول على سلة بالمعرف 1 ، أنا مهتم بجميع أوضاع هذه السلة ومعلومات المنتج. من المعلومات ، يعد الاسم والسعر ورقم المخزون والصور مهمة (والأولى فقط). أنا مهتم أيضًا بكمية هذه المنتجات ، ما هو سعرها والتكلفة الإجمالية في السلة .
" { cart(id: 1) { items { product { title price sku images(limit: 1) } quantity total } subTotal } }
بعد الانتهاء بنجاح من الطلب ، نحصل بالضبط على ما سئل:

الفوائد الرئيسية
- أخذ عينات مرنة. يمكن للعميل تقديم طلب وفقًا لمتطلباته الخاصة.
- أخذ العينات الفعال. يقوم الرد بإرجاع البيانات المطلوبة فقط.
- تنمية أسرع. يمكن أن تحدث العديد من التغييرات على العميل دون الحاجة إلى تغيير أي شيء من جانب الخادم. على سبيل المثال ، استنادًا إلى مثالنا ، يمكنك بسهولة عرض طريقة عرض مختلفة للسلة لويب الجوال.
- تحليلات مفيدة. نظرًا لأن العميل يجب أن يشير إلى الحقول بشكل صريح في الطلب ، فإن الخادم يعرف بالضبط الحقول المطلوبة حقًا. وهذه معلومات مهمة لسياسة الإهلاك.
- يعمل على رأس أي مصدر للبيانات والنقل. من المهم أن يسمح لك GraphQL بالعمل على رأس أي مصدر بيانات وأي نقل. في هذه الحالة ، HTTP ليس حلاً سحريًا ، يمكن أن يعمل GraphQL أيضًا من خلال WebSocket ، وسوف نتطرق إلى هذه النقطة لاحقًا.
اليوم ، يمكن إنشاء خادم GraphQL بأي لغة تقريبًا. إن الإصدار الأكثر اكتمالاً من خادم
GraphQL هو
GraphQL.js لمنصة Node. في مجتمع Java ، يكون تنفيذ المرجع هو
GraphQL Java .
إنشاء API GraphQL
دعونا نرى كيفية إنشاء خادم GraphQL على مثال ملموس للحياة.
ضع في اعتبارك إصدارًا مبسطًا لمتجر عبر الإنترنت يستند إلى بنية خدمة صغيرة تتكون من مكونين:
- خدمة عربة توفر العمل مع سلة مخصصة. يخزن البيانات في قاعدة بيانات علائقية ويستخدم SQL للوصول إلى البيانات. خدمة بسيطة للغاية ، بدون الكثير من السحر :)
- خدمة المنتج توفر الوصول إلى كتالوج المنتج ، الذي يتم فيه تعبئة السلة في الواقع. يوفر HTTP API للوصول إلى بيانات المنتج.
يتم تنفيذ كلتا الخدمتين أعلى Boot Spring الكلاسيكي وتحتوي بالفعل على جميع المنطق الأساسي.

نحن عازمون على إنشاء API GraphQL في أعلى خدمة سلة التسوق. تم تصميم واجهة برمجة التطبيقات هذه لتوفير الوصول إلى بيانات سلة المنتجات والمنتجات المضافة إليها.
الإصدار الأول
سيساعدنا تنفيذ مرجع GraphQL لنظام جافا البيئي ، الذي ذكرناه سابقًا - GraphQL Java.
أضف بعض التبعيات إلى
pom.xml:
<dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java</artifactId> <version>9.3</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java-tools</artifactId> <version>5.2.4</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphiql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency>
بالإضافة إلى
graphql-java
المذكور سابقًا
graphql-java
سوف نحتاج إلى مكتبة
graphql-java-tools,
بالإضافة إلى "مشغلات" Boot Spring للمبتدئين في GraphQL ، والتي ستبسط إلى حد كبير الخطوات الأولى لإنشاء خادم GraphQL:
- يوفر graphql-spring-boot-starter آلية لتوصيل GraphQL Java بسرعة إلى Boot Spring ؛
- يضيف graphiql-spring-boot-starter وحدة تحكم ويب تفاعلية Graph i QL لتشغيل استعلامات GraphQL.
الخطوة المهمة التالية هي تحديد مخطط خدمة الرسم البياني ، الرسم البياني الخاص بنا. يتم وصف عقد هذا الرسم البياني باستخدام
الأنواع ، والحواف باستخدام
الحقول . يبدو تعريف الرسم البياني الفارغ كما يلي:
schema { }
في هذا المخطط ، كما تتذكر ، هناك "نقاط دخول" أو استعلامات المستوى الأعلى. يتم تعريفها من خلال حقل
الاستعلام في المخطط. اتصل
بنوعنا للحصول على
نقاط دخول
EntryPoints :
schema { query: EntryPoints }
نحدد فيه بحث سلة حسب المعرف كنقطة الدخول الأولى:
type EntryPoints { cart(id: Long!): Cart }
Cart
ليست أكثر من
حقل من حيث الرسم البياني.
id
معلمة من هذا المجال مع نوع العددية
Long
. علامة تعجب
!
بعد تحديد النوع يعني أن المعلمة مطلوبة.
حان الوقت لتحديد وكتابة
Cart
:
type Cart { id: Long! items: [CartItem!]! subTotal: BigDecimal! }
بالإضافة إلى
id
القياسي ، تتضمن السلة عناصر العناصر ومقدار جميع منتجات
subTotal
. لاحظ أن
العناصر يتم تعريفها على أنها قائمة ، كما هو موضح بأقواس مربعة
[]
. عناصر هذه القائمة هي أنواع
CartItem
. وجود علامة تعجب بعد اسم نوع الحقل
!
يشير إلى أن الحقل مطلوب. هذا يعني أن الخادم يوافق على إرجاع قيمة غير فارغة لهذا الحقل ، إذا تم طلبها.
يبقى النظر إلى تعريف نوع
CartItem
، والذي يتضمن رابطًا للمنتج (
productId ) ، وعدد المرات التي يتم إضافتها إلى السلة (
quantity
) وكمية المنتج ، محسوبة على الرقم (
total
):
type CartItem { productId: String! quantity: Int! total: BigDecimal! }
كل شيء بسيط هنا - جميع حقول الأنواع العددية إلزامية.
لم يتم اختيار هذا المخطط عن طريق الصدفة. لقد حددت خدمة سلة التسوق بالفعل سلة السلة وعناصر
CartItem
بنفس أسماء الحقول وأنواعها تمامًا كما هو الحال في مخطط GraphQL. يستخدم نموذج عربة التسوق مكتبة لومبوك لتوليد السيارات / المستوطنين والمنشئين وطرق أخرى. يستخدم JPA للاستمرار في قاعدة البيانات.
فئة
Cart
:
import lombok.Data; import javax.persistence.*; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @Entity @Data public class Cart { @Id @GeneratedValue private Long id; @ElementCollection(fetch = FetchType.EAGER) private List<CartItem> items = new ArrayList<>(); public BigDecimal getSubTotal() { return getItems().stream() .map(Item::getTotal) .reduce(BigDecimal.ZERO, BigDecimal::add); } }
فئة
CartItem
:
import lombok.AllArgsConstructor; import lombok.Data; import javax.persistence.Column; import javax.persistence.Embeddable; import java.math.BigDecimal; @Embeddable @Data @AllArgsConstructor public class CartItem { @Column(nullable = false) private String productId; @Column(nullable = false) private int quantity; @Column(nullable = false) private BigDecimal total; }
لذلك ، يتم وصف السلة (
Cart
) وعناصر السلة (
CartItem
) في كل من الرسم البياني GraphQL وفي الشفرة ، وهي "متوافقة" مع بعضها البعض وفقًا لمجموعة الحقول وأنواعها. ولكن هذا لا يزال غير كافٍ لعمل خدمتنا.
نحتاج إلى توضيح كيفية عمل نقطة الدخول "
cart(id: Long!): Cart
". للقيام بذلك ، قم بإنشاء تكوين Java بسيط للغاية لـ Spring مع نوع من نوع GraphQLQueryResolver. يصف GraphQLQueryResolver فقط "نقاط الدخول" في المخطط. نحدد طريقة باسم مطابق للحقل عند نقطة الدخول (
cart
) ،
cartService
متوافقًا حسب نوع المعلمات ، ونستخدم
cartService
للعثور على نفس السلة حسب المعرف:
@Bean public GraphQLQueryResolver queryResolver() { return new GraphQLQueryResolver () { public Cart cart(Long id) { return cartService.findCart(id); } } }
هذه التغييرات كافية بالنسبة لنا للحصول على تطبيق فعال. بعد إعادة تشغيل خدمة سلة التسوق في وحدة تحكم GraphiQL ، سيبدأ تنفيذ الاستعلام التالي بنجاح:
{ cart(id: 1) { items { productId quantity total } subTotal } }
ملاحظة
- نستخدم الأنواع العددية
Long
and String
فريدة للسلة والمنتج. يحتوي GraphQL على نوع خاص لهذه الأغراض - ID
. دلالة ، هذا هو الخيار الأفضل لواجهة برمجة تطبيقات حقيقية. يمكن استخدام قيم ID
النوع كمفتاح للتخزين المؤقت.
- في هذه المرحلة من تطوير تطبيقنا ، تكون نماذج المجال الداخلي والخارجي متطابقة تمامًا. نحن نتحدث عن
CartItem
Cart
و CartItem
واستخدامها مباشرة في محللات GraphQL. في التطبيقات القتالية ، يوصى بفصل هذه النماذج. بالنسبة لمحللي GraphQL ، يجب وجود نموذج منفصل عن مجال الموضوع الداخلي.
جعل واجهة برمجة التطبيقات مفيدة
لذلك حصلنا على النتيجة الأولى ، وهذا رائع. ولكن الآن API لدينا بدائية للغاية. على سبيل المثال ، حتى الآن لا توجد طريقة لطلب بيانات مفيدة حول منتج ، مثل الاسم والسعر والمقال والصور وما إلى ذلك. بدلاً من ذلك ، هناك
productId
منتج فقط. دعونا نجعل واجهة برمجة التطبيقات مفيدة حقًا ونضيف دعمًا كاملاً لمفهوم المنتج. إليك ما يبدو تعريفه في الرسم التخطيطي:
type Product { id: String! title: String! price: BigDecimal! description: String sku: String! images: [String!]! }
أضف الحقل المطلوب إلى
CartItem
، وضع
productId
الحقل productId على أنه مهمل:
type Item { quantity: Int! product: Product! productId: String! @deprecated(reason: "don't use it!") total: BigDecimal! }
لقد اكتشفنا المخطط. والآن حان الوقت لوصف كيفية عمل التحديد لحقل
product
. اعتمدنا سابقًا على
CartItem
Cart
و
CartItem
، مما سمح لـ GraphQL Java بربط القيم تلقائيًا. ولكن هنا تجدر الإشارة إلى أن خاصية
product
فقط في فئة
CartItem
ليست:
@Embeddable @Data @AllArgsConstructor public class CartItem { @Column(nullable = false) private String productId; @Column(nullable = false) private int quantity; @Column(nullable = false) private BigDecimal total; }
لدينا خيار:
- أضف خاصية المنتج إلى CartItem و " علمها " كيفية تلقي بيانات المنتج ؛
- حدد كيفية الحصول على المنتج دون تغيير فئة CartItem .
الطريقة الثانية هي الأفضل ، لأن نموذج وصف المجال الداخلي (فئة
CartItem
) في هذه الحالة لن يتم تغطيته بتفاصيل تنفيذ Graph
i QL API.
لتحقيق هذا الهدف ، ستساعد واجهة علامة GraphQLResolver. من خلال تنفيذه ، يمكنك تحديد (أو تجاوز) كيفية الحصول على قيم الحقول للنوع
T
هذا هو شكل الفول المطابق في تكوين الربيع:
@Bean public GraphQLResolver<CartItem> cartItemResolver() { return new GraphQLResolver<CartItem>() { public Product product(CartItem item) { return http.getForObject("http://localhost:9090/products/{id}", Product.class, item.getProductId()); } }; }
لم يتم اختيار اسم طريقة
product
عن طريق الصدفة. يبحث GraphQL Java عن طرق تنزيل البيانات حسب اسم الحقل ، وكنا بحاجة فقط إلى تحديد أداة تحميل لحقل
product
! كائن من نوع
CartItem
تم تمريره كمعلمة يحدد السياق الذي يتم فيه تحديد المنتج. التالي هو مسألة تقنية. باستخدام عميل
http
مثل
RestTemplate
نقوم بتقديم طلب GET إلى خدمة المنتج وتحويل النتيجة إلى
Product
، والذي يبدو كما يلي:
@Data public class Product { private String id; private String title; private BigDecimal price; private String description; private String sku; private List<String> images; }
يجب أن تكون هذه التغييرات كافية لتنفيذ عينة أكثر إثارة للاهتمام ، والتي تتضمن العلاقة الحقيقية بين السلة والمنتجات المضافة إليها.
بعد إعادة تشغيل التطبيق ، يمكنك تجربة استعلام جديد في وحدة تحكم Graph
i QL.
{ cart(id: 1) { items { product { title price sku images } quantity total } subTotal } }
وهنا نتيجة تنفيذ الاستعلام:

على الرغم من وضع علامة على
productId
أنه
@deprecated
،
@deprecated
الاستعلامات التي تشير إلى هذا الحقل في العمل. لكن وحدة تحكم Graph
i QL لن تقدم الإكمال التلقائي لهذه الحقول وستسلط الضوء على استخدامها بطريقة خاصة:

حان الوقت لإظهار مستكشف المستندات ، وهو جزء من وحدة تحكم Graph
i QL ، والذي تم بناؤه على أساس مخطط GraphQL ويعرض معلومات حول جميع الأنواع المحددة. إليك ما يبدو عليه Document Explorer لنوع
CartItem
:

لكن عد إلى المثال. من أجل تحقيق نفس الوظائف كما في العرض التوضيحي الأول ، لا يزال هناك حد غير كافٍ لعدد الصور التي تم إرجاعها. في الواقع ، بالنسبة للسلة ، على سبيل المثال ، تحتاج إلى صورة واحدة فقط لكل منتج:
images(limit: 1)
للقيام بذلك ، قم بتغيير النظام وإضافة معلمة جديدة لحقل
الصور إلى نوع
المنتج :
type Product { id: ID! title: String! price: BigDecimal! description: String sku: String! images(limit: Int = 0): [String!]! }
وفي رمز التطبيق سنستخدمه مرة أخرى GraphQLResolver
، هذه المرة فقط حسب النوع Product
: @Bean public GraphQLResolver<Product> productResolver() { return new GraphQLResolver<Product>() { public List<String> images(Product product, int limit) { List<String> images = product.getImages(); int normalizedLimit = limit > 0 ? limit : images.size(); return images.subList(0, Math.min(normalizedLimit, images.size())); } }; }
مرة أخرى ، استرعي انتباهكم إلى أن اسم الطريقة ليس عرضيًا: فهو يتزامن مع اسم الحقل images
. Product
يتيح كائن السياق الوصول إلى الصور ، limit
وهو معلمة للحقل نفسه.إذا لم يحدد العميل أي شيء كقيمة له limit
، فستعرض خدمتنا جميع صور المنتج. إذا حدد العميل قيمة معينة ، فستعود الخدمة بنفس المقدار (ولكن ليس أكثر من أي قيمة في المنتج).نقوم بتجميع المشروع والانتظار حتى تتم إعادة تشغيل الخادم. إعادة تشغيل الدائرة في وحدة التحكم وتنفيذ الطلب ، نرى أن الطلب الكامل يعمل حقًا. { cart(id: 1) { items { product { title price sku images(limit: 1) } quantity total } subTotal } }
موافق ، كل هذا رائع جدا. في وقت قصير ، لم نتعلم فقط ما هو GraphQL ، بل نقلنا أيضًا نظام خدمة مايكرو بسيط لدعم واجهة برمجة التطبيقات هذه. ولا يهمنا من أين أتت البيانات: تتناسب واجهات برمجة تطبيقات SQL و HTTP بشكل جيد تحت سقف واحد.الكود الأول ونهج GraphQL SPQR
ربما لاحظت أنه خلال عملية التطوير كان هناك بعض الإزعاج ، أي الحاجة إلى الحفاظ على مخطط وجدول الشفرة باستمرار في المزامنة. يجب دائمًا إجراء تغييرات الكتابة في مكانين. في كثير من الحالات ، من الملائم أكثر استخدام نهج الكود الأول. جوهره هو أن مخطط GraphQL يتم إنشاؤه تلقائيًا من التعليمات البرمجية. في هذه الحالة ، لا تحتاج إلى صيانة الدائرة بشكل منفصل. الآن سأري كيف يبدو.فقط الميزات الأساسية لـ GraphQL Java ليست كافية بالنسبة لنا ، سنحتاج أيضًا إلى مكتبة GraphQL SPQR. الخبر السار هو أن GraphQL SPQR هو إضافة لـ GraphQL Java ، وليس تطبيقًا بديلاً لخادم GraphQL في Java.أضف التبعية المطلوبة إلى pom.xml
: <dependency> <groupId>io.leangen.graphql</groupId> <artifactId>spqr</artifactId> <version>0.9.8</version> </dependency>
إليك الشفرة التي تطبق نفس الوظيفة المستندة إلى GraphQL SPQR للسلة: @Component public class CartGraph { private final CartService cartService; @Autowired public CartGraph(CartService cartService) { this.cartService = cartService; } @GraphQLQuery(name = "cart") public Cart cart(@GraphQLArgument(name = "id") Long id) { return cartService.findCart(id); } }
وبالنسبة للمنتج: @Component public class ProductGraph { private final RestTemplate http; @Autowired public ProductGraph(RestTemplate http) { this.http = http; } @GraphQLQuery(name = "product") public Product product(@GraphQLContext CartItem cartItem) { return http.getForObject( "http://localhost:9090/products/{id}", Product.class, cartItem.getProductId() ); } @GraphQLQuery(name = "images") public List<String> images(@GraphQLContext Product product, @GraphQLArgument(name = "limit", defaultValue = "0") int limit) { List<String> images = product.getImages(); int normalizedLimit = limit > 0 ? limit : images.size(); return images.subList(0, Math.min(normalizedLimit, images.size())); } }
يتم استخدام التعليق التوضيحيGraphQLQuery لوضع علامة على طرق أداة تحميل الحقول. @GraphQLContext
يحدد التعليق التوضيحي نوع التحديد للحقل. ويشير التعليق التوضيحي @GraphQLArgument
بوضوح إلى معلمات الحجة. كل هذه أجزاء من آلية واحدة تساعد GraphQL SPQR على إنشاء مخطط تلقائيًا. الآن ، إذا قمت بحذف تكوين Java القديم ونظامه ، فأعد تشغيل خدمة Cart باستخدام الرقائق الجديدة من GraphQL SPQR ، يمكنك التأكد من أن كل شيء يعمل بنفس الطريقة السابقة.نحل مشكلة N + 1
لقد حان الوقت للنظر في ب على التفاصيل lshih كيفية تنفيذ الطلب كله "تحت غطاء محرك السيارة". لقد أنشأنا بسرعة واجهة برمجة تطبيقات GraphQL ، ولكن هل تعمل بكفاءة؟خذ بعين الاعتبار المثال التالي:
الحصول على السلة cart
يحدث في استعلام SQL واحد لقاعدة البيانات. items
تم subtotal
إرجاع البيانات وإعادتها إلى هناك ، لأن عناصر السلة محملة بالمجموعة بأكملها ، استنادًا إلى إستراتيجية جلب JPA المتلهفة: @Data public class Cart { @ElementCollection(fetch = FetchType.EAGER) private List<Item> items = new ArrayList<>(); ... }
عندما يتعلق الأمر بتنزيل البيانات على المنتجات ، فسيتم تنفيذ طلبات خدمة المنتج تمامًا كما هو الحال في سلة المنتجات هذه. إذا كانت هناك ثلاثة منتجات مختلفة في السلة ، فسوف نتلقى ثلاثة طلبات إلى HTTP API لخدمة المنتج ، وإذا كان هناك عشرة منها ، فيجب على نفس الخدمة الرد على عشرة من هذه الطلبات.
فيما يلي الاتصال بين خدمة Cart وخدمة المنتج في Charles Proxy:
وفقًا لذلك ، نعود إلى مشكلة N + 1 الكلاسيكية. بالضبط تلك التي حاولوا جاهدين الابتعاد عنها في بداية التقرير. بلا شك ، لدينا تقدم ، لأنه يتم تنفيذ طلب واحد بالضبط بين العميل النهائي ونظامنا. ولكن ضمن النظام البيئي للخادم ، من الواضح أن الأداء يحتاج إلى تحسين.أريد حل هذه المشكلة عن طريق الحصول على جميع المنتجات المناسبة في طلب واحد. لحسن الحظ ، تدعم خدمة المنتج هذه الميزة بالفعل من خلال معلمة ids
في مورد المجموعة: GET /products?ids=:id1,:id2,...,:idn
دعونا نرى كيف يمكنك تعديل رمز طريقة العينة لحقل المنتج . الإصدار السابق: @GraphQLQuery(name = "product") public Product product(@GraphQLContext CartItem cartItem) { return http.getForObject( "http://localhost:9090/products/{id}", Product.class, cartItem.getProductId() ); }
استبدل بواحد أكثر فعالية: @GraphQLQuery(name = "product") @Batched public List<Product> products(@GraphQLContext List<Item> items) { String productIds = items.stream() .map(Item::getProductId) .collect(Collectors.joining(",")); return http.getForObject( "http://localhost:9090/products?ids={ids}", Products.class, productIds ).getProducts(); }
فعلنا ثلاثة أشياء بالضبط:- وضع علامة على طريقة برنامج bootloader مع التعليقات التوضيحية المجمعة @ ، مما يوضح لـ GraphQL SPQR أن التحميل يجب أن يحدث مع مجموعة ؛
- تغيير نوع الإرجاع ومعلمة السياق إلى قائمة ، لأن العمل مع الدُفعة يفترض قبول العديد من الكائنات وإعادتها ؛
- تغيير هيكل الطريقة ، وتنفيذ مجموعة مختارة من جميع المنتجات اللازمة في وقت واحد.
هذه التغييرات كافية لحل مشكلة N + 1. تعرض نافذة تطبيق Charles Proxy الآن طلبًا واحدًا لخدمة المنتج ، والذي يُرجع ثلاثة منتجات دفعة واحدة:
عينات ميدانية فعالة
لقد حللنا المشكلة الرئيسية ، ولكن يمكنك جعل التحديد أسرع! الآن تقوم خدمة المنتج بإرجاع جميع البيانات ، بغض النظر عن ما يحتاجه العميل النهائي. يمكننا تحسين الاستعلام وإرجاع الحقول المطلوبة فقط. على سبيل المثال ، إذا لم يطلب العميل النهائي الصورة ، فلماذا نحتاج إلى نقلها إلى خدمة سلة التسوق؟من الرائع أن واجهة برمجة تطبيقات HTTP لخدمة المنتج تدعم هذه الميزة بالفعل من خلال معلمة التضمين لمورد المجموعة نفسه: GET /products?ids=...?include=:field1,:field2,...,:fieldN
بالنسبة لأسلوب أداة تحميل التشغيل ، أضف معلمة من النوع Set مع تعليق توضيحي @GraphQLEnvironment
. يدرك GraphQL SPQR أن الشفرة في هذه الحالة "تطلب" قائمة بأسماء الحقول المطلوبة للمنتج ، وتعبئتها تلقائيًا في: @GraphQLQuery(name = "product") @Batched public List<Product> products(@GraphQLContext List<Item> items, @GraphQLEnvironment Set<String> fields) { String productIds = items.stream() .map(Item::getProductId) .collect(Collectors.joining(",")); return http.getForObject( "http://localhost:9090/products?ids={ids}&include={fields}", Products.class, productIds, String.join(",", fields) ).getProducts(); }
الآن نموذجنا فعال حقًا ، وخالي من مشكلة N + 1 ويستخدم البيانات الضرورية فقط:
استفسارات "ثقيلة"
تخيل العمل باستخدام رسم بياني للمستخدم في شبكة اجتماعية كلاسيكية مثل Facebook. إذا كان مثل هذا النظام يوفر واجهة برمجة تطبيقات GraphQL ، فلا شيء يمنع العميل من إرسال طلب من الطبيعة التالية: { user(name: "Vova Unicorn") { friends { name friends { name friends { name friends { name ... } } } } } }
على مستوى التداخل 5-6 ، سيؤدي التنفيذ الكامل لمثل هذا الطلب إلى اختيار جميع المستخدمين في العالم. من المؤكد أن الخادم لن يكون قادرًا على التعامل مع مثل هذه المهمة في جلسة واحدة وعلى الأرجح أنها ستسقط ببساطة.هناك عدد من الإجراءات التي يجب اتخاذها لحماية نفسك من مثل هذه المواقف:- الحد من عمق الطلب. وبعبارة أخرى ، لا ينبغي السماح للعملاء بطلب بيانات التعشيش التعسفي.
- الحد من تعقيد الطلب. من خلال تعيين وزن لكل حقل وحساب مجموع أوزان جميع الحقول في الطلب ، يمكنك قبول أو رفض مثل هذه الطلبات على الخادم.
على سبيل المثال ، ضع في الاعتبار الاستعلام التالي: { cart(id: 1) { items { product { title } quantity } subTotal } }
من الواضح أن عمق هذا الطلب هو 4 ، لأن أطول مسار بداخله cart -> items -> product -> title
.إذا افترضنا أن وزن كل حقل هو 1 ، ثم مع مراعاة 7 حقول في الاستعلام ، فإن تعقيده هو أيضًا 7.في GraphQL Java ، يتم تحقيق تراكب الشيكات من خلال الإشارة إلى أدوات إضافية عند إنشاء الكائن GraphQL
: GraphQL.newGraphQL(schema) .instrumentation(new ChainedInstrumentation(Arrays.asList( new MaxQueryComplexityInstrumentation(20), new MaxQueryDepthInstrumentation(3) ))) .build();
MaxQueryDepthInstrumentation
تتحقق الأجهزة من عمق الطلب ولا تسمح بإطلاق الطلبات "العميقة" (في هذه الحالة ، بعمق أكبر من 3).الأجهزة MaxQueryComplexityInstrumentation
قبل تنفيذ الاستعلام تحسب وتتحقق من تعقيدها. إذا تجاوز هذا الرقم القيمة المحددة (20) ، فسيتم رفض هذا الطلب. يمكنك إعادة تعريف الوزن لكل حقل ، لأن بعضها يصبح "أصعب" بشكل واضح من البعض الآخر. على سبيل المثال ، يمكن تعيين تعقيد حقل المنتج 10 من خلال التعليق التوضيحي @GraphQLComplexity,
المدعوم في GraphQL SPQR: @GraphQLQuery(name = "product") @GraphQLComplexity("10") public List<Product> products(...)
فيما يلي مثال على فحص العمق عندما يتجاوز بوضوح القيمة المحددة:
بالمناسبة ، لا تقتصر آلية الأجهزة على فرض القيود. يمكن استخدامه أيضًا لأغراض أخرى ، مثل تسجيل الدخول أو التتبع.لقد فحصنا تدابير "الحماية" الخاصة بـ GraphQL. ومع ذلك ، هناك عدد من الحيل التي تستحق الانتباه إليها بغض النظر عن نوع واجهة برمجة التطبيقات:- اختناق / تقييد المعدل - تحديد عدد الطلبات لكل وحدة زمنية
- المهلات - الحد الزمني للعمليات مع الخدمات وقواعد البيانات الأخرى ، وما إلى ذلك ؛
- ترقيم الصفحات - دعم ترقيم الصفحات.
طفرة البيانات
حتى الآن ، نحن نفكر في أخذ عينات بيانات بحتة. لكن GraphQL يسمح لك بتنظيم استلام البيانات ليس فقط ، ولكن أيضًا تغييرها. هناك آلية لذلك
. في المخطط ، يتم حجز مكان خاص لهذا - الحقل mutation
: schema { query: EntryPoints, mutation: Mutations }
على سبيل المثال ، يمكن تنظيم إضافة منتج إلى سلة من خلال الطفرة التالية: type Mutations { addProductToCart(cartId: Long!, productId: String!, count: Int = 1): Cart }
يشبه هذا تحديد حقل ، لأن الطفرة لها أيضًا معلمات وقيمة إرجاع.تنفيذ طفرة في رمز الخادم باستخدام GraphQL SPQR كما يلي: @GraphQLMutation(name = "addProductToCart") public Cart addProductToCart( @GraphQLArgument(name = "cartId") Long cartId, @GraphQLArgument(name = "productId") String productId, @GraphQLArgument(name = "quantity", defaultValue = "1") int quantity) { return cartService.addProductToCart(cartId, productId, quantity); }
بالطبع ، يتم معظم العمل المفيد داخليًا cartService
. ومهمة طريقة البينية هذه هي ربطها بواجهة برمجة التطبيقات. كما هو الحال في أخذ عينات البيانات ، بفضل التعليقات التوضيحية ، من @GraphQL*
السهل جدًا فهم مخطط GraphQL الذي تم إنشاؤه من تعريف الطريقة هذا.في وحدة تحكم GraphQL ، يمكنك الآن إجراء طلب تغيير لإضافة منتج معين إلى سلتنا بمبلغ 2: mutation { addProductToCart( cartId: 1, productId: "59eb83c0040fa80b29938e3f", quantity: 2) { items { product { title } quantity total } subTotal } }
نظرًا لأن الطفرة لها قيمة إرجاع ، فمن الممكن طلب الحقول منها وفقًا لنفس القواعد كما فعلنا للعينات العادية.يستخدم العديد من فرق تطوير WIX بنشاط GraphQL مع Scala ومكتبة Sangria ، وهو التطبيق الرئيسي لـ GraphQL في هذه اللغة.إحدى التقنيات المفيدة المستخدمة في WIX هي دعم استعلامات GraphQL عند عرض HTML. نقوم بذلك لإنشاء JSON مباشرة في شفرة الصفحة. فيما يلي مثال لملء قالب HTML: // Pre-rendered <html> <script data-embedded-graphiql> { product(productId: $productId) title description price ... } } </script> </html>
وها هو الناتج: // Rendered <html> <script> window.DATA = { product: { title: 'GraphQL Sticker', description: 'High quality sticker', price: '$2' ... } } </script> </html>
يسمح لنا هذا المزيج من عارض HTML وخادم GraphQL بإعادة استخدام واجهة برمجة التطبيقات الخاصة بنا إلى أقصى حد وعدم إنشاء طبقة إضافية من وحدات التحكم. علاوة على ذلك ، غالبًا ما يتبين أن هذه التقنية مفيدة من حيث الأداء ، لأنه بعد تحميل الصفحة ، لا يحتاج تطبيق JavaScript إلى الانتقال إلى الواجهة الخلفية لأول بيانات ضرورية - فهي موجودة بالفعل على الصفحة.مساوئ GraphQL
تستخدم GraphQL اليوم عددًا كبيرًا من الشركات ، بما في ذلك الشركات العملاقة مثل GitHub و Yelp و Facebook وغيرها الكثير. وإذا قررت الانضمام إلى رقمهم ، فيجب ألا تعرف فقط مزايا GraphQL ، ولكن أيضًا عيوبها ، وهناك العديد منها:- -, GraphQL . GraphQL , HTTP API. Cache-Control Last-Modified HTTP GraphQL API. , proxy gateways (Varnish, Fastly ). , GraphQL , , .
- GraphQL — . , API, , .
- GraphQL . .
- . GraphQL — . JSON XML, , , GraphQL, .
- GraphQL . , HTTP PUT POST -. , . GraphQL . .
- . , -: «delete» «kill», «annihilate» «terminate», . GraphQL API . HTTP DELETE .
- Joker 2016 . GraphQL . API- , , , HATEOAS, , « REST». , , GraphQL .
من الجدير بالذكر أيضًا أنك إذا لم تنجح في تطوير واجهة برمجة تطبيقات HTTP بشكل جيد ، فلن تتمكن على الأرجح من تطوير واجهة برمجة تطبيقات GraphQL. بعد كل شيء ، ما هو الأهم في تطوير أي API؟ افصل نموذج النطاق الداخلي عن نموذج واجهة برمجة التطبيقات الخارجي. أنشئ واجهة برمجة تطبيقات بناءً على سيناريوهات الاستخدام ، وليس الجهاز الداخلي للتطبيق. افتح فقط الحد الأدنى من المعلومات الضرورية ، وليس كلها على التوالي. اختر الأسماء الصحيحة. صف الرسم البياني بشكل صحيح. يوجد رسم بياني لمورد في HTTP API ، ورسم بياني ميداني في GraphQL API. في كلتا الحالتين ، يجب أن يتم هذا الرسم البياني نوعيًا.هناك بدائل في عالم HTTP API ، ولا يتعين عليك دائمًا استخدام GraphQL عندما تحتاج إلى تحديدات معقدة. على سبيل المثال ، يوجد معيار OData ، الذي يدعم التحديدات الجزئية والمتوسعة ، مثل GraphQL ، ويعمل على أعلى HTTP. هناك واجهة برمجة تطبيقات JSON قياسية تعمل مع JSON وتدعم وسائط الوسائط المتعددة وقدرات الجلب المعقدة. هناك أيضًا LinkRest ، والذي يمكنك معرفة المزيد عنه من خلال https://youtu.be/EsldBtrb1Qc "> تقرير Andrus Adamchik في جوكر 2017.بالنسبة لأولئك الذين يريدون تجربة GraphQL ، أوصي بشدة بقراءة مقالات المقارنة من المهندسين الذين لديهم دراية كبيرة REST و GraphQL من وجهة نظر عملية وفلسفية:أخيرا حول الاشتراكات وتأجيل
يتمتع GraphQL بميزة واحدة مثيرة للاهتمام على واجهات برمجة التطبيقات القياسية. في GraphQL ، يمكن لكل من حالات الاستخدام المتزامن وغير المتزامن أن تجلس تحت نفس السقف.لقد أخذنا في الاعتبار تلقي البيانات من query
خلالك ، وتغيير حالة الخادم من خلال mutation
، ولكن هناك ميزة أخرى جيدة. على سبيل المثال ، القدرة على تنظيم الاشتراكات subscriptions
.تخيل أن العميل يريد تلقي إشعارات حول إضافة منتج إلى السلة بشكل غير متزامن. من خلال واجهة برمجة تطبيقات GraphQL ، يمكن القيام بذلك بناءً على هذا المخطط: schema { query: Queries, mutation: Mutations, subscription: Subscriptions } type Subscriptions { productAdded(cartId: String!): Cart }
يمكن للعميل الاشتراك من خلال الطلب التالي: subscription { productAdded(cart: 1) { items { product ... } subTotal } }
الآن ، في كل مرة يتم فيها إضافة منتج إلى السلة 1 ، سيرسل الخادم رسالة إلى كل عميل مشترك على WebSocket بالبيانات المطلوبة في السلة. مرة أخرى ، استمرارًا لسياسة GraphQL ، ستأتي البيانات التي طلبها العميل عند الاشتراك فقط: { "data": { "productAdded": { "items": [ { "product": …, "subTotal": … }, { "product": …, "subTotal": … }, { "product": …, "subTotal": … }, { "product": …, "subTotal": … } ], "subTotal": 289.33 } } }
يمكن للعميل الآن إعادة رسم السلة ، وليس بالضرورة إعادة رسم الصفحة بأكملها.يعد هذا مناسبًا لأنه يمكن وصف كل من API المتزامن (HTTP) وواجهة برمجة التطبيقات غير المتزامنة (WebSocket) من خلال GraphQL.مثال آخر على استخدام الاتصال غير المتزامن هو آلية التأجيل . الفكرة الرئيسية هي أن العميل يختار البيانات التي يريد تلقيها على الفور (بشكل متزامن) ، وتلك التي يكون مستعدًا لاستلامها لاحقًا (بشكل غير متزامن). على سبيل المثال ، لمثل هذا الطلب: query { feedStories { author { name } message comments @defer { author { name } message } } }
سيقوم الخادم أولاً بإعادة المؤلف ورسالة لكل قصة: { "data": { "feedStories": [ { "author": …, "message": … }, { "author": …, "message": … } ] } }
بعد ذلك ، سيرسل الخادم ، بعد استلام بيانات حول التعليقات ، إلى العميل عبر WebSocket بشكل غير متزامن ، مشيرًا إلى المسار الذي أصبحت فيه تعليقات المحفوظات جاهزة الآن: { "path": [ "feedStories", 0, "comments" ], "data": [ { "author": …, "message": … } ] }
مصدر العينة
يمكن العثور على الرمز المستخدم لإعداد هذا التقرير على GitHub .أعلنا مؤخرًا عن JPoint 2019 ، التي ستعقد من 5 إلى 6 أبريل 2019. معرفة المزيد حول ما يمكن توقعه من مؤتمر يمكن العثور عليها في موقعنا habraposta . حتى الأول من ديسمبر ، لا تزال تذاكر Early Bird متاحة بأقل سعر.