tutorial

67 Menghubungkan Beberapa Service GraphQL di Go

67 Menghubungkan Beberapa Service GraphQL di Go

Ketika skala sistem microservices mulai meningkat, biasanya muncul kebutuhan untuk mengintegrasikan beberapa service agar mampu memberikan pengalaman API yang konsisten. Salah satu pendekatan yang sedang naik daun adalah federasi GraphQL, di mana beberapa service dengan GraphQL API masing-masing digabungkan ke dalam satu gateway terpadu. Pada artikel kali ini, saya akan membahas cara menghubungkan beberapa service GraphQL di Go, lengkap dengan contoh kode, simulasi request, dan diagram alur untuk membangun federasi sederhana.


Mengapa Federasi GraphQL?

Sebelum masuk ke implementasi, mari pahami dulu why-nya. Berikut beberapa alasan popularitas federasi GraphQL:

KeuntunganPenjelasan
Konsistensi APIKonsumen hanya perlu terhubung ke satu endpoint GraphQL.
Pengembangan TerpisahTiap team dapat membangun dan merilis schema GraphQL secara independen.
ScalabilityService bisa dipisah dan diskalakan secara mandiri.
Satu Sumber KebenaranGateway bisa menyatukan data dari berbagai sumber menjadi satu graph besar.

Landscape Library GraphQL di Go

Sampai artikel ini ditulis, library GraphQL di Go yang paling populer antara lain:

Untuk federasi, ekosistem Go belum sematang Node.js. Namun, dengan trik dan orchestrator ringan, solusi federasi tetap bisa dibangun. Pendekatan yang kita pakai di sini adalah Gateway BFF Pattern.


Studi Kasus: Menggabungkan user-service dan post-service

Bayangkan kita memiliki dua service:

  • user-service: menyajikan data user.
  • post-service: menyajikan data post, dengan referensi ke user.

Kita ingin agar di gateway satu endpoint GraphQL yang bisa mengakses data gabungan, misal: mendapatkan daftar posts beserta nama authors.

1. Struktur Arsitektur

flowchart TD
  subgraph Backend
    US(User Service) 
    PS(Post Service)
  end
  GQ[GraphQL Gateway] --> US
  GQ --> PS
  CLIENTS((Clients)) --> GQ

1. Membuat Schema Microservice

Mari mulai dengan mendefinisikan skema di tiap service.

user-service/schema.graphqls

type User {
  id: ID!
  name: String!
  email: String!
}

type Query {
  user(id: ID!): User
  users: [User!]!
}

post-service/schema.graphqls

type Post {
  id: ID!
  title: String!
  content: String!
  authorID: ID!
}

type Query {
  post(id: ID!): Post
  posts: [Post!]!
}

2. Membuat Layanan GraphQL di Go (gqlgen)

Contoh implementasi sederhana untuk user-service (file hanya penting saja, ringkas demi kejelasan):

user-service/main.go

package main

import (
    "log"
    "net/http"
    "github.com/99designs/gqlgen/graphql/handler"
    "github.com/99designs/gqlgen/graphql/playground"
    "user-service/graph"
    "user-service/graph/generated"
)

func main() {
    srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))

    http.Handle("/", playground.Handler("GraphQL playground", "/query"))
    http.Handle("/query", srv)

    log.Println("connect to http://localhost:8081/ for user service playground")
    log.Fatal(http.ListenAndServe(":8081", nil))
}

Isi resolver bisa dengan in-memory data sederhana, begitu juga untuk post-service.


3. Membuat GraphQL Gateway (API-Orchestrator)

Inti artikel ini: menghubungkan dua (atau lebih) GraphQL service ke dalam satu orchestrator. Pada Go, kita belum punya Apollo Federation sepenuhnya, jadi kita akan menggunakan Schema Stitching ala BFF.

Skema Gateway

Kita ingin pengguna bisa melakukan query seperti berikut:

{
  posts {
    id
    title
    author {
      id
      name
    }
  }
}

Artinya, kita perlu menambah field virtual “author” di graph Post pada gateway, yang resolvenya adalah remote call ke user-service.

Contoh Kode Gateway

Instalasi package pendukung:

go get github.com/99designs/gqlgen
go get github.com/machinebox/graphql

gateway/schema.graphqls

type User {
  id: ID!
  name: String!
  email: String!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
}

type Query {
  posts: [Post!]!
  post(id: ID!): Post
}

gateway/resolver.go (inti pola stitch)

package graph

import (
  "context"
  "github.com/machinebox/graphql"
)

type Resolver struct{}

type Post struct {
  ID      string
  Title   string
  Content string
  AuthorID string
}

func (r *queryResolver) Posts(ctx context.Context) ([]*Post, error) {
  // call post-service
  client := graphql.NewClient("http://localhost:8082/query")
  req := graphql.NewRequest(`
    query { posts { id title content authorID } }
  `)
  var resp struct {
    Posts []*Post
  }
  if err := client.Run(ctx, req, &resp); err != nil {
    return nil, err
  }
  return resp.Posts, nil
}

func (r *postResolver) Author(ctx context.Context, obj *Post) (*User, error) {
  // call user-service
  client := graphql.NewClient("http://localhost:8081/query")
  req := graphql.NewRequest(`
    query ($id: ID!) { user(id: $id) { id name email } }
  `)
  req.Var("id", obj.AuthorID)
  var resp struct {
    User *User
  }
  if err := client.Run(ctx, req, &resp); err != nil {
    return nil, err
  }
  return resp.User, nil
}

Penjelasan :

  • Posts query mengambil data ke post-service secara remote.
  • Di setiap author, resolver melakukan remote GraphQL ke user-service.

4. Simulasi Request

query {
  posts {
    id
    title
    author {
      id
      name
    }
  }
}

Diagram Alur

sequenceDiagram
    participant CLIENT
    participant GATEWAY
    participant POST_SERVICE
    participant USER_SERVICE

    CLIENT->>GATEWAY: Query posts { id title author { id name } }
    GATEWAY->>POST_SERVICE: Query posts { id title content authorID }
    POST_SERVICE-->>GATEWAY: Posts Data
    loop For each Post
      GATEWAY->>USER_SERVICE: Query user { id name email } (with authorID)
      USER_SERVICE-->>GATEWAY: User Data
    end
    GATEWAY-->>CLIENT: Daftar posts & author

5. Implikasi dan Keterbatasan

AspekPenjelasan
PerformaUntuk setiap post, ada call ke user-service. Batasi N+1, idealnya pakai batching (DataLoader).
Error HandlingError pada salah satu service harus di-handle dengan baik di orchestrator.
Schema ManagementPerubahan schema di masing-masing service harus selalu di-sync gateway.
Auth & ResilienceGateway menjadi central point untuk security dan throttle.

6. Towards Production

Langkah di atas adalah minimal viable architecture. Untuk production:

  • Gunakan DataLoader untuk menghindari N+1 remote call.
  • Perhatikan timeout dan circuit breaker.
  • Implement cache untuk kebutuhan spesifik.
  • Amati latency agregat.
  • Untuk federasi penuh (apollo federation style), pantau gqlgen feature request 980.

Kesimpulan

Dengan sedikit schema stitching, kita bisa menghubungkan beberapa service GraphQL di Go tanpa framework federasi berat. Pola orchestrator BFF ini sangat cocok untuk team kecil hingga mid-size yang ingin kenyamanan dan konsistensi API global.

Jika kebutuhan semakin kompleks (subgraph ratusan, skema sangat dinamis), lebih baik memantau perkembangan federasi GraphQL native di Go atau, jika memungkinkan, mix dengan Node.js untuk gateway federasi.

Selamat mencoba dan semoga kode Anda lebih terorkestrasi! 🚀


Referensi

comments powered by Disqus