Go
Meydan okuma 4 - Database
Transaction ve prepared statement ile güvenli veri yazarsın.
Meydan okuma: güvenli transfer
Bir bankacılık servisinde para transferini yöneten fonksiyonu yazacaksın. Gerçek veritabanı yerine şu arayüzü kullan:
type Tx interface {
Exec(sorgu string, args ...any) error
Commit() error
Rollback() error
}
Kurallar: tutar pozitif olmalı; çekme ve yatırma aynı transaction'da yapılmalı; herhangi bir adım hata verirse Rollback çağrılmalı; sorgular yer tutucu kullanmalı.
Sahte Tx ile test
Arayüz sayesinde sahte bir Tx yazıp hangi çağrıların yapıldığını kaydedebilirsin:
type sahteTx struct {
log []string
hataAt int // kaçıncı Exec hata versin (0 = hiç)
}
func (t *sahteTx) Exec(q string, a ...any) error {
t.log = append(t.log, "exec")
if len(t.log) == t.hataAt { return errors.New("db hatası") }
return nil
}
Testte log'un [exec exec commit] ya da [exec rollback] olduğunu doğrularsın.
Uygulama transfer fonksiyonu
transfer(tx Tx, kimden, kime, tutar int) error fonksiyonunu kurallara göre yaz. Sahte Tx ile iki senaryo çalıştır: hatasız ve ikinci Exec'te hata. İki senaryonun log içeriğini ayrı satırlarda yazdır.
Örnek çözümü göster
package main
import (
"errors"
"fmt"
)
type Tx interface {
Exec(sorgu string, args ...any) error
Commit() error
Rollback() error
}
type sahteTx struct {
log []string
hataAt int
}
func (t *sahteTx) Exec(q string, a ...any) error {
t.log = append(t.log, "exec")
if len(t.log) == t.hataAt {
return errors.New("db hatası")
}
return nil
}
func (t *sahteTx) Commit() error { t.log = append(t.log, "commit"); return nil }
func (t *sahteTx) Rollback() error { t.log = append(t.log, "rollback"); return nil }
func transfer(tx Tx, kimden, kime, tutar int) error {
if tutar <= 0 {
return errors.New("geçersiz tutar")
}
if err := tx.Exec("UPDATE hesap SET bakiye = bakiye - ? WHERE id = ?", tutar, kimden); err != nil {
tx.Rollback()
return err
}
if err := tx.Exec("UPDATE hesap SET bakiye = bakiye + ? WHERE id = ?", tutar, kime); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
func main() {
a := &sahteTx{}
transfer(a, 1, 2, 100)
b := &sahteTx{hataAt: 2}
transfer(b, 1, 2, 100)
fmt.Println(a.log)
fmt.Println(b.log)
}
Kendini test et
-
Tutar negatifse en doğru davranış hangisidir?
- Transaction'a hiç dokunmadan hata dönmek
- Önce çekmeyi yapıp sonra kontrol etmek
- Tutarın mutlak değerini kullanmak
- Commit edip uyarı loglamak
Cevabı göster
Transaction'a hiç dokunmadan hata dönmek — Doğrulama işlemden önce yapılır; geçersiz girdide hiçbir veritabanı işlemi başlamamalıdır.
-
Hatada geri al
if err := tx.Exec("UPDATE hesap SET bakiye = bakiye - ? WHERE id = ?", tutar, kimden); err != nil {
tx.____()
return err
}
Cevabı göster
if err := tx.Exec("UPDATE hesap SET bakiye = bakiye - ? WHERE id = ?", tutar, kimden); err != nil {
tx.Rollback()
return err
}
-
Durum ve sonuç
- Tutar ≤ 0
- Hata, Tx'e dokunma
- Exec hatası
- Rollback
- Sorgu parametresi
- ? yer tutucusu
- İkisi de başarılı
- Commit
Sıradaki ders: İleri seviye bölüm sınavı