[ما هو الخطأ في GraphQL] ... وكيفية التعامل معها

في المقالة السابقة ، درسنا النقاط غير المريحة في نظام نوع GraphQL.
والآن سنحاول هزيمة بعضهم. جميع المهتمين ، من فضلك ، تحت القط.


يتوافق ترقيم القسم مع المشكلات التي تمكنت من التعامل معها.


1.2 NON_NULL INPUT


في هذه المرحلة ، قمنا بفحص الغموض الذي يولد ميزة تنفيذ لاغية في GraphQL.


والمشكلة هي أنه لا يسمح بتنفيذ مفهوم التحديث الجزئي من البداية - وهو تناظرية لطريقة HTTP PATCH في بنية REST. في التعليقات على المواد السابقة ، تعرضت لانتقادات شديدة بسبب تفكير "الراحة". أستطيع أن أقول فقط أن بنية CRUD تلزمني بذلك. ولم أكن مستعدًا للتخلي عن فوائد REST ، ببساطة لأن "لا تفعل هذا". نعم ، وتم العثور على حل لهذه المشكلة.


وهكذا ، عد إلى المشكلة. كما نعلم جميعًا ، يبدو البرنامج النصي CRUD عند تحديث السجل كما يلي:


  1. حصلت على سجل من الخلف.
  2. تم تعديل حقول السجل.
  3. إرسال سجل إلى الخلف.

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


 input ExampleInput { foo: String! bar: String } 

ثم عند تعيين متغير من النوع ExampleInput بهذه القيمة


 { "foo": "bla-bla-bla" } 

على DTO مع هذا الهيكل:


 ExampleDTO { foo: String #   bar: ?String #   } 

نحصل على كائن DTO بهذه القيمة:


 { foo: "bla-bla-bla", bar: null } 

وعند تعيين متغير مع هذه القيمة


 { "foo": "bla-bla-bla", "bar": null } 

نحصل على كائن DTO بنفس قيمة المرة الأخيرة:


 { foo: "bla-bla-bla", bar: null } 

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


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


 $repository->find(42)->setFoo('bla-bla-lba'); 

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


 type Mutation { entityRepository: EntityManager! } type EntityManager { update(id: ID!): PersitedEntity } type PersitedEntity { setFoo(foo: String!): String! setBar(foo: String): String } 

الآن ، إذا أردنا ، يمكننا استدعاء طريقة setBar ، وتعيين قيمتها إلى قيمة خالية ، أو عدم لمس هذه الطريقة ، ثم لن يتم تغيير القيمة. وبالتالي ، فإن التنفيذ الجيد partial update يخرج. ليس أسوأ من PATCH من REST سيئة السمعة.


في التعليقات على المواد السابقة ، سأل برنامج سويندوويند : لماذا نحتاج إلى partial update ؟ أجيب: هناك حقول كبيرة جدا.

3. تعدد الأشكال


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


 #   AccountInput { login: "Acme", password: "***", subject: OrganiationInput { title: "Acme Inc" } } 

 #    AccountInput { login: "Acme", password: "***", subject: PersonInput { firstName: "Vasya", lastName: "Pupkin", } } 

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


الطريقة 0 - الجبهة


أول ما يتبادر إلى الذهن هو فصل الجزء المتغير من الإدخال:


 input AccountInput { login: String! password: Password! subjectOrganization: OrganiationInput subjectPerson: PersonInput } 

حسنًا ... عندما أرى رمزًا كهذا ، أتذكر جوزفين بافلنا. هذا لا يناسبني.


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


ثم يمكننا أن نفعل شيئا بروح:


 input AccountInput { login: String! password: Password! subject: SubjectSelectInput! } input SubjectSelectInput { id: ID! } type Mutation { createAccount( organization: OrganizationInput, person: PersonInput, account: AccountInput! ): Account! } 

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


 type Mutation { createAccount(account: AccountInput!): Account! createOrganization(organization: OrganizationInput!): Organization! createPerson(person: PersonInput!) : Person! } 

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


الطريقة 2 - العددية السحرية
الحيلة هي أن العدد القياسي في GraphQL ليس فقط Int ، Sting ، Float ، إلخ. هذا بشكل عام أي شيء (حسنًا ، بينما JSON يمكنها التعامل مع هذا ، بالطبع).
ثم يمكننا ببساطة إعلان العددية:


 scalar SubjectInput 

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


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


4. الوراثة.


كل شيء تافه هنا ولم أتوصل إلى شيء أفضل من إنشاء الشفرة. وبدون Rails (حزمة railt / sdl) لم أكن قادراً على (أو بالأحرى ، كنت قد فعلت الشيء نفسه مع العكازات). الحيلة هي أن Rail يتيح لك تحديد توجيهات مستوى المستند (لا يوجد مثل هذا الموقف للتوجيهات في ورقة المواصفات).


 directive @example on DOCUMENT 

أي أن التوجيهات غير مرتبطة بأي شيء آخر غير المستند الذي يطلق عليه.


قدمت التوجيهات التالية:


 directive @defineMacro(name: String!, template: String!) on DOCUMENT directive @macro(name: String!, arguments: [String]) on DOCUMENT 

أعتقد أن لا أحد يحتاج إلى شرح جوهر وحدات الماكرو ...


هذا كل شيء الآن. لا أعتقد أن هذه المواد سوف تسبب الكثير من الضوضاء مثل الماضي. على الرغم من ذلك ، كان هناك عنوان "أصفر" جميل


في التعليقات على المقال السابق ، غرق سكان خابروفسك للمشاركة في الوصول ... لذلك فإن المقال التالي سيكون حول إذن.

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


All Articles