Meydan okuma 4 - Database
- Go kursu
- İleri seviye
- Ders 68/68
- 8 dk
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.
Başlangıç kodu
package main
import (
"errors"
"fmt"
)
func main() {
// buraya yaz
}Örnek çözüm
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)
}Beklenen çıktı
[exec exec commit] [exec exec rollback]
Soru ve cevaplar
Dersin sorularını cevaplarıyla birlikte inceleyebilirsin. Kendini denemek istersen dersi interaktif olarak çöz.
1 Tutar negatifse en doğru davranış hangisidir?
- Transaction'a hiç dokunmadan hata dönmek Doğru cevap
- Önce çekmeyi yapıp sonra kontrol etmek
- Tutarın mutlak değerini kullanmak
- Commit edip uyarı loglamak
Açıklama: Doğrulama işlemden önce yapılır; geçersiz girdide hiçbir veritabanı işlemi başlamamalıdır.
2 Hatada geri al
if err := tx.Exec("UPDATE hesap SET bakiye = bakiye - ? WHERE id = ?", tutar, kimden); err != nil {
tx.____()
return err
}
if err := tx.Exec("UPDATE hesap SET bakiye = bakiye - ? WHERE id = ?", tutar, kimden); err != nil {
tx.Rollback()
return err
}
Açıklama: Hata durumunda Rollback, transaction içindeki tüm değişiklikleri geri alır.
3 Durum ve sonuç
| Kavram | Anlamı |
|---|---|
Tutar ≤ 0 |
Hata, Tx'e dokunma |
Exec hatası |
Rollback |
Sorgu parametresi |
? yer tutucusu |
İkisi de başarılı |
Commit |