25 Menggunakan Interface dan Union Types di graphql-go
GraphQL memberikan fleksibilitas luar biasa dalam mendefinisikan schema API, terutama saat menangani data yang memiliki banyak bentuk (polymorphic). Di antara fitur powerful tersebut, Interface dan Union Types adalah dua alat utama untuk membangun aplikasi yang scalable dan maintainable. Pada artikel kali ini, kita akan membahas secara komprehensif bagaimana menggunakan Interface dan Union Types di graphql-go, library populer untuk membangun GraphQL server di Go.
Apa Itu Interface dan Union Types di GraphQL?
Sebelum bersentuhan dengan graphql-go, mari kita pahami dulu konsep dasarnya dalam GraphQL.
Interface Type
Interface mirip dengan konsep interface di pemrograman berorientasi objek. Sebuah interface mendefinisikan satu set field yang harus diimplementasikan oleh object type yang mengimplementasinya. Query terhadap interface akan mengembalikan salah satu dari object yang mengimplementasinya.
Contoh sederhana:
1interface Character {
2 id: ID!
3 name: String!
4}
5type Human implements Character {
6 id: ID!
7 name: String!
8 homePlanet: String
9}
10type Droid implements Character {
11 id: ID!
12 name: String!
13 primaryFunction: String
14}Union Type
Union mendefinisikan sebuah type yang bisa berupa salah satu dari beberapa type yang ditentukan, tanpa mewajibkan mereka untuk memiliki field yang sama.
Contoh:
1union SearchResult = Human | Droid | StarshipMengimplementasikan Interface dan Union di graphql-go
Untuk kebutuhan produksi, kita menggunakan package github.com/graphql-go/graphql .
Skenario: Kita membuat API karakter Star Wars yang memiliki dua jenis karakter (Human dan Droid) serta sebuah fitur pencarian (search) yang hasilnya bisa berupa objek Human, Droid, atau Starship.
1. Instalasi
1go get github.com/graphql-go/graphql2. Definisi Interface
Mari kita buat interface Character.
1var characterInterface = graphql.NewInterface(graphql.InterfaceConfig{
2 Name: "Character",
3 Fields: graphql.Fields{
4 "id": &graphql.Field{Type: graphql.NewNonNull(graphql.ID)},
5 "name": &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
6 },
7 ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
8 switch p.Value.(type) {
9 case Human:
10 return humanType
11 case Droid:
12 return droidType
13 default:
14 return nil
15 }
16 },
17})Penjelasan:
ResolveTypeadalah fungsi penting untuk runtime type resolution. Ia akan menentukan object mana yang dikembalikan ketika client men-query sebuah interface.
3. Definisi Object Type yang Mengimplementasikan Interface
1type Human struct {
2 ID string `json:"id"`
3 Name string `json:"name"`
4 HomePlanet string `json:"homePlanet"`
5}
6
7var humanType = graphql.NewObject(graphql.ObjectConfig{
8 Name: "Human",
9 Interfaces: []*graphql.Interface{characterInterface},
10 Fields: graphql.Fields{
11 "id": &graphql.Field{Type: graphql.NewNonNull(graphql.ID)},
12 "name": &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
13 "homePlanet": &graphql.Field{Type: graphql.String},
14 },
15})
16
17type Droid struct {
18 ID string `json:"id"`
19 Name string `json:"name"`
20 PrimaryFunction string `json:"primaryFunction"`
21}
22
23var droidType = graphql.NewObject(graphql.ObjectConfig{
24 Name: "Droid",
25 Interfaces: []*graphql.Interface{characterInterface},
26 Fields: graphql.Fields{
27 "id": &graphql.Field{Type: graphql.NewNonNull(graphql.ID)},
28 "name": &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
29 "primaryFunction": &graphql.Field{Type: graphql.String},
30 },
31})4. Definisi Union Type
Misal kita juga punya tipe Starship:
1type Starship struct {
2 ID string `json:"id"`
3 Name string `json:"name"`
4 Model string `json:"model"`
5}
6
7var starshipType = graphql.NewObject(graphql.ObjectConfig{
8 Name: "Starship",
9 Fields: graphql.Fields{
10 "id": &graphql.Field{Type: graphql.NewNonNull(graphql.ID)},
11 "name": &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
12 "model": &graphql.Field{Type: graphql.String},
13 },
14})
15
16var searchResultUnion = graphql.NewUnion(graphql.UnionConfig{
17 Name: "SearchResult",
18 Types: []*graphql.Object{humanType, droidType, starshipType},
19 ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
20 switch p.Value.(type) {
21 case Human:
22 return humanType
23 case Droid:
24 return droidType
25 case Starship:
26 return starshipType
27 default:
28 return nil
29 }
30 },
31})5. Schema GraphQL dan Query
Mari kita susun root query yang menggunakan Interface dan Union:
1var rootQuery = graphql.NewObject(graphql.ObjectConfig{
2 Name: "Query",
3 Fields: graphql.Fields{
4 "character": &graphql.Field{
5 Type: characterInterface,
6 Args: graphql.FieldConfigArgument{
7 "id": &graphql.ArgumentConfig{Type: graphql.NewNonNull(graphql.ID)},
8 },
9 Resolve: func(p graphql.ResolveParams) (interface{}, error) {
10 id := p.Args["id"].(string)
11 if h, ok := humanData[id]; ok {
12 return h, nil
13 }
14 if d, ok := droidData[id]; ok {
15 return d, nil
16 }
17 return nil, nil
18 },
19 },
20 "search": &graphql.Field{
21 Type: graphql.NewList(searchResultUnion),
22 Args: graphql.FieldConfigArgument{
23 "text": &graphql.ArgumentConfig{Type: graphql.NewNonNull(graphql.String)},
24 },
25 Resolve: func(p graphql.ResolveParams) (interface{}, error) {
26 text := p.Args["text"].(string)
27 // Simulasi pencarian
28 results := []interface{}{}
29 for _, h := range humanData {
30 if strings.Contains(h.Name, text) {
31 results = append(results, h)
32 }
33 }
34 for _, d := range droidData {
35 if strings.Contains(d.Name, text) {
36 results = append(results, d)
37 }
38 }
39 for _, s := range starshipData {
40 if strings.Contains(s.Name, text) {
41 results = append(results, s)
42 }
43 }
44 return results, nil
45 },
46 },
47 },
48})6. Contoh Data Dummy
1var humanData = map[string]Human{
2 "1000": {"1000", "Luke Skywalker", "Tatooine"},
3}
4
5var droidData = map[string]Droid{
6 "2000": {"2000", "C-3PO", "Protocol"},
7}
8
9var starshipData = map[string]Starship{
10 "3000": {"3000", "Millennium Falcon", "YT-1300"},
11}Query Contoh
Query untuk interface:
1{
2 character(id: "1000") {
3 id
4 name
5 ... on Human {
6 homePlanet
7 }
8 ... on Droid {
9 primaryFunction
10 }
11 }
12}Query untuk union:
1{
2 search(text: "Sky") {
3 ... on Human {
4 id
5 name
6 homePlanet
7 }
8 ... on Droid {
9 id
10 name
11 primaryFunction
12 }
13 ... on Starship {
14 id
15 name
16 model
17 }
18 }
19}7. Alur Eksekusi dengan Interface dan Union
Mari visualisasikan dengan diagram alur menggunakan Mermaid.
flowchart TD
Q1([Client Query])
S1([Server])
RT1{Apakah Interface?}
RT2{Apakah Union?}
TYPE1([Resolve Type])
RESP([Response])
Q1 --> S1
S1 --> RT1
RT1 -- Ya --> TYPE1
RT1 -- Tidak --> RT2
RT2 -- Ya --> TYPE1
RT2 -- Tidak --> RESP
TYPE1 --> RESP
8. Tabel Perbandingan Interface vs Union
| Aspek | Interface | Union |
|---|---|---|
| Field yang harus dimiliki | Wajib memiliki field tertentu (sesuai interface) | Tidak wajib punya field yang sama |
| Kegunaan utama | Polymorphism dengan field yang seragam | Polymorphism alternatif tanpa field seragam |
| Contoh kasus | User: Admin, Guest, Member | Hasil pencarian yang bisa berbagai tipe objek |
9. Testing Endpoint dengan Go
Anda bisa menguji endpoint dengan code berikut:
1schema, _ := graphql.NewSchema(graphql.SchemaConfig{
2 Query: rootQuery,
3})
4
5params := graphql.Params{
6 Schema: schema,
7 RequestString: `{ search(text: "Sky") { ... on Human { id name homePlanet } } }`,
8}
9
10result := graphql.Do(params)
11fmt.Printf("%v", result.Data)Outputnya akan berupa JSON sesuai type yang dikembalikan.
Penutup
Menggunakan Interface dan Union Types di graphql-go membuat API kita lebih fleksibel dan siap menangani kebutuhan bisnis yang kompleks. Dengan pendekatan ini, aplikasi backend Go Anda akan lebih maintainable, scalable, serta mudah diintegrasikan dengan client yang heterogen.
Jika Anda membangun startup atau proyek besar dengan Go, pastikan selalu memanfaatkan fitur schema GraphQL ini. Integrasikan dengan testing, monitoring, dan dokumentasi yang baik agar developer experience seluruh tim selalu terjaga.
Selamat bereksperimen dan membangun API GraphQL yang solid dengan graphql-go! 🚀