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:
interface Character {
  id: ID!
  name: String!
}
type Human implements Character {
  id: ID!
  name: String!
  homePlanet: String
}
type Droid implements Character {
  id: ID!
  name: String!
  primaryFunction: String
}
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:
union SearchResult = Human | Droid | Starship
Mengimplementasikan 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
go get github.com/graphql-go/graphql
2. Definisi Interface
Mari kita buat interface Character.
var characterInterface = graphql.NewInterface(graphql.InterfaceConfig{
    Name: "Character",
    Fields: graphql.Fields{
        "id": &graphql.Field{Type: graphql.NewNonNull(graphql.ID)},
        "name": &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
    },
    ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
        switch p.Value.(type) {
        case Human:
            return humanType
        case Droid:
            return droidType
        default:
            return nil
        }
    },
})
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
type Human struct {
    ID         string `json:"id"`
    Name       string `json:"name"`
    HomePlanet string `json:"homePlanet"`
}
var humanType = graphql.NewObject(graphql.ObjectConfig{
    Name:       "Human",
    Interfaces: []*graphql.Interface{characterInterface},
    Fields: graphql.Fields{
        "id":         &graphql.Field{Type: graphql.NewNonNull(graphql.ID)},
        "name":       &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
        "homePlanet": &graphql.Field{Type: graphql.String},
    },
})
type Droid struct {
    ID              string `json:"id"`
    Name            string `json:"name"`
    PrimaryFunction string `json:"primaryFunction"`
}
var droidType = graphql.NewObject(graphql.ObjectConfig{
    Name:       "Droid",
    Interfaces: []*graphql.Interface{characterInterface},
    Fields: graphql.Fields{
        "id":              &graphql.Field{Type: graphql.NewNonNull(graphql.ID)},
        "name":            &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
        "primaryFunction": &graphql.Field{Type: graphql.String},
    },
})
4. Definisi Union Type
Misal kita juga punya tipe Starship:
type Starship struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Model string `json:"model"`
}
var starshipType = graphql.NewObject(graphql.ObjectConfig{
    Name: "Starship",
    Fields: graphql.Fields{
        "id":    &graphql.Field{Type: graphql.NewNonNull(graphql.ID)},
        "name":  &graphql.Field{Type: graphql.NewNonNull(graphql.String)},
        "model": &graphql.Field{Type: graphql.String},
    },
})
var searchResultUnion = graphql.NewUnion(graphql.UnionConfig{
    Name:  "SearchResult",
    Types: []*graphql.Object{humanType, droidType, starshipType},
    ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
        switch p.Value.(type) {
        case Human:
            return humanType
        case Droid:
            return droidType
        case Starship:
            return starshipType
        default:
            return nil
        }
    },
})
5. Schema GraphQL dan Query
Mari kita susun root query yang menggunakan Interface dan Union:
var rootQuery = graphql.NewObject(graphql.ObjectConfig{
    Name: "Query",
    Fields: graphql.Fields{
        "character": &graphql.Field{
            Type: characterInterface,
            Args: graphql.FieldConfigArgument{
                "id": &graphql.ArgumentConfig{Type: graphql.NewNonNull(graphql.ID)},
            },
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                id := p.Args["id"].(string)
                if h, ok := humanData[id]; ok {
                    return h, nil
                }
                if d, ok := droidData[id]; ok {
                    return d, nil
                }
                return nil, nil
            },
        },
        "search": &graphql.Field{
            Type: graphql.NewList(searchResultUnion),
            Args: graphql.FieldConfigArgument{
                "text": &graphql.ArgumentConfig{Type: graphql.NewNonNull(graphql.String)},
            },
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                text := p.Args["text"].(string)
                // Simulasi pencarian
                results := []interface{}{}
                for _, h := range humanData {
                    if strings.Contains(h.Name, text) {
                        results = append(results, h)
                    }
                }
                for _, d := range droidData {
                    if strings.Contains(d.Name, text) {
                        results = append(results, d)
                    }
                }
                for _, s := range starshipData {
                    if strings.Contains(s.Name, text) {
                        results = append(results, s)
                    }
                }
                return results, nil
            },
        },
    },
})
6. Contoh Data Dummy
var humanData = map[string]Human{
    "1000": {"1000", "Luke Skywalker", "Tatooine"},
}
var droidData = map[string]Droid{
    "2000": {"2000", "C-3PO", "Protocol"},
}
var starshipData = map[string]Starship{
    "3000": {"3000", "Millennium Falcon", "YT-1300"},
}
Query Contoh
Query untuk interface:
{
  character(id: "1000") {
    id
    name
    ... on Human {
      homePlanet
    }
    ... on Droid {
      primaryFunction
    }
  }
}
Query untuk union:
{
  search(text: "Sky") {
    ... on Human {
      id
      name
      homePlanet
    }
    ... on Droid {
      id
      name
      primaryFunction
    }
    ... on Starship {
      id
      name
      model
    }
  }
}
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:
schema, _ := graphql.NewSchema(graphql.SchemaConfig{
    Query: rootQuery,
})
params := graphql.Params{
    Schema:        schema,
    RequestString: `{ search(text: "Sky") { ... on Human { id name homePlanet } } }`,
}
result := graphql.Do(params)
fmt.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! 🚀
47 Reserved Fields dan Reserved Numbers
48 Custom Options di Protobuf
Artikel Terhangat
111 Menambahkan Mutation ke Skema gqlgen
10 Oct 2025
108 Menangani Resolver Otomatis dan Manual
10 Oct 2025
106 Menulis Skema `.graphqls` untuk gqlgen
10 Oct 2025

111 Menambahkan Mutation ke Skema gqlgen

108 Menangani Resolver Otomatis dan Manual

