[Was ist los mit GraphQL] ... und wie man damit umgeht

Im vorherigen Artikel haben wir unbequeme Punkte im GraphQL-Typsystem untersucht.
Und jetzt werden wir versuchen, einige von ihnen zu besiegen. Alle Interessierten bitte unter Katze.


Die Partitionsnummerierung entspricht den Problemen, mit denen ich fertig geworden bin.


1.2 NON_NULL INPUT


Zu diesem Zeitpunkt haben wir die Mehrdeutigkeit untersucht, die eine nullbare Implementierungsfunktion in GraphQL generiert.


Das Problem ist, dass das Konzept der teilweisen Aktualisierung nicht von Grund auf neu implementiert werden kann - ein Analogon zur HTTP- PATCH Methode in der REST-Architektur. In Kommentaren zu früheren Materialien wurde ich heftig für "REST" -Denken kritisiert. Ich kann nur sagen, dass mich die CRUD-Architektur dazu verpflichtet. Und ich war nicht bereit, die Vorteile von REST aufzugeben, einfach weil "Tu das nicht". Ja, und es wurde eine Lösung für dieses Problem gefunden.


Und so zurück zum Problem. Wie wir alle wissen, sieht das CRUD-Skript beim Aktualisieren des Datensatzes folgendermaßen aus:


  1. Habe eine Platte von hinten.
  2. Bearbeitete Datensatzfelder.
  3. Schickte eine Aufzeichnung nach hinten.

Das Konzept der teilweisen Aktualisierung sollte es in diesem Fall ermöglichen, nur die Felder zurückzusenden, die geändert wurden.
Wenn wir also ein Eingabemodell auf diese Weise definieren


 input ExampleInput { foo: String! bar: String } 

dann beim ExampleInput einer Variablen vom Typ ExampleInput mit diesem Wert


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

auf einem DTO mit dieser Struktur:


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

Wir erhalten ein DTO-Objekt mit diesem Wert:


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

und beim Zuordnen einer Variablen mit diesem Wert


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

Wir erhalten ein DTO-Objekt mit demselben Wert wie beim letzten Mal:


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

Das heißt, Entropie tritt auf - wir verlieren Informationen darüber, ob das Feld vom Client übertragen wurde oder nicht.
In diesem Fall ist nicht klar, was mit dem Feld des endgültigen Objekts zu tun ist: Berühren Sie es nicht, weil der Client das Feld nicht übergeben hat, oder setzen Sie es auf null weil der Client null .


Genau genommen ist GraphQL ein RPC-Protokoll. Und ich begann darüber nachzudenken, wie ich solche Dinge auf dem Rücken mache und welche Verfahren ich aufrufen sollte, um genau das zu tun, was ich will. Und im Backend aktualisiere ich die Felder teilweise wie folgt:


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

Das heißt, ich berühre den Setter einer Entitätseigenschaft buchstäblich nicht, es sei denn, ich muss den Wert dieser Eigenschaft ändern. Wenn Sie dies in das GraphQL-Schema verschieben, erhalten Sie das folgende Ergebnis:


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

Wenn wir möchten, können wir jetzt die setBar Methode setBar und ihren Wert auf null setzen oder diese Methode nicht berühren. Der Wert wird dann nicht geändert. Somit kommt eine schöne Implementierung des partial update heraus. Nicht schlimmer als PATCH vom berüchtigten REST.


In Kommentaren zu früheren Materialien fragte Summerwind : Warum brauchen wir ein partial update ? Ich antworte: Es gibt sehr große Felder.

3. Polymorphismus


Es kommt häufig vor, dass Sie sich an die Eingabeentitäten wenden müssen, die "ein und dasselbe" sind, aber nicht ganz. Ich werde das Beispiel der Erstellung eines Kontos aus früheren Materialien verwenden.


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

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

Offensichtlich können wir keine Daten mit einer solchen Struktur für ein Argument einreichen - GraphQL erlaubt uns dies einfach nicht. Sie müssen dieses Problem also irgendwie lösen.


Methode 0 - Stirn


Das erste, was mir in den Sinn kommt, ist die Trennung des variablen Teils der Eingabe:


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

Hmm ... wenn ich einen solchen Code sehe, erinnere ich mich oft an Josephine Pavlovna. Es passt nicht zu mir.


Methode 1 - nicht auf der Stirn, sondern auf der Stirn
Dann kam mir die Tatsache zu Hilfe, dass ich zur Identifizierung von Entitäten UUID verwende (im Allgemeinen empfehle ich es jedem - es hilft mehr als einmal). Dies bedeutet, dass ich gültige Entitäten direkt auf dem Client erstellen, sie durch eine Kennung zusammenbinden und sie separat an das Back-End senden kann.


Dann können wir etwas tun im Geiste von:


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

oder, was sich als noch bequemer herausstellte (warum es bequemer ist, werde ich Ihnen sagen, wenn wir zur Generierung von Benutzeroberflächen kommen), teilen Sie es in verschiedene Methoden auf:


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

Dann müssen wir eine Anfrage an createAccount und createOrganization / createPerson senden
eine Charge. Es ist anzumerken, dass dann die Verarbeitung des Stapels in eine Transaktion eingeschlossen werden muss.


Methode 2 - der magische Skalar
Der Trick ist, dass der Skalar in GraphQL nicht nur Int , Sting , Float usw. ist. Dies ist im Allgemeinen alles (na ja, während JSON dies natürlich bewältigen kann).
Dann können wir einfach einen Skalar deklarieren:


 scalar SubjectInput 

Schreiben Sie dann Ihren Handler darauf, und es wird nicht gedämpft. Dann können wir leicht variable Felder in die Eingabe verschieben.


Welchen Weg wählen? Ich benutze beide und habe eine Regel für mich entwickelt:
Wenn die übergeordnete Entität eine aggregierte Wurzel für das untergeordnete Element ist, wähle ich die zweite Methode, andernfalls die erste.


4. Generika.


Hier ist alles trivial und ich habe mir nichts Besseres als die Codegenerierung ausgedacht. Und ohne Rails (railt / sdl-Paket) hätte ich das nicht können (oder besser gesagt, ich hätte dasselbe mit Krücken gemacht). Der Trick besteht darin, dass Sie mit Rail Direktiven auf Dokumentebene definieren können (es gibt keine solche Position für Direktiven im Datenblatt).


 directive @example on DOCUMENT 

Das heißt, Direktiven sind nur an das Dokument angehängt, in dem sie aufgerufen werden.


Ich habe folgende Richtlinien eingeführt:


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

Ich denke, niemand muss die Essenz von Makros erklären ...


Das ist alles für jetzt. Ich denke nicht, dass dieses Material so viel Lärm verursachen wird wie in der Vergangenheit. Trotzdem war der Titel dort ziemlich "gelb")


In den Kommentaren zum vorherigen Artikel sind die Bewohner von Chabrowsk ertrunken, weil sie den Zugang geteilt haben. Im nächsten Artikel geht es also um die Autorisierung.

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


All Articles