OfficeFighters’ın veritabanı hesap bilgileri kadar karakter sürümlerini, varlık ilişkilerini, maç kayıtlarını ve e-posta kuyruğunu da taşıyor. Bu modelde iki kişinin aynı taslağı düzenlemesi veya oturum yenilemeyle çıkışın çakışması sıradan birer veri problemi. DB-first yaklaşım ve AtomicWrite yardımcısı, şemanın nerede tanımlandığıyla eşzamanlı kararların nasıl kalıcılaştırıldığını birlikte açıklıyor.
SQL betikleri şemanın başlangıç noktası
Projede database dizinindeki numaralı SQL betikleri uygulanıyor ve schema_versions kayıtlarında SHA-256 bilgisiyle izleniyor. EF Core modelleri daha sonra scaffold.ps1 aracılığıyla veritabanından üretiliyor. Böylece tablolar, kısıtlar ve indeksler önce SQL incelemesinin konusu; uygulama sınıfları bunun istemci modeli hâline geliyor.
Microsoft’un reverse engineering belgesi de DbContext ve entity sınıflarının mevcut şemadan üretildiğini açıklıyor. Bu projedeki önemli iş disiplini, üretilmiş dosyaya kalıcı iş kuralı yerleştirmemek. Yeniden üretim sırasında kaybolabilecek özel davranışın servis, partial veya ayrı yapılandırma sınırında bulunması gerekiyor.
Supabase bağlantısında uygulama ve yönetim işleri
Geliştirme veritabanı Supabase PostgreSQL. Proje ayar akışı uygulama bağlantısını şema kurma ve scaffold gibi yönetim işlerinden ayırıyor. README’de IPv4 erişim için Session pooler seçimi ve uygulama havuzunun sınırlanması belgelenmiş. Burada operasyonel değerleri koda sabitlemek yerine yalnız bağlantı rolünü anlamak önemli: aynı bağlantı sayısı, aynı anda çalışabilen sorgu sayısının sınırsız olduğu anlamına gelmez.
Sorgu sonucunu işin ihtiyacı kadar taşımak
AssetService listelemesinde AsNoTracking kullanılıyor, filtreler sorguya uygulanıyor ve yalnız DTO için gereken alanlar seçiliyor. Sayfa boyutu yüzle sınırlandırılıyor. Böylece atlasın baytlarını veya bütün sürüm geçmişini varlık listesinin her satırına taşımak gerekmiyor. Count sorgusu ile sayfa sorgusunun farklı zamanlarda çalışabileceği de unutulmamalı; eşzamanlı değişimde toplam ile içerik anlık olarak farklı görünebilir.
Bu, her ekran için tek sorgu yazma hedefinden daha somut bir yaklaşım: liste hafif kalır, ayrıntı ekranı gerektiğinde sürümleri ve kullanımları ayrıca ister. İzlemede sorgu sayısı kadar dönen alanlar ve satırlar da ölçülmeli.
Revision kullanıcı düzenlemelerinin çakışmasını gösteriyor
Modeldeki revision alanı, beklenen sürümle yapılan değişikliğin hâlâ geçerli olup olmadığını ayırt etmek için kullanılıyor. Aşağıdaki SQL ilkeyi gösteren öğretici eşdeğer; gerçek uygulama yazımını birebir kopyalamıyor.
-- Illustrative SQL equivalent of a revision-checked update.
-- :id and :expected_revision are application parameters.
UPDATE fighters
SET revision = revision + 1
WHERE id = :id AND revision = :expected_revision;
-- Zero affected rows means the expected version was not updated.Güncellenen satır yoksa eski formdaki kullanıcının verisini otomatik olarak en yeni kayıt üzerine yazmak doğru olmayabilir. Kullanıcıya çakışma gösterip mevcut düzenlemeyi korumak gerekir. Revision kontrolü tek satırın beklenen sürümünü korurken, birden çok satırdan türetilen kararın bütünlüğü ayrıca ele alınmalı.
Oku–karar ver–yaz akışı tek işlemde
AtomicWrite bu tür kararları Serializable transaction içine alıyor. Uygulamanın yürütme stratejisi transaction’ı da kapsıyor; tekrar gereken durumda işlem baştan veri okuyarak çalışabiliyor. Aşağıdaki kısaltılmış kod bu sınırı gösteriyor.
// AtomicWrite.RunAsync: abbreviated transaction boundary.
return db.Database.CreateExecutionStrategy().ExecuteAsync(
async () => {
db.ChangeTracker.Clear();
await using var tx = await db.Database.BeginTransactionAsync(
IsolationLevel.Serializable, ct);
var result = await work(ct);
await tx.CommitAsync(ct);
return result;
});ChangeTracker her denemenin başında temizleniyor. İşin içinde daha önce okunmuş bir entity’ye veya dışarıdaki değişkenin eski değerine güvenmek, yeniden denemenin amacını bozabilir. İşin ihtiyacı olan veriyi yeniden okuması ve aynı mantıksal niyeti tekrar uygulayabilmesi gerekiyor.
İş sonucu ile commit kararı ayrı tasarlanıyor
RunAsync sonuç başarısız olsa da işlemi commit edebiliyor; token ailesi iptali gibi güvenlik değişiklikleri bu davranışa ihtiyaç duyuyor. RunOrRollbackAsync ise yalnız başarılı ServiceResult için commit ediyor. Birden fazla yazımın ortasında çakışma veya doğrulama hatası olduğunda önceki adımların yarım kalıcılaşmasını önlüyor.
Hangi yardımcıyı seçtiğimiz bu yüzden stil tercihi değil. Örneğin önce eski ilişkileri silip sonra yeni ilişkileri ekleyen akışta ikinci adım reddedilirse ilk silmenin de geri alınması gerekir. Buna karşılık kötü token kullanımını kaydeden bir reddin iptal değişikliğini koruması gerekebilir.
Tekrar edilen transaction dış yan etkiyi tekrarlamamalı
Serializable çakışma veya deadlock nedeniyle iş yeniden çalışabilir. SMTP gönderimi ya da S3 yüklemesini bu tekrar bloğunun içine gelişigüzel koymak, veritabanındaki rollback’in geri alamadığı dış etkiyi iki kez üretebilir. Kaynaktaki e-posta outbox ayrımı, niyeti ilişkisel işlemde tutup gönderimi daha sonra yapmanın somut örneği.
Kontrolde aynı revision ile iki güncelleme, refresh ile logout yarışı, başarısız ikinci adımda ilk yazımın geri alınması ve tekrar denemede eski tracked entity taşınmaması değerli. Bu yazı mevcut kodun işlem sınırlarını anlatıyor; OfficeFighters veritabanında bu inceleme sırasında şema değişikliği veya canlı veri denemesi yapılmadı.
Kaynaklar / References
Microsoft Learn — EF Core reverse engineering · PostgreSQL — transaction isolation
OfficeFighters serisinin diğer yazıları
- OfficeFighters — Supabase Storage ve S3 ile oyun varlıklarını yönetmek
- OfficeFighters — SignalR ve WebSocket ile gerçek zamanlı oyun iletişimi
- OfficeFighters — Phaser, sabit adımlı simülasyon ve snapshot interpolasyonu
- OfficeFighters — MailKit ve outbox ile dayanıklı e-posta gönderimi
- OfficeFighters — JWT, refresh token rotasyonu ve çok sekmeli oturum yönetimi