tutorial

25 Menggunakan Interface dan Union Types di graphql-go

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:

  • ResolveType adalah 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

AspekInterfaceUnion
Field yang harus dimilikiWajib memiliki field tertentu (sesuai interface)Tidak wajib punya field yang sama
Kegunaan utamaPolymorphism dengan field yang seragamPolymorphism alternatif tanpa field seragam
Contoh kasusUser: Admin, Guest, MemberHasil 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! 🚀

comments powered by Disqus