GraphQL-Mutationsdesign: Anämische Mutationen

Mutationen sind einer der schwierigsten Teile eines GraphQL-Schemas. Wir verbringen viel Zeit damit, über GraphQL-Abfragen zu sprechen und wie einfach sie zu verwenden sind. Mutationen werden jedoch weit weniger geliebt.

In den letzten Tagen habe ich darüber nachgedacht, wie ich das Design von Mutationen einschätze und wie es mit RPC, REST und domänengetriebenem Design zusammenhängt. Ich werde eine Reihe von Beiträgen zu einigen Themen veröffentlichen, die sich auf GraphQL Mutation Design beziehen.

In diesem Beitrag konzentrieren wir uns auf das, was ich anämische Mutationen nenne.

Anämische Mutationen ™ ️

In der domänengetriebenen Designwelt gibt es dieses Ding, das als anämische Domänenmodelle bezeichnet wird. Das AnemicDomainModel ist ein Muster, in dem unser Domänenmodell nur Daten enthält, ohne das damit verbundene Verhalten.

Ich denke, wir können eine ziemlich interessante Verbindung zwischen diesem „Anti-Pattern“ und dem Entwerfen eines guten Mutationssystems in unseren GraphQL-APIs herstellen. Hier ist ein Beispiel für das, was ich als anämische Mutation bezeichnen würde. Stellen Sie sich ein Checkout-Objekt vor, das Teil eines E-Commerce-GraphQL-Schemas ist:

Als Erstes können wir uns ansehen, wie optional alle Eingabefelder von UpdateCheckoutInput sind. Da sie sich für eine einfache CRUD-Mutation entschieden haben und möglicherweise Teilaktualisierungen während des Checkout-Vorgangs zulassen möchten, ist es zunächst sinnvoll, alles optional zu machen. Es gibt ein paar Dinge an diesem Design, die ich wirklich nicht mag.

Da wir uns für eine grobkörnige Mutation entschieden haben, mit der wir Änderungen an diesem Checkout-Objekt vornehmen können, mussten wir zunächst alles auf null setzen (optional). Eine der großen Stärken von GraphQL ist das Typensystem. Indem wir alle Einschränkungen hinsichtlich der Nullbarkeit der Eingabefelder aufheben, haben wir diese Validierung so gut wie auf die Laufzeit verschoben, anstatt das Schema zu verwenden, um den API-Benutzer zur ordnungsgemäßen Verwendung zu führen.

Der zweite Punkt macht diese Mutation zu einer Anämischen Mutation. Wir haben den Eingabetyp sehr datenorientiert gestaltet, anstatt uns auf das Verhalten zu konzentrieren. Stellen Sie sich zum Beispiel vor, dass unser API-Benutzer die API verwenden möchte, um eine Schaltfläche "In den Warenkorb" zu erstellen:

  • Da sich die Mutation so sehr auf Daten und nicht auf Verhaltensweisen konzentriert, müssen unsere Kunden raten, wie sie eine bestimmte Aktion ausführen sollen. Was ist, wenn das Hinzufügen eines Artikels zu unserer Kasse tatsächlich Aktualisierungen einiger anderer Attribute erfordert? Unser Kunde würde nur erfahren, dass durch Fehler zur Laufzeit oder im schlimmsten Fall ein falscher Zustand eintreten kann, wenn er vergisst, ein Attribut zu aktualisieren.
  • Wir haben den Kunden eine kognitive Überlastung hinzugefügt, da sie die zu aktualisierenden Felder auswählen müssen, wenn sie eine bestimmte Aktion ausführen möchten, z. B. "In den Warenkorb".
  • Da wir uns auf die Form der internen Daten einer Kaufabwicklung und nicht auf das mögliche Verhalten einer Kaufabwicklung konzentrieren, geben wir nicht ausdrücklich an, dass es überhaupt möglich ist, diese Aktionen auszuführen. Wir lassen sie anhand unseres Datenmodells erraten.

Schauen wir uns an, wie wir diese Mutation so gestalten können, dass wir explizit erfahren, wie ein Artikel zu einer Kasse hinzugefügt wird:

Wir haben die meisten Probleme behoben, über die wir zuvor gesprochen haben:

  • Unser Schema ist stark typisiert. Bei dieser Mutation ist nichts optional. Unsere Kunden wissen genau, was sie bereitstellen müssen, um einen Artikel zur Kasse hinzuzufügen.
  • Kein Raten mehr. Anstatt herauszufinden, welche Daten aktualisiert werden sollen, fügen wir einen Artikel hinzu. Unseren Kunden ist es egal, welche Daten in diesen Fällen aktualisiert werden müssen, sie möchten lediglich einen Artikel hinzufügen.
  • Die Menge möglicher Fehler, die während der Ausführung dieser Mutation auftreten können, wurde stark reduziert. Unser Resolver kann feinkörnigere Fehler zurückgeben.
  • Der Client kann mit dieser Mutation nicht in einen seltsamen Zustand geraten, da dies von unserem Resolver verarbeitet wird.

Ein interessanter Nebeneffekt beim Entwerfen von Mutationen auf diese Weise ist, dass die Entwicklung dieser Mutationen im Backend viel einfacher wird. Da der Mutationseingang viel vorhersehbarer ist, können wir viele nicht benötigte Validierungen im Resolver entfernen. Eine andere wirklich coole Sache ist, dass das Aussenden von Ereignissen auch einfacher wird. Stellen Sie sich vor, Sie möchten jedes Mal ein ItemAdded-Ereignis ausgeben, wenn ein Benutzer seinem Checkout ein Item hinzufügt. Bei einer großen Fettmutation mit optionalen Eingabefeldern müssen wir jedes Szenario mit Bedingungen überprüfen und Ereignisse ausgeben, die von diesen Bedingungen abhängen. Es wird chaotisch.

In gewisser Weise hängt das mit ein paar Punkten zusammen, die Caleb Meredith vor einiger Zeit in seinem Post (https://dev-blog.apollodata.com/designing-graphql-mutations-e09de826ed97) angesprochen hat, und die ich wirklich genossen habe .

In den kommenden Tagen und Wochen werde ich einige andere Beiträge zum Design von GraphQL-Mutationen veröffentlichen. Ich schreibe über das Verwalten von Zuständen mit Mutationen, das Entwerfen für statische Abfragen und ein gutes Argumentdesign.

Vielen Dank fürs Lesen. Wenn dir dieser Beitrag gefallen hat, kannst du mir auf Twitter folgen! Sie werden wahrscheinlich auch The Little Book of GraphQL Schema Design mögen, an dessen Fertigstellung ich hart arbeite. Es würde eine Menge bedeuten, wenn Sie Updates abonnieren würden!