tutorial

88 Struktur Project graphql-go yang Scalable

88 Struktur Project graphql-go yang Scalable: Panduan Untuk Backend Engineer

Membangun API dengan GraphQL di Go menawarkan keunggulan pada skalabilitas, maintainability, dan developer experience. Namun, struktur project yang keliru dapat membuat codebase sulit di-maintain bahkan sebelum traffic naik. Maka dari situ, kita membutuhkan strategi struktur project yang solid.

Artikel ini membahas secara praktis tentang 88 struktur project GraphQL-Go yang scalable dan maintainable, berdasarkan pengalaman dan skenario proyek skala menengah hingga perusahaan. Saya juga sajikan contoh snippet, simulasi flow, hingga diagram mermaid agar konsepnya mudah dicerna.


Why Structure Matters?

Kontrak GraphQL yang dinamis dan nested memaksa kita untuk mempunyai struktur kode yang modular dan mudah dikustomasi. Di Go, kita juga harus memperhatikan prinsip separation of concern dan type safety. Kalau tidak hati-hati, Anda akan cepat terjebak pada file resolver yang gemuk, file model atau schema yang bercampur aduk, hingga dependency yang tidak jelas.

Struktur yang baik memberikan:

  • Enkapsulasi & isolasi business logic
  • Kemudahan testing dan pengembangan fitur baru
  • Skalabilitas menambah tim/engineer
  • Integrasi mudah ke sistem lain (REST, gRPC, message bus, dll)

Anatomy: Struktur Project GraphQL-Go Modern

Di bawah ini adalah struktur direktori (simbol 📁 untuk folder, 📄 untuk file):

📁 graph
    📁 schema
        📄 schema.graphqls
        📄 directives.graphqls
    📁 resolver
        📄 user_resolver.go
        📄 product_resolver.go
    📁 generated
        📄 generated.go
    📄 handler.go
    📄 loader.go
📁 domain
    📁 user
        📄 model.go
        📄 repository.go
        📄 service.go
        📄 service_test.go
    📁 product
        📄 model.go
        📄 repository.go
        📄 service.go
📁 pkg
    📄 logger.go
    📄 config.go
    📄 middleware.go
    📄 util.go
📁 cmd
    📄 main.go
📁 migration
    📄 001_init.sql
    📄 002_add_user.sql
....

Tabel 1: Penjelasan Folder

FolderPenjelasan singkat
graphSemua berhubungan langsung dengan GraphQL (schema, loader, handler)
domainModular business logic per entitas/fungsi
pkgShared library/fungsi utilitas
cmdEntry point aplikasi
migrationDatabase migration SQL

Struktur ini sangat scalable karena setiap flow (GraphQL, domain bisnis, utilitas, dan migrasi) terpisah dengan jelas.


Flow Eksekusi Query me (Simulasi)

Bagan berikut membantu memahami flow permintaan GraphQL me (get current user):

graph LR
    A[Client] --> B(GraphQL Handler)
    B --> C(Auth Middleware)
    C --> D(UserResolver.me)
    D --> E(UserService.GetUserByID)
    E --> F(UserRepository.FindByID)
    F --> |Database| G((Postgres))
    G --> F
    F --> E
    E --> D
    D --> C
    C --> B
    B --> A

Deep Dive: Pembahasan Struktur

1. graph/schema/*.graphqls

Pisahkan definisi schema dan directive. Contoh:

# graph/schema/schema.graphqls
type User {
    id: ID!
    name: String!
    email: String!
}

type Query {
    me: User!
}

type Mutation {
    register(name: String!, email: String!, password: String!): User!
}

2. graph/resolver/*.go

Resolver jangan sampai kebanyakan business logic. Fungsinya hanya mapping dan sedikit validasi.

// graph/resolver/user_resolver.go
func (r *queryResolver) Me(ctx context.Context) (*model.User, error) {
    userID := GetUserIDFromContext(ctx)
    user, err := r.UserService.GetUserByID(ctx, userID)
    if err != nil {
        return nil, err
    }
    return user, nil
}

3. domain/user/model.go

Definisi model entity. Tidak boleh ada dependency dengan paket GraphQL.

type User struct {
    ID       string
    Name     string
    Email    string
    Password string
}

4. domain/user/repository.go

Abstraction ke database. Idealnya menggunakan interface agar testable.

type UserRepository interface {
    FindByID(ctx context.Context, id string) (*User, error)
    FindByEmail(ctx context.Context, email string) (*User, error)
    Create(ctx context.Context, user *User) error
}

5. domain/user/service.go

Tempatkan business logic di sini.

type UserService struct {
    repo UserRepository
}

func (s *UserService) GetUserByID(ctx context.Context, id string) (*User, error) {
    return s.repo.FindByID(ctx, id)
}

6. pkg/

Tempatkan shared lib, contoh config dengan env, logger, dan middleware.

// pkg/logger.go
func NewLogger() *zap.Logger {
    logger, _ := zap.NewProduction()
    return logger
}

Saran Modularisasi Untuk 88+ Endpoints

Kenapa saya sebut 88 struktur? Karena setiap domain bisa mewakili 1 atau beberapa GraphQL endpoint. Praktik bagus, pisahkan resolver, service, repository, dan model per domain. Untuk API bisnis rumit (misal, e-commerce: user, product, order, payment, shipping, dst), bisa terus-menerus tambah folder di dalam domain/*.

Contoh penambahan domain baru

📁 domain
    📁 payment
        📄 model.go
        📄 repository.go
        📄 service.go
        ...

Code Generation: Otomatisasi Dengan gqlgen

gqlgen sangat populer untuk generate kode GraphQL di Go dengan type safety. Letakkan konfigurasi di root (gqlgen.yml), dan gunakan folder graph/generated.

Perlu diingat:
Folder generated dan file-file di dalamnya jangan diubah manual. Semua logic custom di letakkan pada resolver/service/domain.


Life-cycle Deploy Production

Diagram alur umum deployment pipeline:

graph LR
    A[Engineer Push Code] --> B[Run Unit Test]
    B --> C[Build Binary]
    C --> D[Run Integration Test]
    D --> E[Create Container Image]
    E --> F[Deploy ke Kubernetes/VM]

Testing: Layered Unit Test

  • Test logic bisnis (di /domain/*/service_test.go)
  • Test integration resolver (pakai test client, e.g. gqlgen)
  • Test middleware dan utility (di /pkg)

Simulasi Unit Test sederhana

func TestUserService_GetUserByID(t *testing.T) {
    mockRepo := new(MockUserRepo)
    mockRepo.On("FindByID", mock.Anything, "123").Return(&User{ID:"123", Name:"Budi"}, nil)
    s := NewUserService(mockRepo)
    user, err := s.GetUserByID(context.TODO(), "123")
    require.NoError(t, err)
    require.Equal(t, "Budi", user.Name)
}

Checklist Good GraphQL-Go Project Structure

CekPenjelasan
Schema terpisahPisah file .graphqls per domain
Resolver thinResolver minim logic bisnis, hanya delegasi
Domain modularSetiap domain punya model, repo, service
Pkg reusableLib utilities tidak tergantung domain spesifik
Dependency inj.Service, repo gunakan injection untuk kemudahan test/migrasi
Automated testTersedia test unit dan integrasi
ConfigurableKonfigurasi (env) terpusat dan mudah diubah
CI/CDSupport pipeline build/test/deploy

Penutup

Struktur project yang scalable bukan hanya soal “banyak folder”, namun tentang disiplin pemisahan concern sekaligus menjaga codebase tetap practical maintainable. Dengan struktur 88 domain modular, Anda bisa mengatasi kompleksitas API GraphQL-Go dengan tim besar, sekaligus tetap bisa iterasi cepat di development.

Tips dari saya: Awali dengan struktur minimal tapi siap berkembang, dan selalu refactor jika ada pain point baru.
Sudahkah struktur project GraphQL-Go Anda seperti ini?


Rangkuman

  • Modularity & separation of concern adalah fondasi skalabilitas
  • Pisahkan resolver, domain logic, utilities, dan schema
  • Coverage testing dan otomasi deployment mutlak wajib
  • Jangan ragu untuk split domain bila jumlah resolver sudah tinggi

Yuk berdiskusi:
Bagaimana pengalaman Anda dengan struktur project GraphQL di Go?
Tulis di kolom komentar!

comments powered by Disqus