CodeBlanks Logo Icon CodeBlanks

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

main.go
package main

import (
    "errors"
    "fmt"
)

func main() {
    // buraya yaz
}

Örnek çözüm

main.go
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

Soru
if err := tx.Exec("UPDATE hesap SET bakiye = bakiye - ? WHERE id = ?", tutar, kimden); err != nil {
    tx.____()
    return err
}
Cevap
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

İleri seviye bölümündeki tüm dersler