Razvoj GraphQL API-ja po mjeri

Razvoj GraphQL API-ja daje svakom klijentu jedan endpoint i jezik upita, tako da ekran traži samo polja koja mu trebaju, a ostatak ostaje na serveru. Stranica proizvoda na webu i kartica proizvoda u mobilnoj aplikaciji dijele istu šemu, a novi ekran može dodati polje bez nove rute i bez nove verzije API-ja.

U CodeWizu dizajniramo šeme, pišemo resolvere i doterujemo performanse GraphQL API-ja koji stoje ispred PostgreSQL-a, MySQL-a, postojećih REST servisa, pretrage i redova. Uobičajeni serveri su GraphQL Yoga, Apollo Server ili Mercurius na Node.js-u, te Lighthouse na Laravelu, uz generisane tipove za klijente.

Usluga je za produkt timove sa desetinama preklapajućih REST endpointa, za firme koje web i mobilnu aplikaciju hrane istim podacima, i za platforme koje žele da partneri čitaju podatke bez novog endpointa za svaku integraciju. Kada svaki novi ekran preraste u dogovor oko oblika JSON-a, šema je trajniji ugovor.

GraphQL API: Uredno složene drvene police biblioteke sa knjigama raspoređenim po kategorijama

Razvoj GraphQL API-ja počinje dizajnom šeme

Dizajn šeme je ono što klijenti osjete svaki dan. Tipovi, polja i mutacije su javni jezik proizvoda. Šema nazvana po domenu ostaje stabilna kada podijelite tabelu, dodate keš ili premjestite servis, dok šema prepisana iz tabela curi detalje skladištenja u svaku aplikaciju.

Prvo posao, pa skladište

  • Tipovi koriste riječi koje tim već koristi: Order, Invoice, StockLevel, a ne redove iz spojne tabele.
  • Liste koje rastu koriste cursor paginaciju, sa filterima i sortiranjem navedenim na samom polju.
  • Mutacije imenuju radnju: placeOrder, cancelShipment, publishArticle. Generički update sa vrećom opcionalnih polja krije koje kombinacije su dozvoljene.
  • Nullabilnost je obećanje. Cijena koja mora postojati nije null; popust koji smije izostati jeste, a opis kaže zašto.

Šta dobijate

  • Šemu u SDL-u, pregledanu sa ljudima koji prave ekrane koji je zovu.
  • Politiku aditivnih izmjena: nova polja su sigurna, uklonjena polja prolaze kroz deprecation sa razlogom i datumom, a lomljive izmjene su rijetke i eksplicitne.
  • Primjere operacija za glavne ekrane, koji služe i kao dokumentacija i kao početak testova.
  • Generisanje koda za TypeScript, Swift ili Kotlin klijente, tako da preimenovano polje pukne na buildu, a ne u produkciji.

Dokumentacija GraphQL šeme je referenca za sam jezik tipova. Od te osnove šemu oblikujemo oko vašeg domena.

Razvoj resolvera bez skrivenih troškova upita

Resolver je funkcija koja učitava podatke za jedno polje. Ako svako polje posebno udara u ORM, lista od pedeset proizvoda koji svaki učitavaju kategoriju postaje pedeset i jedan upit prema bazi. Taj N+1 obrazac je uobičajen razlog zašto GraphQL API djeluje sporo iako šema na papiru izgleda uredno.

Resolvere držimo tankim, a poslovna pravila ostaju u servisnom sloju koji ostatak aplikacije može zvati. REST endpointi, pozadinski poslovi i GraphQL API tada dijele jednu implementaciju „place an order“, umjesto tri kopije koje se vremenom raziđu. Testovi ciljaju taj servis, a resolver samo provjerava da je polje vezano na pravi poziv, pa se pravilo ne prepisuje kada se sutra doda još jedna putanja prema istim podacima.

Kako resolveri ostaju jeftini

  • Grupisanje po zahtjevu preko DataLoadera, ili ekvivalenta u Lighthouseu i Mercuriusu, tako da se povezani zapisi učitaju jednom po upitu.
  • Čitanje prema selection setu tamo gdje stack to dozvoljava: ako klijent nije tražio težak opis, upit ga ne vuče.
  • Pretraga, preporuke i potpisani URL-ovi fajlova stoje na poljima koja mogu sama pasti ili se keširati, tako da jedan timeout ne ugasi cijeli ekran.
  • Greške mapirane na mali skup kodova. Neuspješna validacija, nedostajući zapis i pao servis su različiti ishodi, i klijent ih može razlikovati.
const categoryLoader = new DataLoader(async (ids) => { const rows = await db.categories.findByIds(ids); const byId = new Map(rows.map((row) => [row.id, row])); return ids.map((id) => byId.get(id) ?? null); }); const resolvers = { Product: { category: (product, _args, ctx) => ctx.loaders.category.load(product.categoryId), }, };

Ugovor grupisanja opisan je u repozitoriju graphql/dataloader. Posao je primijeniti ga na svaku vezu u šemi.

Linije izvornog koda u editoru na svijetlom ekranu laptopa

Sistem tipova kojem klijenti mogu vjerovati

Sistem tipova je ugovor koji čitaju i kompajler i ljudi. Prilagođeni skalari, enumi, interfejsi i union rezultati uklanjaju klasu grešaka u kojoj je valuta ponekad „eur“, a ponekad „EUR“, ili greška postoji samo unutar teksta poruke.

  • Prilagođeni skalari za DateTime, Money, Email, URL i Locale, svaki sa zapisanim formatom. Novac putuje kao cijeli broj u manjoj jedinici plus enum valute, tako da cijene sa pokretnim zarezom nikad ne izađu u odgovoru.
  • Odvojeni ulazni i izlazni tipovi. Ono što klijent smije poslati pri kreiranju narudžbe je manji i stroži oblik od onoga što API vrati kada narudžba već postoji.
  • Enumi za zatvorene skupove, kao što je status narudžbe, a stringovi samo tamo gdje je vrijednost zaista otvorena, npr. napomena kupca.
  • Interfejsi i unioni kada feed miješa proizvode, članke i banere koji dijele naslov i sliku, a malo toga još.
  • Tipovi rezultata na mutacijama. placeOrder vraća union od OrderPlaced i malog skupa domenskih neuspjeha (nema na stanju, plaćanje odbijeno), pa UI može granati bez parsiranja ljudskog teksta.
Izbor u šemiOdgovara kadaSlabije odgovara kada
Non-null poljeVrijednost je dio ugovora, npr. ukupan iznos narudžbeVrijednost često nedostaje i klijent to mora obraditi
Cursor connectionListe koje se dijele na stranice, filtriraju ili rastuFiksni skup od tri načina dostave
Interface ili unionViše tipova dijeli smislen skup poljaDva tipa koja dijele samo id
Rezultat mutacije kao unionUI mora prikazati očekivani, konkretan neuspjehNeočekivane greške, koje treba iznijeti kao prave greške

Svaki skalar i enum dokumentovan je u opisu šeme. GraphiQL taj tekst pokazuje svakome ko istražuje API, a isti tekst hrani generisane klijente.

Razvoj GraphQL API-ja: optimizacija performansi

Mjerimo operacije koje vaši ekrani zaista šalju, pa uklanjamo troškove koje te operacije plaćaju. Brži proces ne popravlja upit koji na svaki otkucaj učita pola kataloga. Na stagingu snimimo nekoliko stvarnih upita sa ekrana koji se najčešće otvaraju, vidimo koji resolver troši vrijeme i tek onda diramo keš ili indeks.

  • Persisted queries. Javne aplikacije šalju hash umjesto proizvoljnog dokumenta. Nepoznati hash se odbija, što keširanje odgovora i kontrolu zloupotrebe čini praktičnim.
  • Limiti dubine, širine i složenosti. Jedan GraphQL zahtjev može se razgranati u hiljade izvršavanja polja. Limiti su dio konfiguracije servera, sa većim budžetom za vaše vlastite aplikacije nego za anoniman saobraćaj.
  • Keširanje odgovora za javna čitanja, u Redisu ili na CDN-u, sa ključem od operacije i opsega gledaoca. Autentifikovani i anonimni odgovori nikad ne dijele isti unos u kešu.
  • Indeksi baze koji odgovaraju filterima i sortiranju izloženim na šemi. Novi argument filtera je istovremeno odluka o proizvodu i odluka o indeksu.
  • Praćenje po polju na stagingu, i uzorkovano u produkciji, tako da se sporo polje vidi prije nego korisnici prijave težak ekran.

Kada je samo skladište granica, dorada šeme neće pomoći. Dijeljenje ili particioniranje podataka je poseban dizajn, opisan u vodiču o sharding-u i particioniranju baze. Pretraga pripada search engineu: polje search može ići kroz Typesense, umjesto SQL LIKE preko velikih tabela.

Izbliza snimljen stari brzinomjer sa crnim brojčanikom

GraphQL, REST ili smišljena kombinacija

GraphQL je jak produktni API. Loša je zamjena za svaki HTTP endpoint koji već imate. Webhookovi, upload fajlova, health checkovi i treće strane koje govore samo REST trebaju ostati REST. Mnogi sistemi koje gradimo izlažu GraphQL vašim vlastitim aplikacijama, a manju REST površinu svima ostalima.

GraphQLRESTgRPC
Ko oblikuje odgovorKlijent, u okviru šemeServer, po endpointu.proto ugovor
HTTP keširanjeTraži persisted queries ili poseban cache ključPrirodno uz GET i cache zaglavljaNije namijenjen browserima
Mnogo ekranaJedna šema služi webu, mobilnoj aplikaciji i partnerimaEndpointi se množe sa svakim ekranomGenerisani stubovi, slaba priča za browser
Javni integratoriDobro kada su upiti sačuvani i dokumentovaniPoznato većini programeraRijetko ono što partneri očekuju
Upload i webhookoviMoguće, nezgrapnoNajjednostavniji izborPozivi između internih servisa

Subscriptions odgovaraju ekranu koji se mora osvježavati uživo, npr. statusu narudžbe. Traže dugotrajne konekcije i pub/sub backplane, pa je proizvod koji se osvježava na zahtjev bolje pokriti upitom i kratkim kešom. Kada tri stabilna resursa rade posao, predlažemo mali REST API umjesto šeme i lanca alata za klijenta.

Sigurnost i kontrola pristupa na GraphQL API-ju

Autentifikacija dokazuje ko zove. Autorizacija odlučuje koja polja i koje redove smije vidjeti. Obje provjere ulaze u dizajn prije prvog resolvera, jer neograničeno čitanje i dalje može preopteretiti bazu. Limit dubine, budžet složenosti i provjera viewer-a zato stoje u istom pregledu kao i sama šema, prije nego što prvi klijent dobije URL.

  • Autentifikacija na ivici: session kolačići za first-party web, OAuth ili JWT za mobilne aplikacije i partnere. Resolver dobija viewer i sam ne parsira tokene.
  • Autorizacija na tipovima i poljima. Vidjeti User ne znači vidjeti email, a vidjeti Order ne znači vidjeti svaku narudžbu u tenantu.
  • Introspection ugašen na javnom produkcijskom endpointu, ili ograničen na prijavljene programere. Persisted queries zamjenjuju slobodne dokumente iz browsera.
  • Limiti dubine, složenosti i aliasa, plus timeout zahtjeva. Anoniman saobraćaj dobija uži budžet od vaših aplikacija.
  • Rate limiti koji računaju cijenu, a ne samo broj HTTP zahtjeva. Jeftin zahtjev i upit koji spaja pola kataloga nisu isto.
  • Interne greške ostaju interne. Klijenti dobijaju stabilan kod; stack trace i SQL ostaju u logovima.

HTTP rate limiting i dalje stoji ispred GraphQL endpointa. Naš vodič za API rate limiting pokriva obrasce koje spajamo sa limitima cijene upita. OWASP GraphQL Cheat Sheet je lista koju prolazimo prije nego šema izađe izvan vaših aplikacija.

Česte GraphQL greške koje ispravljamo

  • Šema jednom generisana iz tabela i zatim tretirana kao gotova, pa svaka izmjena skladišta postane lomljiva izmjena API-ja.
  • Resolveri koji šalju jedan upit po polju, bez grupisanja i bez traga koji bi to pokazao.
  • Jedan ogromni ulazni tip za svaku mutaciju, sa skoro svakim poljem opcionalnim.
  • Autorizacija sprovedena kao „vrati null“, pa nedostajući i zabranjeni podaci izgledaju isto, a greške se kriju mjesecima.
  • Introspection i neograničena dubina upita ostavljeni otvoreni na javnom endpointu.
  • Poslovna pravila prepisana u resolvere, pa REST putanja i GraphQL putanja prihvataju različite narudžbe.
  • Nema navike deprecationa: polja nestanu između objava, a mobilne aplikacije koje se nisu ažurirale počnu padati.

Šta određuje cijenu GraphQL projekta

Procjenu dajemo nakon kratkog pogleda na domen, klijente i sisteme koje resolveri moraju zvati.

  • Koliko tipova proizvod zaista treba, i koliko klijentskih aplikacija (web, iOS, Android, partneri) generiše kod iz šeme.
  • Da li je model podataka već jasan ili se još mijenja zajedno sa proizvodom.
  • Koliko sistema jedan ekran dira: bazu, pretragu, plaćanja, stari REST servis.
  • Dubina autorizacije: interni alat jednog tenanta manji je posao od pravila po polju kroz više tenanata.
  • Da li trebaju subscriptions, persisted queries i keš na ivici, ili samo upiti.
  • Jednokratna šema i server, ili stalno upravljanje kako novi timovi dodaju polja.

Razvoj GraphQL API-ja mjerimo prema tim faktorima, a procjena stiže prije početka implementacije. Ako tanji REST API rješava isti problem proizvoda, to je prijedlog koji ćete dobiti.

Kako radimo

  1. Analiza: mapiramo ekrane, postojeće endpointe, skladišta podataka i pravila autorizacije, te navedemo operacije koje danas bole.
  2. Šema: sa vama skiciramo tipove i mutacije, dogovorimo šta je non-null i zamrznemo prvu verziju koju klijenti mogu isprobati.
  3. Izrada: implementiramo resolvere, grupisanje, auth, limite i generisanje koda prema toj šemi.
  4. Testiranje: glavne operacije pokrivamo automatskim testovima, vruće upite opterećujemo i provjeravamo da generisani klijenti i dalje prolaze kompajliranje.
  5. Puštanje u rad: API stavljamo pored postojećeg i prelazimo ekran po ekran, uz uključeno praćenje da se spora polja vide rano.
  6. Podrška: vodimo dnevnik deprecationa, pregledamo nova polja i doterujemo limite kako stigne stvarni saobraćaj.

Zašto CodeWiz

  • Full-stack, a ne šema u vakuumu: gradimo Laravel, Node.js, WordPress i mobilne klijente koji zovu API, pa šema nastaje sa obje strane.
  • Open-source na prvom mjestu: Yoga, Apollo, Mercurius, Lighthouse i graphql-codegen drže vas dalje od licence po zahtjevu.
  • Obim po mjeri: GraphQL tamo gdje mnogo klijenata dijeli jedan model koji raste, REST tamo gdje su resurs i webhook dovoljni.
  • Fer cijene: vrhunski razvoj bez vrhunskih cijena, iz Banje Luke, na daljinu sa klijentima iz regiona i svijeta od 2023.
  • Performanse i sigurnost zajedno: cijena upita, keširanje, Cloudflare i rate limiti dio su iste isporuke. Više o studiju je na stranici o CodeWizu.

Često postavljana pitanja

Koliko košta razvoj GraphQL API-ja?

Zavisi od veličine domena, broja klijentskih aplikacija i broja sistema do kojih resolveri moraju doći. Šema nad jednom postojećom bazom kratak je projekat. Partnerski API sa autorizacijom po polju i nekoliko servisa iza njega veći je posao. Pisana procjena stiže nakon analize, prije nego što pišemo resolvere.

Koliko traje prva korisna šema?

Prva verzija koja pokriva glavne ekrane jednog proizvoda često može biti na stagingu za par sedmica, uključujući grupisanje, auth i generisane tipove klijenta. Zamjena velike REST površine ekran po ekran traje duže, jer je migracija dio posla.

Da li treba u potpunosti zamijeniti REST API?

REST ostavite za webhookove, upload, health checkove i integratore koji ga očekuju. GraphQL stavite ispred ekrana proizvoda koji danas slažu nekoliko endpointa. Oba mogu zvati isti servisni sloj.

Da li je GraphQL sporiji od REST-a?

Upit koji zamijeni četiri REST kruga često je brži na mobilnoj mreži, jer telefon plaća jednu konekciju i jedan JSON dokument. Postaje spor kada resolveri nisu grupisani ili jedan upit smije proći cijeli graf. Zato su grupisanje, limiti i praćenje dio izrade.

Možete li dodati GraphQL ispred Laravela ili postojećeg Node.js API-ja?

Možemo. Lighthouse na Laravelu i Node.js server kao Yoga ili Mercurius mogu stajati na aplikaciji koju već imate. Resolveri zovu postojeće servise i repozitorije, tako da domenski kod ostaje na mjestu.

Da li mobilnim aplikacijama treba posebna šema?

Jedna šema služi objema. Mobilna aplikacija i web biraju različita polja iz istih tipova. Odvojene šeme se raziđu čim se polje promijeni samo u jednoj. Lista dozvoljenih persisted upita po aplikaciji dovoljna je kada svaki klijent treba izvršavati samo svoje operacije.

Spremni za dizajn GraphQL API-ja?

Recite nam koji se ekrani danas teško učitavaju i koji sistemi drže podatke. Predložićemo šemu, pristup resolverima i uvođenje koje odgovara klijentima koje već isporučujete. Kontaktirajte nas da razgovaramo o projektu, ili pogledajte cijeli obim naših usluga razvoja GraphQL-a.