İçeriğe geç

Bağımlılık Enjeksiyonu Kalıpları

Gin uygulamanız büyüdükçe, veritabanı bağlantıları, yapılandırma ve servisler gibi bağımlılıkları işleyiciler arasında paylaşmanın temiz bir yoluna ihtiyaç duyarsınız. Go’nun sadeliği, ağır DI framework’leri yerine doğrudan kalıpları teşvik eder.

En deyimsel Go yaklaşımı: bağımlılıkları closure’lar aracılığıyla geçirin.

package main
import (
"database/sql"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
func PingHandler(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
if err := db.Ping(); err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "database unreachable"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
}
func GetUserHandler(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
var name string
err := db.QueryRowContext(c.Request.Context(), "SELECT name FROM users WHERE id = $1", id).Scan(&name)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusOK, gin.H{"name": name})
}
}
func main() {
db, err := sql.Open("postgres", "postgres://user:pass@localhost/dbname?sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
r := gin.Default()
r.GET("/ping", PingHandler(db))
r.GET("/users/:id", GetUserHandler(db))
r.Run(":8080")
}

Bu kalıp küçük ve orta ölçekli uygulamalar için iyi çalışır. Her işleyici bağımlılıklarını açıkça bildirir.

Birçok paylaşılan bağımlılığa sahip uygulamalar için işleyicileri bir struct’ta gruplayın:

package main
import (
"database/sql"
"log/slog"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
type App struct {
DB *sql.DB
Logger *slog.Logger
}
func (a *App) GetUser(c *gin.Context) {
id := c.Param("id")
var name string
err := a.DB.QueryRowContext(c.Request.Context(), "SELECT name FROM users WHERE id = $1", id).Scan(&name)
if err != nil {
a.Logger.Error("user not found", slog.String("id", id), slog.String("error", err.Error()))
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusOK, gin.H{"id": id, "name": name})
}
func (a *App) CreateUser(c *gin.Context) {
var input struct {
Name string `json:"name" binding:"required"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
_, err := a.DB.ExecContext(c.Request.Context(), "INSERT INTO users (name) VALUES ($1)", input.Name)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create user"})
return
}
c.JSON(http.StatusCreated, gin.H{"name": input.Name})
}
func main() {
db, err := sql.Open("postgres", "postgres://user:pass@localhost/dbname?sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
app := &App{
DB: db,
Logger: slog.Default(),
}
r := gin.Default()
r.GET("/users/:id", app.GetUser)
r.POST("/users", app.CreateUser)
r.Run(":8080")
}

Bağımlılıkları istek context’ine enjekte etmek için ara katman kullanın. Birçok işleyicinin aynı bağımlılığa ihtiyaç duyduğu durumlarda faydalıdır:

func DatabaseMiddleware(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("db", db)
c.Next()
}
}
func GetUser(c *gin.Context) {
db := c.MustGet("db").(*sql.DB)
// Use db...
}
func main() {
db, _ := sql.Open("postgres", "postgres://user:pass@localhost/dbname?sslmode=disable")
r := gin.Default()
r.Use(DatabaseMiddleware(db))
r.GET("/users/:id", GetUser)
r.Run(":8080")
}
KalıpTür güvenliğiTest edilebilirlikEn uygun
ClosureDerleme zamanıMock’lamak kolayKüçük uygulamalar, az bağımlılık
StructDerleme zamanıMock’lamak kolayOrta-büyük uygulamalar
Ara katmanÇalışma zamanıOrtaÇapraz kesen konular, paylaşılan durum

Tüm kalıplar testi kolaylaştırır — test çiftlerini enjekte edin:

func TestGetUser(t *testing.T) {
// Set up test database or mock
db := setupTestDB(t)
router := gin.New()
router.GET("/users/:id", GetUserHandler(db)) // closure pattern
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/1", nil)
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}