tutorial

98 Membuat Plugin Sendiri untuk graphql-go

98 Membuat Plugin Sendiri untuk graphql-go

Membangun aplikasi modern yang scalable dan maintainable tidak terlepas dari pemilihan arsitektur API yang tepat. GraphQL, dengan segala kelebihannya dibandingkan REST, telah menjadi primadona dalam ekosistem development saat ini. Salah satu library Go yang sangat populer untuk mengimplementasikan GraphQL adalah graphql-go. Namun, tahukah Anda bahwa Anda dapat memperluas kemampuannya dengan membuat plugin sendiri?

Di artikel ini, saya akan membahas praktik membuat plugin custom pada library graphql-go. Tidak hanya teori, saya juga akan berikan contoh kode, diagram alur, dan simulasi penggunaan plugin yang Anda buat sendiri. Ini penting jika Anda ingin mengintegrasikan logika lintas bidang aplikasi seperti logging, validasi, atau bahkan authentication secara modular.


Kenapa Membutuhkan Plugin di graphql-go?

Meskipun graphql-go sendiri tidak memakai sistem plugin resmi seperti pada TypeScript atau Python, pada praktiknya kita bisa membuat middleware, extension, atau bahkan meng-inject hook ke dalam proses request GraphQL. Pattern ini biasa disebut “plugin-like behavior” atau “extension points”.

Contoh use-case:

KebutuhanKenapa Plugin?
Logging queryAudit, trace, debugging lebih mudah
Validasi customValidasi di luar skema GraphQL (misal validasi business)
Rate LimitingMemproteksi endpoint dari abuse
Custom AuthenticationIntegrasi SSO, JWT, token, dst

Konsep: Dimana Plugin Bisa Ditempatkan?

Untuk itu, kita perlu paham siklus hidup penanganan request pada graphql-go. Sederhananya:

  1. HTTP Handler menerima request GraphQL.
  2. Handler men-decode dan mem-parsing query.
  3. Query dieksekusi pada root resolver.
  4. Hasilnya di-encode ke JSON, dikirim ke client.

Kita bisa menyuntikan extensibility paling fleksibel pada HTTP Handler atau setiap resolver. Berikut adalah diagram siklus hidup request dengan “hook” area untuk plugin:

flowchart DK
    A[HTTP Handler Menerima Request] --> B{Plugin Middleware}
    B --> C[Decode / Parse Query]
    C --> D{Plugin Eksekusi Pre-Resolver}
    D --> E[Eksekusi Resolver]
    E --> F{Plugin Eksekusi Post-Resolver}
    F --> G[Encode Response]
    G --> H[Return ke Client]

Plugin bisa dipasang di middleware handler, sebelum/ sesudah resolver, atau bahkan di dalam resolver.


Studi Kasus: Membuat Plugin Logging Query GraphQL

Mari kita mulai dengan use case sederhana: logging seluruh query dan hasil eksekusinya.

Step 1: Persiapan Project

Buat project Go baru, install graphql-go:

go mod init my-graphql-plugin
go get github.com/graph-gophers/graphql-go
go get github.com/graph-gophers/graphql-go/relay

Siapkan skema sederhana:

# schema.graphql
type Query {
    hello(name: String!): String!
}

Siapkan resolver dasar:

// resolver.go
package main

type Resolver struct{}

func (r *Resolver) Hello(args struct{ Name string }) string {
    return "Hello, " + args.Name
}

Step 2: Membuat Plugin Logging

Buat struct plugin, yang nantinya akan membungkus handler GraphQL milik relay.

Interface Plugin (Pattern Middleware)

Kita define plugin sebagai fungsi yang menerima http.Handler dan mengembalikan http.Handler lagi:

// plugin.go
package main

import "net/http"

type Plugin func(http.Handler) http.Handler

Implementasi Logging Plugin

// logging_plugin.go
package main

import (
    "bytes"
    "io"
    "io/ioutil"
    "log"
    "net/http"
)

func LoggingPlugin(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Method == http.MethodPost {
            // Read the body (may be io.ReadCloser)
            var buf bytes.Buffer
            tee := io.TeeReader(r.Body, &buf)
            body, _ := ioutil.ReadAll(tee)
            r.Body.Close()
            r.Body = io.NopCloser(&buf)
            // Log query GraphQL
            log.Printf("[GraphQL-QUERY]: %s", string(body))
        }
        // Eksekusi handler berikutnya (relay)
        next.ServeHTTP(w, r)
    })
}

Membungkus Handler Relay Dengan Plugin

// main.go
package main

import (
    "log"
    "net/http"
    "os"
    "github.com/graph-gophers/graphql-go"
    "github.com/graph-gophers/graphql-go/relay"
)

func main() {
    schemaBytes, err := os.ReadFile("schema.graphql")
    if err != nil {
        log.Fatal(err)
    }
    schema := graphql.MustParseSchema(string(schemaBytes), &Resolver{})

    relayHandler := &relay.Handler{Schema: schema}
    // Bungkus handler dengan plugin
    handlerWithPlugin := LoggingPlugin(relayHandler)

    http.Handle("/graphql", handlerWithPlugin)
    log.Println("Server run at :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Sekarang, setiap request GraphQL yang masuk akan terekam log-nya:

$ curl -XPOST -d '{"query":"{ hello(name:\"Budi\") }"}' localhost:8080/graphql

Log pada server:

[GraphQL-QUERY]: {"query":"{ hello(name:\"Budi\") }"}

Step 3: Mendukung Multiple Plugin Sekaligus

Bagaimana jika ingin chaining banyak plugin sekaligus, misal logging, rate-limiting dan authentication?

Define fungsi untuk chaining plugin:

// plugin_chain.go
package main

import "net/http"

func ChainPlugins(handler http.Handler, plugins ...Plugin) http.Handler {
    for _, plugin := range plugins {
        handler = plugin(handler)
    }
    return handler
}

Penggunaannya:

handlerWithPlugins := ChainPlugins(relayHandler,
    LoggingPlugin,
    RateLimitPlugin,
    AuthPlugin,
)
http.Handle("/graphql", handlerWithPlugins)

Step 4: Simulasi: Membuat Plugin Rate Limiting Sederhana

Sebagai ilustrasi, inilah contoh plugin sederhana untuk membatasi 5 requests per IP tiap menit:

// rate_limit_plugin.go
package main

import (
    "net/http"
    "sync"
    "time"
)

type rateLimiter struct {
    mu    sync.Mutex
    store map[string][]time.Time
}

func NewRateLimiter() *rateLimiter {
    return &rateLimiter{
        store: make(map[string][]time.Time),
    }
}

func (rl *rateLimiter) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ip := r.RemoteAddr
        rl.mu.Lock()
        reqs := rl.store[ip]
        now := time.Now()
        // Remove expired timestamps (older than 1 min)
        newReqs := []time.Time{}
        for _, t := range reqs {
            if now.Sub(t) < time.Minute {
                newReqs = append(newReqs, t)
            }
        }
        if len(newReqs) >= 5 {
            rl.mu.Unlock()
            http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
            return
        }
        rl.store[ip] = append(newReqs, now)
        rl.mu.Unlock()

        next.ServeHTTP(w, r)
    })
}

Gabungkan ke ChainPlugins:

limiter := NewRateLimiter()
handlerWithPlugins := ChainPlugins(relayHandler,
    LoggingPlugin,
    limiter.Middleware,
)

Step 5: Advanced—Plugin per-Resolver

Untuk akses ke context yang lebih dalam, Anda bisa membuat wrapper pada resolver, misal untuk logging argumen setiap resolver. Gunakan interface dekorator, meskipun pattern ini lebih advanced dan verbose. Opsi lain: inject dependency ke resolver struct.


Kesimpulan

Dengan pattern plugin-like middleware di graphql-go, Anda bisa memperkaya server GraphQL Go Anda dengan fitur middleware modern ala ExpressJS atau FastAPI—meski Go sendiri tidak punya sintaks plugin natif. Dengan menerapkan pola ini, logging, authentication, hingga rate limiting bisa Anda kelola secara modular, testable, dan scalable.

Melangkah lebih jauh, Anda bisa membuat plugin open source sendiri, mendistribusikan ke tim, atau bahkan komunitas!


Rangkuman

  • Plugin di Go = middleware pattern, diterapkan pada handler HTTP.
  • Bisa chain beberapa plugin sekaligus.
  • Digunakan untuk: logging, auth, monitoring, rate limiting, dsb.
  • Mudah diintegrasikan ke project berbasis GraphQL-go.
  • Membantu menjaga codebase tetap clean dan modular.

Jangan ragu bereksperimen dengan middleware custom Anda sendiri—karena sebuah arsitektur yang tangguh adalah arsitektur yang mudah diperluas! 🚀

comments powered by Disqus