1. İşlemler ve Veri Uyumluluğu Tanıtımı

Bir işlem, veritabanı yönetim sisteminin yürütme sürecinde, bir dizi işlemden oluşan mantıksal bir birimdir. Bu işlemlin tamamı başarılı olur veya hiçbiri başarılı olmaz ve bunlar bölünemez bir bütün olarak kabul edilir. Bir işlemin temel özellikleri ACID olarak özetlenebilir:

  • Atomiklik: Bir işlemin tümü ya tamamen tamamlanır ya da hiç tamamlanmaz; kısmi tamamlama mümkün değildir.
  • Uyum: Bir işlem, veritabanını tutarlı bir durumdan başka bir tutarlı duruma geçirmelidir.
  • İzolasyon: Bir işlemin yürütülmesi, diğer işlemlerin müdahalesinden izole edilmelidir ve birden çok eşzamanlı işlem arasındaki veri izole edilmelidir.
  • Kalıcılık: Bir işlem bir kere başarılı bir şekilde gerçekleştirildiğinde, onun tarafından yapılan değişiklikler veritabanında kalıcı olacaktır.

Veri uyumluluğu, bir dizi işlemden sonra veritabanında doğru ve geçerli bir veri durumunun korunması anlamına gelir. Eşzamanlı erişim veya sistem hatalarının karıştığı senaryolarda, veri uyumluluğu özellikle önemli olup, işlemler hatalar veya çatışmalar durumunda bile veri uyumluluğunun bozulmamasını sağlar.

2. ent Framework'ünün Genel Bakışı

ent, Go programlama dilinde kod üretimi aracılığıyla veritabanlarını işletmek için tip güvenli bir API sağlayan bir varlık çerçevesidir. Bu, veritabanı işlemlerini daha sezgisel ve güvenli hale getirir ve SQL enjeksiyonu gibi güvenlik sorunlarını önlemeye olanak tanır. İşlem işlemleri açısından, ent çerçevesi, karmaşık işlem işlemlerini kısa kodlarla gerçekleştirmeye ve işlemlerin ACID özelliklerinin karşılanmasını sağlamaya güçlü destek sağlar.

3. Bir İşlem Başlatma

3.1 ent İçinde Bir İşlem Nasıl Başlatılır

ent çerçevesinde, belirli bir bağlam içinde client.Tx yöntemi kullanılarak yeni bir işlem kolayca başlatılabilir ve Tx işlem nesnesi döndürülür. Kod örneği aşağıdaki gibidir:

tx, err := client.Tx(ctx)
if err != nil {
    // İşlem başlatılırken hataların işlenmesi
    return fmt.Errorf("İşlem başlatılırken hata oluştu: %w", err)
}
// tx ile sonraki işlemleri gerçekleştir...

3.2 Bir İşlem İçinde İşlemleri Gerçekleştirmek

Tx nesnesi başarıyla oluşturulduktan sonra, veritabanı işlemleri gerçekleştirmek için kullanılabilir. Tx nesnesi üzerinde gerçekleştirilen tüm oluşturma, silme, güncelleme ve sorgu işlemleri işlemi bir parçası haline gelir. Aşağıdaki örnek, bir dizi işlemi göstermektedir:

hub, err := tx.Group.
    Create().
    SetName("Github").
    Save(ctx)
if err != nil {
    // Bir hata oluşursa, işlemi geri al
    return rollback(tx, fmt.Errorf("Grup oluşturma başarısız: %w", err))
}
// İşlemlerin ardından buraya ek işlemler eklenebilir...
// İşlemi onayla
tx.Commit()

4. Hata İşleme ve İşlemlerde Geri Alma

4.1 Hata İşleminin Önemi

Veritabanı ile çalışırken, ağ sorunları, veri çakışmaları veya kısıtlama ihlalleri gibi çeşitli hatalar her zaman ortaya çıkabilir. Bu hataları uygun bir şekilde işlemek, veri uyumluluğunu sağlamak için oldukça önemlidir. Bir işlemde bir işlemin başarısız olması durumunda, veritabanının uyumluluğunu tehlikeye atabilecek kısmi bir şekilde tamamlanmış işlemlerin bırakılmaması için işlem geri alınmalıdır.

4.2 Geri Alma Nasıl Uygulanır

ent çerçevesinde, Tx.Rollback() yöntemi kullanılarak tüm işlem geri alınabilir. Genellikle, geri alma ve hataları işlemek için aşağıdaki gibi bir rollback yardımcı fonksiyon tanımlanır:

func rollback(tx *ent.Tx, err error) error {
    if rerr := tx.Rollback(); rerr != nil {
        // Geri alma başarısız olursa, orijinal hatayı ve geri alma hatasını birlikte döndür
        err = fmt.Errorf("%w: İşlem geri alınırken hata oluştu: %v", err, rerr)
    }
    return err
}

Bu rollback fonksiyonu ile hataları güvenli bir şekilde işleyip işlem geri alınabilir. Bu, bir hata durumunda bile veritabanının uyumluluğu üzerinde olumsuz bir etkisi olmayacağını sağlar.

5. İşlemci Müşteri Kullanımı

Pratik uygulamalarda, non-transactional kodları hızla transactional koda dönüştürmemiz gereken senaryolar olabilir. Bu tür durumlar için, kodu sorunsuz bir şekilde taşımak için bir işlemci müşterisi kullanabiliriz. Aşağıda, mevcut non-transactional müşteri kodunu desteklemek için nasıl dönüştüreceğimize dair bir örnek bulunmaktadır:

// Bu örnekte, orijinal Gen işlevini bir işlem içine kapsülleriz.
func WrapGen(ctx context.Context, client *ent.Client) error {
    // İlk olarak, bir işlem oluşturun
    tx, err := client.Tx(ctx)
    if err != nil {
        return err
    }
    // İşlemci müşterisini işlemden alın
    txClient := tx.Client()
    // Orijinal Gen kodunu değiştirmeden işlemci müşterisi kullanarak Gen işlevini çalıştırın
    if err := Gen(ctx, txClient); err != nil {
        // Bir hata oluşursa, işlemi geri alın
        return rollback(tx, err)
    }
    // Başarılıysa, işlemi tamamlayın
    return tx.Commit()
}

Yukarıdaki kodda, işlemci müşterisi tx.Client() kullanılarak, orijinal Gen işlevinin işlem garantisi altında çalıştırılmasına izin verilmiştir. Bu yaklaşım sayesinde, mevcut non-transactional kodu orijinal mantığa minimum etki ile kolayca transactional koda dönüştürebiliriz.

6. İşlemler için En İyi Uygulamalar

6.1 Geri Çağrı İşlevleri ile İşlemlerin Yönetilmesi

Kod mantığımız karmaşık hale geldiğinde ve birden fazla veritabanı işlemini içerdiğinde, bu işlemlerin merkezi bir şekilde bir işlem içinde yönetilmesi oldukça önemli hale gelir. Aşağıda, geri çağrı işlevleri aracılığıyla işlemlerin yönetildiği bir örnek bulunmaktadır:

func WithTx(ctx context.Context, client *ent.Client, fn func(tx *ent.Tx) error) error {
    tx, err := client.Tx(ctx)
    if err != nil {
        return err
    }
    // Potansiyel panik senaryolarını ele almak için defer ve recover kullanın
    defer func() {
        if v := recover(); v != nil {
            tx.Rollback()
            panic(v)
        }
    }()
    // Sağlanan geri çağrı işlevini çağırarak iş mantığını yürütün
    if err := fn(tx); err != nil {
        // Bir hata durumunda, işlemi geri alın
        if rerr := tx.Rollback(); rerr != nil {
            err = fmt.Errorf("%w: işlemi geri alma: %v", err, rerr)
        }
        return err
    }
    // İş mantığı hatasızsa, işlemi tamamlayın
    return tx.Commit()
}

WithTx işlevini iş mantığını kapsaması için kullanarak, hata veya istisna durumları olsa bile işlemin doğru bir şekilde işlendiğini sağlayabiliriz (hem tamamlanmış hem de geri alınmış olabilir).

6.2 İşlem Kancalarının Kullanımı

Şema kancaları ve çalışma zamanı kancalarıyla benzer şekilde, aktif bir işlem (Tx) içinde Tx.Commit veya Tx.Rollback ile tetiklenecek kancaları da kaydedebiliriz:

func Do(ctx context.Context, client *ent.Client) error {
    tx, err := client.Tx(ctx)
    if err != nil {
        return err
    }
    tx.OnCommit(func(next ent.Committer) ent.Committer {
        return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error {
            // İşlemi taahhüt etmeden önceki mantık
            err := next.Commit(ctx, tx)
            // İşlemi taahhüt ettikten sonraki mantık
            return err
        })
    })
    tx.OnRollback(func(next ent.Rollbacker) ent.Rollbacker {
        return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error {
            // İşlemi geri almadan önceki mantık
            err := next.Rollback(ctx, tx)
            // İşlemi geri aldıktan sonraki mantık
            return err
        })
    })
    // Diğer iş mantığını yürüt
    //
    // 
    //
    return err
}

İşlemi taahhüt etme ve geri alma sırasında kancalar ekleyerek, loglama veya kaynak temizleme gibi ek mantıkları ele alabiliriz.

7. Farklı İşlem İzolasyon Seviyelerini Anlama

Veritabanı sistemlerinde işlem izolasyon seviyesinin ayarlanması, çeşitli eşzamanlılık sorunlarını (kirli okumalar, tekrarlanmayan okumalar ve hayalet okumalar gibi) önlemek için önemlidir. İşte bazı standart izolasyon seviyeleri ve onları ent framework'ünde nasıl ayarlayacağımız:

  • OKUMA YAPILMAMIŞ: En düşük seviye, henüz işlenmemiş verilerin okunmasına izin verir; bu durum kirli okumalara, tekrarlanmayan okumalara ve hayalet okumalara yol açabilir.
  • OKUMA YAPILMIŞ: Verilerin okunmasına ve işlenmesine izin verir, kirli okumaları engeller, ancak tekrarlanmayan okumalar ve hayalet okumalar hala meydana gelebilir.
  • TEKRARLANABİLİR OKUMA: Aynı işlem içinde kez defa aynı veriyi okumanın tutarlı sonuçlar üretmesini sağlar, tekrarlanmayan okumaları engeller, ancak hayalet okumalar hala meydana gelebilir.
  • SERİLEŞTİRİLEBİLİR: En katı izolasyon seviyesi, ilgili verileri kilitleyerek kirli okumaları, tekrarlanmayan okumaları ve hayalet okumaları engellemeye çalışır.

ent içinde, veritabanı sürücüsünün işlem izolasyon seviyesini ayarlama desteği varsa, aşağıdaki gibi ayarlanabilir:

// İşlem izolasyon seviyesini tekrarlanabilir okuma olarak ayarla
tx, err := client.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})

İşlem izolasyon seviyelerini anlamak ve veritabanlarında uygulamalarını anlamak, veri tutarlılığını ve sistem stabilitesini sağlamak için hayati önem taşır. Geliştiriciler, belirli uygulama gereksinimlerine dayanarak uygun bir izolasyon seviyesi seçmeli ve veri güvenliğini sağlama ve performansı optimize etme en iyi uygulama prensibini benimsemelidir.