في الآونة الأخيرة ، اكتسب GraphQL شعبية متزايدة. صيغة رشيقة للطلبات والتصنيف والاشتراكات.
يبدو: "ها هي - لقد وجدنا اللغة المثالية لتبادل البيانات!" ...
لقد كنت أتطور باستخدام هذه اللغة لأكثر من عام ، وسأخبركم: كل شيء بعيد عن السلاسة. لدى GraphQL لحظات غير مريحة ومشاكل أساسية في تصميم اللغة نفسها.
من ناحية أخرى ، تم إجراء معظم "حركات التصميم" لسبب ما - كان ذلك بسبب اعتبارات مختلفة. في الواقع ، إن GraphQL ليست للجميع ، وقد لا تكون الأداة التي تحتاجها على الإطلاق. لكن أول الأشياء أولاً.
أعتقد أنه من الجدير إبداء ملاحظة صغيرة حول مكان استخدام هذه اللغة. هذه لوحة إدارة سبا معقدة نوعًا ما ، معظم العمليات التي تكون فيها CRUDs غير بسيطة (كيانات معقدة). يرتبط جزء كبير من الحجة في هذه المادة بطبيعة التطبيق وطبيعة البيانات المعالجة. في التطبيقات من نوع مختلف (أو مع طبيعة مختلفة من البيانات) ، قد لا تنشأ مثل هذه المشاكل من حيث المبدأ.
1. NON_NULL
هذه ليست مشكلة خطيرة. بدلاً من ذلك ، هذه سلسلة كاملة من المضايقات المتعلقة بكيفية تنظيم العمل مع nullable في GraphQL.
هناك لغات برمجة وظيفية (وليس فقط) ، مثل هذا النموذج هو monads. لذلك ، هناك شيء مثل monad Maybe
(Haskel) أو Option
(Scala). خلاصة القول هي أن القيمة الموجودة داخل هذا monad قد تكون أو لا تكون موجودة (أي تكون فارغة). حسنًا ، أو يمكن تنفيذه من خلال التعداد ، كما هو الحال في الصدأ.
بطريقة أو بأخرى ، وفي معظم اللغات ، تجعل هذه القيمة ، التي "تلتف" الأصل ، خيارًا إضافيًا للقيمة الرئيسية. ونحيا - إنها دائما إضافة إلى النوع الرئيسي. هذه ليست دائمًا فئة منفصلة من النوع - في بعض اللغات ، هل هي مجرد إضافة في شكل لاحقة أو بادئة ?
.
والعكس صحيح في GraqhQL. جميع الأنواع قابلة للإلغاء افتراضيًا - وهذا لا يعني فقط كتابة النوع على أنه قابل للإبطال ، بل Maybe
monad ، على العكس.
وإذا أخذنا بعين الاعتبار قسم الاستبطان في حقل name
لمثل هذا المخطط:
نجد:

نوع String
ملفوفة في NON_NULL
1.1. الإخراج
لماذا؟ باختصار ، إنها مرتبطة بتصميم اللغة "المتسامح" الافتراضي (من بين أمور أخرى ، فهي صديقة لهندسة الخدمات الصغيرة).
لفهم جوهر هذا "التسامح" ، ضع في اعتبارك مثالًا أكثر تعقيدًا بعض الشيء ، حيث يتم تغليف جميع القيم التي تم إرجاعها بدقة في NON_NULL:
type User { name: String!
لنفترض أن لدينا خدمة تقوم بإرجاع قائمة المستخدمين ، و "صداقة" منفصلة للخدمة الصغيرة تعيد لنا مباراة لأصدقاء المستخدم. بعد ذلك ، في حالة فشل خدمة "الصداقة" ، لن نتمكن من سرد المستخدمين على الإطلاق. تحتاج إلى إصلاح الوضع:
type User { name: String!
هذا هو التسامح مع الأخطاء الداخلية. مثال ، بالطبع ، بعيد المنال. ولكن آمل أن تكون قد فهمت الجوهر.
بالإضافة إلى ذلك ، يمكنك أن تجعل حياتك أسهل قليلاً في المواقف الأخرى. افترض أن هناك مستخدمين عن بعد ، ويمكن تخزين معرفات الأصدقاء في بعض الهياكل الخارجية غير ذات الصلة. يمكننا فقط إزالة ما لدينا فقط ، ولكن بعد ذلك لن نتمكن من فهم ما تم التخلص منه بالضبط.
type Query {
كل شيء على ما يرام. وما هي المشكلة؟
بشكل عام ، ليست مشكلة كبيرة جدا - حتى الذوق. ولكن إذا كان لديك تطبيق مترابط مع قاعدة بيانات علائقية ، فإن الأخطاء على الأرجح هي أخطاء حقًا ، ويجب أن تكون واجهة برمجة التطبيقات صارمة قدر الإمكان. مرحبا ، علامات التعجب! حيثما استطعت.
أود أن أكون قادرًا على "عكس" هذا السلوك ، ووضع علامات الاستفهام بدلاً من علامات التعجب) سيكون الأمر مألوفًا بطريقة أو بأخرى.
ولكن عند الدخول ، تصبح nullable قصة مختلفة تمامًا. هذا جزء من مستوى مربع الاختيار بتنسيق HTML (أعتقد أن الجميع يتذكر عدم الوضوح هذا عندما لا يتم إرسال حقل مربع الاختيار غير المحدد إلى الخلف).
فكر في مثال:
type Post { id: ID! title: String!
حتى الآن ، جيد جدًا. إضافة تحديث:
type Mutation { createPost(post: PostInput!): Post! updatePost(id: ID!, post: PostInput!): Post! }
والسؤال الآن هو: ماذا يمكن أن نتوقع من حقل الوصف عند تحديث المنشور؟ قد يكون الحقل فارغًا أو قد يكون غائبًا.
إذا كان الحقل مفقودًا ، فما الذي يجب فعله؟ لا تقم بتحديثه؟ أو ضبطه على قيمة خالية؟ خلاصة القول هي أن السماح بالصفر والسماح بغياب حقل هما شيئان مختلفان. ومع ذلك ، GraphQL هو نفس الشيء.
2. فصل المدخلات والمخرجات
هذا مجرد ألم. في نموذج عمل CRUD ، يمكنك الحصول على الكائن من الخلف "لفه" وإرساله مرة أخرى. تقريبا ، هذا هو نفس الشيء. ولكن عليك فقط وصفه مرتين - للإدخال والإخراج. ولا يمكن فعل أي شيء بهذا ، باستثناء كتابة مولد رمز لهذا العمل. أفضّل الفصل في "الإدخال والإخراج" وليس الكائنات نفسها ، ولكن حقول الكائن. على سبيل المثال ، المعدلات:
type Post { input output text: String! output updatedAt(format: DateFormat = W3C): Date! }
أو باستخدام التوجيهات:
type Post { text: String! @input @output updatedAt(format: DateFormat = W3C): Date! @output }
3. تعدد الأشكال
لا تقتصر مشكلات فصل الأنواع في الإدخال والإخراج على وصف مزدوج. في الوقت نفسه ، بالنسبة للأنواع العامة ، يمكنك تحديد واجهات معممة:
interface Commentable { comments: [Comment!]! } type Post implements Commentable { text: String! comments: [Comment!]! } type Photo implements Commentable { src: URL! comments: [Comment!]! }
أو النقابات
type Person { firstName: String, lastName: String, } type Organiation { title: String } union Subject = Organiation | Person type Account { login: String subject: Subject }
لا يمكنك أن تفعل الشيء نفسه لأنواع الإدخال. هناك عدد من المتطلبات الأساسية لهذا ، ولكن هذا يرجع جزئيًا إلى حقيقة أن json يستخدم كتنسيق بيانات للنقل. ومع ذلك ، في الإخراج ، يتم استخدام حقل __typename
لتحديد النوع. لماذا كان من المستحيل فعل الشيء نفسه عند الدخول - ليس واضحًا جدًا. يبدو لي أنه يمكن حل هذه المشكلة بشكل أكثر أناقة من خلال التخلي عن json أثناء النقل وإدخال تنسيقه الخاص. شيء ما في الروح:
union Subject = OrganiationInput | PersonInput input AccountInput { login: String! password: String! subject: Subject! }
ولكن هذا سيجعل من الضروري كتابة محللات إضافية لهذا العمل.
4. الوراثة
ما هو الخطأ في GraphQL مع الأدوية العامة؟ وكل شيء بسيط - ليسوا كذلك. لنأخذ استعلام فهرس CRUD النموذجي مع ترقيم الصفحات أو المؤشر - هذا ليس مهمًا. سأعطي مثالا مع ترقيم الصفحات.
input Pagination { page: UInt, perPage: UInt, } type Query { users(pagination: Pagination): PageOfUsers! } type PageOfUsers { total: UInt items: [User!]! }
والآن للمنظمات
type Query { organizations(pagination: Pagination): PageOfOrganizations! } type PageOfOrganizations { total: UInt items: [Organization!]! }
وهكذا ... كيف أرغب في الحصول على أدوية جنيسة لهذا الغرض
type PageOf<T> { total: UInt items: [T!]! }
ثم أود أن أكتب فقط
type Query { users(page: UInt, perPage: UInt): PageOf<User>! }
نعم طن من التطبيقات! هل يجب أن أخبركم عن الأدوية الجنسية؟
5. مساحات الأسماء
هم ليسوا هناك أيضا. عندما يزيد عدد الأنواع في النظام عن مائة ونصف ، يميل احتمال تضارب الأسماء إلى مائة بالمائة.
وتظهر جميع أنواع Service_GuideNDriving_Standard_Model_Input
. أنا لا أتحدث عن مساحات أسماء كاملة في نقاط نهاية مختلفة ، كما هو الحال في SOAP (نعم ، نعم ، هذا فظيع ، ولكن مساحات الأسماء مصنوعة هناك بشكل مثالي). وعلى الأقل العديد من المخططات على نقطة نهاية واحدة مع القدرة على "تلمس" الأنواع بين المخططات.
المجموع
GraphQL أداة جيدة. إنها تتناسب تمامًا مع بنية الخدمات الصغيرة المتسامحة ، والتي يتم توجيهها أولاً وقبل كل شيء إلى إخراج المعلومات والمدخلات الحتمية البسيطة.
إذا كان لديك كيانات متعددة الأشكال للدخول ، فقد تواجه مشاكل.
إن فصل أنواع المدخلات والمخرجات ، بالإضافة إلى نقص الأدوية الجنيسة - يولد مجموعة من الخربشات من الصفر.
Graphql ليس حقًا (وأحيانًا ليس على الإطلاق ) حول CRUD.
ولكن هذا لا يعني أنه لا يمكنك أكله :)
في المقالة التالية ، أود أن أتحدث عن كيف أقاتل (وأحيانًا بنجاح) مع بعض المشاكل الموضحة أعلاه.