Schema Design Patterns — GraphQL

Schema Design Patterns Schema yang baik = API yang mudah dipakai dan berkembang. Schema yang buruk = tech debt yang mahal. 1. Noun-based Types, Verb-based…

Schema GraphQL adalah kontrak antara server dan semua client-nya. Toko Buku punya tiga: web toko, aplikasi kasir, dan dashboard admin, dan ketiganya gak bisa di-update bersamaan. Desain schema yang baik membuat schema bisa tumbuh tanpa memecah client yang sudah ada. Bayangin menu restoran yang sudah dicetak dan dibagikan ke pelanggan tetap. Mengganti nama menu membuat pesanan lama gak dikenali dapur. Lebih aman menambah menu baru, memberi tanda "segera diganti" pada yang lama, lalu menghapusnya saat gak ada lagi yang memesan.

🧪 Tebakan cepat: Tim mengganti nama field price jadi priceRupiah. Web toko yang belum di-update masih mengirim { books { title price status } }. Apa jawabannya: price jadi null, atau lebih buruk? Jawabannya ada di bagian "Deprecation, bukan versi baru".

Penamaan

Bagian Gaya Contoh di Toko Buku
Type, input, enum, union PascalCase, kata benda Book, CoverUpload, StockStatus
Field dan argument camelCase coverUrl, priceRupiah, bookId
Nilai enum SCREAMING_SNAKE_CASE IN_STOCK, SOLD_OUT
Mutation kata kerja dulu, lalu bendanya createBook, updateStock, attachCover
Input dan payload nama mutation + Input / Payload RestockBookInput, RestockBookPayload

Mutation berawalan kata kerja langsung bilang apa yang terjadi. Nama type gak memakai kata kerja (Book, bukan GetBookResponse), karena type yang sama dipakai banyak operasi.

Input type dan payload type per mutation

updateStock(id: ID!, stock: Int!): Book gampang ditulis, tapi sulit dikembangkan. Bentuk yang dipakai banyak API besar:

type Mutation {
  restockBook(input: RestockBookInput!): RestockBookPayload!
}

input RestockBookInput {
  bookId: ID!
  stock: Int!
}

type RestockBookPayload {
  book: Book
  userErrors: [UserError!]!
}

type UserError {
  field: [String!]
  message: String!
}

Nullability: di mana ! aman

Tanda ! adalah janji: nilainya selalu ada. Kalau janji itu gagal sekali saja, null-nya naik ke tempat terdekat yang boleh null (pelajaran "Schema & Type System").

Deprecation, bukan versi baru

GraphQL gak butuh /v2. Field baru ditambahkan, field lama diberi @deprecated dengan alasan, lalu dihapus saat gak ada lagi yang memakainya:

type Book {
  price: Int! @deprecated(reason: "Pakai priceRupiah.")
  priceRupiah: Int!
}

Client lama tetap jalan: { books { title price status } } masih dijawab lengkap. Tool seperti GraphiQL dan linter memberi peringatan; aturan NoDeprecatedCustomRule di graphql-js menulisnya begini: The field Book.price is deprecated. Pakai priceRupiah.

Mengganti nama langsung berbeda jauh. Setelah price diganti priceRupiah, query web toko gak lolos validasi: Cannot query field "price" on type "Book". Seluruh operasinya ditolak, jadi judul dan status pun gak sampai. Itu jawaban tebakannya. Hal yang sama terjadi kalau updateStock(id, stock) langsung diubah jadi updateStock(input:): aplikasi kasir dapat Unknown argument "id" on field "Mutation.updateStock". Caranya: tambah restockBook(input:) di sampingnya, deprecate updateStock, lalu pantau pemakaian tiap field sampai nol sebelum menghapusnya.

Enum, bukan string

status: StockStatus! lebih baik dari status: String!. Enum mendaftar nilai yang mungkin di schema, dan nilai yang salah ditolak sebelum resolver jalan (Value "HABIS" does not exist in "StockStatus" enum.). Mengubah enum jadi String belakangan memecah client yang menulis books(status: SOLD_OUT): String cannot represent a non string value: SOLD_OUT. Menambah nilai enum baru juga perlu hati-hati, karena client yang memakai switch bisa bertemu nilai yang belum ia kenal.

Interface Node dan global ID

Relay, client GraphQL dari Meta, memakai pola global object identification: tiap objek bisa dicari ulang lewat satu field node.

interface Node {
  id: ID!
}

type Book implements Node {
  id: ID!
  title: String!
}

type Query {
  node(id: ID!): Node
}

Mesin GraphQL mini di Lab belum menjalankan interface, jadi bagian ini cuma ditulis sebagai SDL.

Schema-first atau code-first

⚠️ Jebakan yang sering ketemu

TL;DR: Schema adalah kontrak dengan client yang gak di-update bersamaan. Type PascalCase, field camelCase, nilai enum SCREAMING_SNAKE_CASE, mutation diawali kata kerja. Tiap mutation memakai input type sendiri (mengelompokkan argument, bisa dipakai ulang, aman ditambah field) dan payload type (misalnya book plus userErrors). ! cuma untuk nilai yang pasti selalu ada; di schema besar kebanyakan field nullable. Jangan bikin versi baru: tambah field, beri @deprecated(reason:) pada yang lama, hapus setelah pemakaiannya nol; mengganti nama langsung membuat query lama ditolak (Cannot query field "price" on type "Book".). Pakai enum, bukan string. Interface Node dengan global ID (btoa("Book:1")) membuat tiap objek bisa dicari lewat node(id:). Union memilih salah satu dari beberapa object type, interface berbagi field. Schema-first menulis SDL dulu; code-first membuat SDL dari kode sehingga selalu sinkron dengan implementasi.

Yang akan kamu pelajari