62 Resolver dengan Dependency Injection di Go
Dependency Injection (DI) di Go adalah praktik penting yang mendukung testing, scalability, dan pemisahan concern. Namun, integrasi teknik DI dengan provider dependency—seperti resolver pada GraphQL—masih sering membingungkan, terutama untuk engineer Go yang terbiasa dengan struktur kode monolitik. Dalam artikel ini, saya akan membahas penerapan 62 resolver dengan Dependency Injection di Go, membedah konsep ini, menjelaskan best-practices, dan melengkapi dengan contoh kode, diagram, serta tabel simulasi. Mari kita mulai.
Apa Itu Resolver dalam Konteks Go?
Pada umumnya, resolver merujuk pada fungsi/factory yang “meresolusikan” dependensi runtime saat code berjalan. Contoh paling nyata terlihat pada aplikasi GraphQL di Go, misal menggunakan gqlgen. Resolver merepresentasikan layer antara query GraphQL dan implementation business logic, seringkali bergantung pada service, repository atau komponen lain.
Angka 62 di judul menunjuk pada jumlah resolver yang kita simulasikan di skala proyek menengah-besar (misal: 62 endpoint/service logic yang perlu “diresolusikan”).
Tantangan Mengelola 62 Resolver secara Manual
Masalah utama ketika mengelola banyak resolver di Go:
- Duplikasi kode boilerplate: Injeksi dependensi secara manual di setiap resolver.
- Sulit di-maintain: Setiap penambahan dependency baru, mengubah constructor tiap resolver.
- Testing lebih susah: Kalau DI tidak konsisten, mocking dan testing jadi rumit.
Dependency Injection dasar di Go
Go bukan Java atau .NET yang memiliki DI container secara native. Namun, praktik dependency injection tetap bisa digunakan dengan prinsip constructor injection atau interface injection.
Berikut contoh pola paling dasar:
type UserService interface {
GetUser(id string) (*User, error)
}
type UserResolver struct {
Service UserService
}
func NewUserResolver(svc UserService) *UserResolver {
return &UserResolver{Service: svc}
}
Untuk 62 resolver, tanpa DI yang baik:
var postResolver = NewPostResolver(postService)
var commentResolver = NewCommentResolver(commentService)
var friendResolver = NewFriendResolver(friendService)
// dst untuk ke-62 resolver...
Terlalu verbose dan mudah terjadi kesalahan.
Membuat Layer Dependency Injection yang Efektif
Strategi populer untuk resolver injection di Go:
- Manual wiring — Langsung menghubungkan dependency pada main.go atau composition root
- Menggunakan DI container — Library third-party seperti
uber-go/dig
Mari bahas keduanya.
1. Manual Wiring
Paling simple dan “Go idiomatic”. Tabel berikut membandingkan manual wiring dan container:
Kriteria | Manual Wiring | DI Container (dig) |
---|---|---|
Boilerplate | Cukup Banyak | Minim |
Compile-time Safety | Tinggi | Sedang |
Refactor Mudah | Tergantung | Mudah |
Learning Curve | Rendah | Sedang |
Contoh Manual Wiring (Constructor Injection)
Misal kita punya 3 service dan ingin extend ke 62 resolver (kode berikut prinsip dasarnya):
type AppResolvers struct {
UserResolver *UserResolver
PostResolver *PostResolver
CommentResolver *CommentResolver
// ... sampai 62 resolver
}
func NewAppResolvers(db *sql.DB) *AppResolvers {
userService := NewUserService(db)
postService := NewPostService(db)
commentService := NewCommentService(db)
return &AppResolvers{
UserResolver: NewUserResolver(userService),
PostResolver: NewPostResolver(postService),
CommentResolver: NewCommentResolver(commentService),
}
}
Jika dependency chain semakin dalam (misal service tergantung repository, tergantung cache dll), wiring jadi semakin panjang.
2. Dependency Injection Container dengan Uber-dig
Jika resolver mencapai puluhan, library seperti Uber Dig bisa sangat membantu. dig
menyederhanakan pembuatan dependency graph & otomatis resolve dependency.
Instalasi
go get go.uber.org/dig
Implementasi dengan Uber-dig: Simulasi Resolver
Misal kita punya 3 resolver dari total 62 (untuk ringkas):
import (
"database/sql"
"go.uber.org/dig"
)
type UserService struct{ db *sql.DB }
type PostService struct{ db *sql.DB }
type CommentService struct{ db *sql.DB }
func NewUserService(db *sql.DB) *UserService { return &UserService{db} }
func NewPostService(db *sql.DB) *PostService { return &PostService{db} }
func NewCommentService(db *sql.DB) *CommentService{ return &CommentService{db} }
type UserResolver struct{ Service *UserService }
type PostResolver struct{ Service *PostService }
type CommentResolver struct{ Service *CommentService }
func NewUserResolver(svc *UserService) *UserResolver { return &UserResolver{svc} }
func NewPostResolver(svc *PostService) *PostResolver { return &PostResolver{svc} }
func NewCommentResolver(svc *CommentService) *CommentResolver { return &CommentResolver{svc} }
func main() {
container := dig.New()
container.Provide(sql.Open) // supply *sql.DB
container.Provide(NewUserService)
container.Provide(NewPostService)
container.Provide(NewCommentService)
container.Provide(NewUserResolver)
container.Provide(NewPostResolver)
container.Provide(NewCommentResolver)
// resolve all resolvers
container.Invoke(func(
userResolver *UserResolver,
postResolver *PostResolver,
commentResolver *CommentResolver,
) {
// bisa inject ke router, GraphQL server, dll
})
}
Scalability: Cukup tambah .Provide()
setiap resolver/service baru. Uber-dig menguraikan dependency graph dan inject secara otomatis, walaupun jumlahnya ratusan.
Diagram Alur Dependency Injection
Mari lihat gambaran wiring resolver dengan DI menggunakan mermaid:
graph TD subgraph Database Layer DB[(Database)] end subgraph Service Layer USV[UserService] PSV[PostService] CSV[CommentService] end subgraph Resolver Layer UR[UserResolver] PR[PostResolver] CR[CommentResolver] end DB --> USV DB --> PSV DB --> CSV USV --> UR PSV --> PR CSV --> CR %% Dst. hingga 62 resolver
Bayangkan diagram di atas membesar ke 62 node di layer Service & Resolver—tanpa DI container, wiring-nya sangat rawan error.
Studi Simulasi: Penambahan Resolver ke-63
Tanpa DI Container
- Ubah constructor
AppResolvers
- Tambah dependency di main.go
- Add/match di tiap service dan resolver
- Tinggi risiko error
Dengan DI Container
- Tambah
.Provide(NewNewResolver)
pada container - Implementasi tetap konsisten
- No need to modify existing code
Tabel Simulasi Penambahan Dependency:
Dependency | Manual Wiring (Langkah) | uber-dig (Langkah) |
---|---|---|
Kode di Resolver | 1 | 1 |
Kode di AppResolvers | 1 | 0 |
Kode di main.go | 2-3 | 1 |
Risiko Human Error | Sedang | Rendah |
Testing Resolver dengan Dependency Injection
Keuntungan utama DI: testing lebih mudah! Kita bisa mock/services, inject dependency ke resolver, dan isolasi unit test.
func TestUserResolver_GetUser(t *testing.T) {
mockService := &MockUserService{}
resolver := NewUserResolver(mockService)
// lanjutkan dengan pengujian
}
Kesimpulan
Mengelola 62+ resolver di project Go adalah tantangan serius jika dependensi tidak didesain dengan baik. Dependency Injection—baik manual maupun menggunakan container seperti Uber-dig—adalah solusi yang scalable, testable, dan maintainable. Meski Go tidak memiliki DI container built-in, pola constructor injection dan adopt library eksternal memberikan fleksibilitas tinggi untuk menangani codebase yang kompleks.
Jadikan DI sebagai standar baru ketika membangun proyek Go berskala besar—baik untuk resolver GraphQL, REST handler, atau service logic lainnya!
Referensi:
Semoga artikel ini membantu Anda membangun resolver yang scalable di Go!
Have fun coding 🚀
84. Mock gRPC Server dengan `gomock`
Artikel Terhangat
84. Mock gRPC Server dengan `gomock`
08 Aug 2025
61 Nested Resolver dan Resolver Berantai
08 Aug 2025
60 Studi Kasus Real-time Notifikasi
08 Aug 2025
82. Test dengan `bufconn` tanpa Network
08 Aug 2025
59 Skema dan Resolver Subscription
08 Aug 2025
81. Unit Test untuk Handler gRPC
08 Aug 2025

84. Mock gRPC Server dengan `gomock`

61 Nested Resolver dan Resolver Berantai

60 Studi Kasus Real-time Notifikasi

82. Test dengan `bufconn` tanpa Network

59 Skema dan Resolver Subscription
