Grafisk mutationsdesign: Anemiske mutationer

Mutationer er en af ​​de vanskeligste dele af et GraphQL-skema at designe. Vi bruger en masse tid på at tale om GraphQL-forespørgsler og hvor lette de er at bruge. Imidlertid får mutationer langt mindre kærlighed.

Jeg brugte de sidste par dage på at tænke på, hvordan jeg tænker på mutationsdesign, og hvordan det relaterer RPC, REST og domænedrevet design. Jeg lægger en række indlæg om et par emner relateret til GraphQL Mutation Design.

I dette indlæg fokuserer vi på det, jeg har kaldt Anemic Mutations.

Anemic Mutations ™ ️

Der er denne ting, der hedder Anemic Domain Models i den domænedrevne designverden. AnemicDomainModel er hurtigt forklaret og er et mønster, hvor vores domænemodel kun indeholder data uden nogen af ​​de opførsler, der er forbundet med den.

Jeg tror, ​​vi kan lave et ret interessant link mellem dette “Anti-Pattern” og designe et godt mutationssystem i vores GraphQL API'er. Her er et eksempel på, hvad jeg vil beskrive som en anemisk mutation. Forestil dig et Checkout-objekt, en del af et e-handels GraphQL-skema:

Den første ting, vi kan se på, er, hvordan alle inputfelterne på UpdateCheckoutInput er valgfri. Da de har valgt en simpel CRUD-mutation, og da de måske ønsker at tillade delvise opdateringer under kasseprocessen, giver det først mening at gøre alt valgfrit. Der er et par ting ved dette design, som jeg virkelig ikke kan lide.

For det første, da vi besluttede at bruge en "grovkornet" mutation, der giver os mulighed for at foretage ændringer i dette Checkout-objekt, var vi nødt til at gøre alt nullable (valgfrit). En af GraphQLs store styrker er dens typesystem. Ved at fjerne eventuelle nullability-begrænsninger på inputfelterne, har vi stort set udsat al denne validering til runtime, i stedet for at bruge skemaet til at guide API-brugeren mod korrekt brug.

Det andet punkt er, hvad der gør denne mutation til en AnemicMutation. Vi har designet inputtypen på en meget datacentrisk måde i stedet for at fokusere på adfærd. Forestil dig for eksempel, at vores API-bruger ønsker at bruge API'en til at oprette en "Tilføj til indkøbskurv" -knap:

  • Fordi mutationen fokuserer så meget på data og ikke på adfærd, er vores klienter nødt til at gætte, hvordan de laver en bestemt handling. Hvad hvis tilføjelse af en vare til vores kasse faktisk nødvendiggør opdateringer til et par andre attributter? Vores klient lærer kun, at gennem fejl ved kørsel eller værst kan ende i en forkert tilstand ved at glemme at opdatere en attribut.
  • Vi har tilføjet kognitiv overbelastning til klienter, fordi de er nødt til at vælge det sæt felter, der skal opdateres, når de vil udføre en bestemt handling, f.eks. "Føj til indkøbskurv".
  • Da vi fokuserer på formen af ​​de interne data i en checkout og ikke på den potentielle adfærd ved en checkout, angiver vi ikke eksplicit, at det er endda muligt at udføre disse handlinger, vi lader dem gætte ved at se på vores datamodel.

Lad os se på, hvordan vi kunne designe denne mutation, så den eksplicit fortæller os, hvordan vi føjer et element til en kasse:

Vi har rettet de fleste af de problemer, vi talte om tidligere:

  • Vores skema er stærkt indtastet. Intet er valgfrit i denne mutation, vores klienter ved nøjagtigt, hvad de skal levere for at føje en vare til en kasse.
  • Ikke mere gætte. I stedet for at finde hvilke data, der skal opdateres, tilføjer vi en vare. Vores kunder er ligeglad med, hvilke data der skal opdateres i disse tilfælde, de vil bare tilføje en vare.
  • Sættet med potentielle fejl, der kan ske under udførelsen af ​​denne mutation, er blevet kraftigt reduceret. Vores resolver kan returnere mere kornede fejl.
  • Der er ingen måde for klienten at komme i en underlig tilstand ved at bruge denne mutation, fordi det håndteres af vores resolver.

En interessant bivirkning ved at designe mutationer på denne måde er, at udvikling af disse mutationer bliver langt mere ligetil på backend. Da mutationsindgangen er langt mere forudsigelig, kan vi fjerne en masse ikke-nødvendige valideringer i resolver. En anden virkelig cool ting er, at det også bliver lettere at udsende begivenheder. Forestil dig at prøve at udsende en ItemAdded-begivenhed, hver gang en bruger tilføjer et element til deres kasse. I en stor fedtmutation med valgfrie inputfelter er vi nødt til at tjekke for alle scenarier med konditioner og udsende begivenheder afhængigt af disse forhold, det bliver rodet.

På en måde finder jeg disse bånd meget med et par punkter, som Caleb Meredith gjorde i sit indlæg (https://dev-blog.apollodata.com/designing-graphql-mutations-e09de826ed97) for et stykke tid siden, som jeg virkelig nød .

I løbet af de kommende dage og uger vil jeg offentliggøre et par andre indlæg om GraphQL-mutationsdesign. Jeg skriver om styring af tilstand med mutationer, design til statiske forespørgsler og god argumentdesign.

Tak for at have læst Hvis du har haft dette indlæg, kan du følge mig på twitter! Du vil sandsynligvis også nyde The Little Book of GraphQL Schema Design, som jeg arbejder hårdt på at efterbehandle. Det ville betyde meget, hvis du abonnerer på opdateringer!