Physical Address

304 North Cardinal St.
Dorchester Center, MA 02124

Firebase Firestore

Yazı, temel Firestore işlemleri, gelişmiş sorgular, gerçek zamanlı güncellemeler, çevrimdışı destek, güvenlik kuralları, veri yapısı ve performans optimizasyonuna kadar tüm detayları kapsar. Firestore’un sağladığı çevrimdışı özelliklerin etkinleştirilmesi, güvenlik kurallarıyla kullanıcı bazlı erişim sağlanması, veri indeksleme ve istemci tarafında filtreleme gibi performans optimizasyon tekniklerine yer verilir.

Temel Firestore İşlemleri

Firebase Firestore, Flutter ile kullanıcıların bulut tabanlı bir veritabanında verilerini depolamalarına, güncellemelerine, sorgulamalarına ve gerçek zamanlı olarak senkronize etmelerine olanak tanır. Firestore, özellikle mobil uygulamalar için optimize edilmiş bir NoSQL veritabanıdır ve belgeler (document) ve koleksiyonlar (collection) şeklinde organize edilmiştir.

1. Kurulum

Firestore’u kullanmaya başlamadan önce Flutter projenizde Firebase ile bağlantı kurmanız gerekir:

  • Firebase Console’dan bir proje oluşturun ve Firestore’u etkinleştirin.
  • Proje ayarlarında Firebase SDK yapılandırmasını bulun.
  • Firebase ile Flutter projesini entegre edin:
  flutter pub add firebase_core
  flutter pub add cloud_firestore
  • Firestore’a bağlantıyı başlatmak için, main.dart dosyasına Firebase başlatma kodunu ekleyin:
  import 'package:firebase_core/firebase_core.dart';

  void main() async {
    WidgetsFlutterBinding.ensureInitialized();
    await Firebase.initializeApp();
    runApp(MyApp());
  }
2. Temel Yapı: Koleksiyon ve Belgeler

Firestore, verileri koleksiyonlar ve belgeler halinde depolar:

  • Koleksiyon: Birden fazla belge içerebilen bir gruptur.
  • Belge: Veriyi depolayan bir nesnedir ve JSON benzeri bir yapıya sahiptir.

Örneğin, users koleksiyonu altında userId adında belgeler oluşturabilirsiniz.

3. Veri Ekleme

Bir koleksiyona veri eklemek için add() veya set() metodunu kullanabilirsiniz.

  • add(): Firestore otomatik olarak bir belge ID’si oluşturur.
  • set(): Belirli bir ID ile belge oluşturmanızı sağlar.
import 'package:cloud_firestore/cloud_firestore.dart';

// Veri eklemek için bir örnek
Future<void> addUser() {
  // `users` koleksiyonuna veri ekliyoruz
  return FirebaseFirestore.instance.collection('users').add({
    'name': 'Eray',
    'age': 25,
    'isVerified': true
  });
}

// Belirli bir ID ile veri eklemek
Future<void> setUser(String userId) {
  return FirebaseFirestore.instance
      .collection('users')
      .doc(userId)
      .set({
        'name': 'Eray',
        'age': 25,
        'isVerified': true
      });
}
4. Veri Okuma

Firestore’dan veri okuma, gerçek zamanlı ve tek seferlik okuma olarak iki şekilde yapılabilir.

Tek Seferlik Okuma

Tek seferlik veri okumak için get() metodunu kullanabilirsiniz:

Future<void> getUser(String userId) async {
  DocumentSnapshot document = await FirebaseFirestore.instance
      .collection('users')
      .doc(userId)
      .get();

  if (document.exists) {
    print("Kullanıcı Adı: ${document['name']}");
  }
}
Gerçek Zamanlı Veri Okuma

Gerçek zamanlı güncellemeleri izlemek için snapshots() metodunu kullanabilirsiniz:

FirebaseFirestore.instance.collection('users').snapshots().listen((snapshot) {
  for (var document in snapshot.docs) {
    print(document.data());
  }
});
5. Veri Güncelleme

Bir belgeyi güncellemek için update() metodunu kullanabilirsiniz:

Future<void> updateUser(String userId) {
  return FirebaseFirestore.instance
      .collection('users')
      .doc(userId)
      .update({
        'age': 26,
        'isVerified': false,
      });
}

Not: update() metodu sadece mevcut alanları günceller. Eğer alan yoksa hata verir.

6. Veri Silme

Bir belgeyi veya alanı silmek için delete() metodunu kullanabilirsiniz:

// Belge silme
Future<void> deleteUser(String userId) {
  return FirebaseFirestore.instance
      .collection('users')
      .doc(userId)
      .delete();
}

// Alan silme
Future<void> deleteField(String userId) {
  return FirebaseFirestore.instance
      .collection('users')
      .doc(userId)
      .update({
        'age': FieldValue.delete(),
      });
}
7. Veri Sorgulama

Firestore’da veriyi filtrelemek için where() metodunu kullanabilirsiniz:

Future<void> getVerifiedUsers() async {
  QuerySnapshot querySnapshot = await FirebaseFirestore.instance
      .collection('users')
      .where('isVerified', isEqualTo: true)
      .get();

  for (var doc in querySnapshot.docs) {
    print(doc.data());
  }
}
8. Veri Sıralama

Veriyi belirli bir alana göre sıralamak için orderBy() metodunu kullanabilirsiniz:

Future<void> getUsersSortedByAge() async {
  QuerySnapshot querySnapshot = await FirebaseFirestore.instance
      .collection('users')
      .orderBy('age', descending: true)
      .get();

  for (var doc in querySnapshot.docs) {
    print(doc.data());
  }
}
9. Limit ve Offset

Büyük veri kümelerinde, limit ve offset kullanarak sorguyu sınırlayabilirsiniz:

Future<void> getLimitedUsers() async {
  QuerySnapshot querySnapshot = await FirebaseFirestore.instance
      .collection('users')
      .limit(5)
      .get();

  for (var doc in querySnapshot.docs) {
    print(doc.data());
  }
}
10. Gerçek Zamanlı Veri Güncellemeleri

Gerçek zamanlı veri güncellemeleri için snapshots() kullanarak sürekli izleme yapabilirsiniz:

FirebaseFirestore.instance
    .collection('users')
    .doc('userId')
    .snapshots()
    .listen((documentSnapshot) {
  print(documentSnapshot.data());
});
11. Transaction Kullanımı

Transaction, veri bütünlüğünü sağlamak için bir dizi işlemi bir arada yürütmeyi sağlar. Örneğin, bir kullanıcı bakiyesi güncellemesi yapmak için kullanabilirsiniz:

Future<void> runTransactionExample(String userId, double amount) async {
  FirebaseFirestore.instance.runTransaction((transaction) async {
    DocumentSnapshot snapshot = await transaction.get(
        FirebaseFirestore.instance.collection('users').doc(userId));

    if (!snapshot.exists) {
      throw Exception("Kullanıcı bulunamadı!");
    }

    double newBalance = snapshot['balance'] + amount;
    transaction.update(snapshot.reference, {'balance': newBalance});
  });
}

Gelişmiş Firestore Sorguları

1. Filtreleme (Filtering)

Equality: Belirli bir alandaki değerin tam eşleşmesini sorgular.

dart FirebaseFirestore.instance .collection('users') .where('age', isEqualTo: 25) .get();

Büyüktür, Küçüktür Filtreleri:

isGreaterThan, isLessThan, isGreaterThanOrEqualTo, isLessThanOrEqualTo gibi operatörler kullanılır.

dart FirebaseFirestore.instance .collection('products') .where('price', isGreaterThan: 100) .get();
2. Çoklu Filtreler (Composite Filters)

Aynı anda birden fazla filtre kullanarak sorgular oluşturabilirsiniz. Ancak, Firestore aynı alanda isGreaterThan ve isLessThan gibi farklı operatörlerle filtrelemeyi aynı anda destekler.

AND Filtreleme:

dart FirebaseFirestore.instance .collection('items') .where('price', isGreaterThan: 100) .where('stock', isGreaterThan: 10) .get();
  • OR Filtreleme: Firestore, or operatörünü doğrudan desteklemez, ancak iki ayrı sorgunun sonuçlarını birleştirerek elde edilebilir.
3. Dizi Filtreleme (Array Filtering)

Belirli bir değerin bir dizi içinde olup olmadığını sorgulamak için array-contains kullanılır.

dart FirebaseFirestore.instance .collection('groups') .where('members', arrayContains: 'user123') .get();
  • Birden fazla değeri kontrol etmek için array-contains-any operatörünü kullanabilirsiniz.
dart FirebaseFirestore.instance .collection('groups') .where('members', arrayContainsAny: ['user123', 'user456']) .get();
4. Sıralama (Ordering)

Sorguları belirli alanlara göre sıralamak için orderBy kullanılır.

dart FirebaseFirestore.instance .collection('users') .orderBy('age', descending: true) .get();
5. Sınırlama (Limiting)

Sorgudan dönecek belge sayısını sınırlamak için limit kullanılır.

dart FirebaseFirestore.instance .collection('posts') .orderBy('date', descending: true) .limit(10) .get();
6. Sayfalama (Pagination)

Firestore, verileri sayfalandırmak için startAt, startAfter, endAt, ve endBefore yöntemlerini sağlar. Bu, büyük veri setleriyle çalışırken yararlıdır.

dart FirebaseFirestore.instance .collection('posts') .orderBy('date') .startAfter([lastDocument.data()['date']]) .limit(10) .get();
7. Toplu Sorgular (Compound Queries)
  • Firestore’da tek bir sorguda birden fazla koşul ve filtre kullanılabilir.
   FirebaseFirestore.instance
       .collection('products')
       .where('category', isEqualTo: 'electronics')
       .where('price', isLessThan: 300)
       .orderBy('rating', descending: true)
       .limit(5)
       .get();
8. Gruplandırılmış Koleksiyon Sorguları (Collection Group Queries)
  • Alt koleksiyonlar arasındaki verileri almak için collectionGroup sorgusu yapılabilir.
FirebaseFirestore.instance
       .collectionGroup('comments')
       .where('postId', isEqualTo: 'post123')
       .get();

Gerçek Zamanlı Güncellemeler

Firebase Firestore’da Gerçek Zamanlı Güncellemeler, kullanıcıya veriler üzerinde bir değişiklik olduğunda bunu anında yansıtma olanağı sağlar.

1. Streams ile Dinleme

Firestore’da gerçek zamanlı güncellemeler sağlamak için snapshots() metodu kullanılır. Bu yöntemle, belirli bir koleksiyon veya belge üzerinde bir değişiklik olduğunda hemen haber verilir, böylece veriler anlık olarak güncellenir.

Koleksiyon Dinleme: Tüm koleksiyonu dinler ve bir belge eklenip silindiğinde veya güncellendiğinde güncellemeleri sağlar.

FirebaseFirestore.instance
    .collection('users')
    .snapshots()
    .listen((querySnapshot) {
        querySnapshot.docs.forEach((doc) {
            print(doc['name']);
        });
    });

Belge Dinleme: Sadece belirli bir belgeye ait değişiklikleri dinlemek için kullanılır.

FirebaseFirestore.instance
    .collection('users')
    .doc('userID')
    .snapshots()
    .listen((documentSnapshot) {
        print(documentSnapshot['name']);
    });

Flutter’ın StreamBuilder widget’ı, bir stream’den veri çekip gerçek zamanlı olarak UI’a yansıtmak için en uygun yöntemlerden biridir.

  • StreamBuilder, bir stream alır ve veri her güncellendiğinde builder fonksiyonunu çağırır. Bu sayede verilerin değişiklikleri anında UI’a yansıtılır.
StreamBuilder(
    stream: FirebaseFirestore.instance.collection('users').snapshots(),
    builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (snapshot.hasError) {
            return Text('Bir hata oluştu: ${snapshot.error}');
        }
        if (snapshot.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
        }
        return ListView(
            children: snapshot.data!.docs.map((DocumentSnapshot document) {
                Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
                return ListTile(
                    title: Text(data['name']),
                    subtitle: Text(data['email']),
                );
            }).toList(),
        );
    },
);
3. Gerçek Zamanlı Güncellemelerde Performans İpuçları

Gerçek zamanlı güncellemeler için dikkat edilmesi gereken bazı performans detayları vardır:

  • Dinleme Alanını Daraltın: Tüm koleksiyon yerine sadece ihtiyacınız olan veriyi dinlemek için filtreler kullanın. Örneğin, yalnızca belirli bir kullanıcının verilerini dinlemek performansı artırır.
  • Bağlantıyı Gerektiğinde Kapatın: StreamBuilder kullanırken gereksiz yere fazla sayıda stream açmamaya dikkat edin. Örneğin, ekran kapandığında bağlantıyı kapatmak uygulamanın kaynaklarını daha iyi kullanmasını sağlar.
4. Kullanıcı Durumuna Göre Güncellemeler

Gerçek zamanlı güncellemeleri belirli durumlara göre (örn. kullanıcı çevrimiçi veya aktifse) yönetmek, verimlilik açısından önemlidir. Örneğin, kullanıcı çevrimdışına çıktığında veya ekran değiştiğinde stream’in durdurulması, gereksiz veri trafiğini azaltabilir.

Offline Destek ve Önbellekleme

Firebase Firestore, çevrimdışı mod ve önbellekleme desteği ile çevrimdışı senkronizasyon ve veri yönetimini sağlar. Bu, Flutter uygulamaları için özellikle önemlidir, çünkü kullanıcıların internet bağlantısı olmadığında bile uygulamanın çalışmasını sağlar.

1. Offline Veritabanı Yönetimi

Firestore’un çevrimdışı desteği sayesinde, uygulamanız internet bağlantısı olmadan da çalışır. Bu özellik varsayılan olarak etkinleştirilmiştir ve kullanıcıların çevrimdışıyken yaptığı veri ekleme, güncelleme veya silme işlemleri çevrimiçi hale geldiklerinde otomatik olarak senkronize edilir.

  • Çevrimdışı Modu Etkinleştirme: Firestore, varsayılan olarak çevrimdışı veri depolamayı etkinleştirir. Ekstra bir işlem yapmanıza gerek yoktur. Ancak bu özelliği devre dışı bırakmak istiyorsanız, FirebaseFirestore.instance.settings ayarlarını kullanarak çevrimdışı önbellekleme özelliğini kapatabilirsiniz:
  FirebaseFirestore.instance.settings = Settings(
    persistenceEnabled: false, // Çevrimdışı desteği kapatır
  );
  • Çevrimdışı Veri Senkronizasyonu: Firestore, çevrimdışı işlemleri (yeni veriler ekleme, mevcut verileri güncelleme veya silme) otomatik olarak sıraya alır ve cihaz çevrimiçi hale geldiğinde bu işlemleri Firestore sunucusuna iletir. Kullanıcı, çevrimdışıyken bile veri görüntüleyebilir ve güncelleme yapabilir.
  • Veri Tutarlılığı: Firestore, çevrimdışıyken yapılan işlemleri yerel bir veri kuyruğunda saklar ve bağlantı sağlandığında bu kuyruğu sunucu ile eşler. Bu, veri tutarlılığını korumanıza yardımcı olur.
2. Önbellek Temizleme ve Yönetme

Firestore, cihazda belirli bir miktar yer kaplayan bir önbellek yönetim sistemine sahiptir. Zamanla bu önbellek gereksiz büyüyebilir ve belirli bir sınırı aştığında eski veriler otomatik olarak temizlenir. Ancak, manuel temizlik işlemleri yapmak isterseniz, clearPersistence() metodunu kullanabilirsiniz.

  • clearPersistence() Kullanımı: Bu metod, Firestore’un yerel önbelleğini temizler. Uygulamayı yeniden başlatmak veya oturumdan çıkmak gibi işlemler öncesinde kullanılabilir.
  await FirebaseFirestore.instance.clearPersistence();
  • Önbellek Boyutunu Yönetme: Firestore, varsayılan olarak 100 MB’a kadar yerel veri depolayabilir. Ancak, Settings içerisinde cacheSizeBytes parametresi ile bu boyutu özelleştirebilirsiniz:
  FirebaseFirestore.instance.settings = Settings(
    cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED, // Sınırsız önbellek
  );
3. Çevrimdışı Destek Özeti
  1. Yerel Kopya: Tüm veriler, çevrimdışıyken kullanıcının erişebilmesi için cihazda saklanır.
  2. Otomatik Senkronizasyon: Çevrimdışıyken yapılan işlemler, çevrimiçi hale gelindiğinde senkronize edilir.
  3. Önbellek Yönetimi: clearPersistence() ile verilerinizi temizleyebilir, cacheSizeBytes ile önbellek boyutunu kontrol edebilirsiniz.
4. Senaryo Kullanımı Örneği

Diyelim ki, bir alışveriş uygulamanız var ve kullanıcılar çevrimdışıyken bile alışveriş listesi oluşturup düzenleyebiliyor. Firestore’un çevrimdışı desteği ile kullanıcılar bu listelere internet bağlantısı olmadan erişebilir, öğeler ekleyebilir veya silebilir. İnternete bağlandıklarında ise tüm veriler senkronize edilir.

Güvenlik Kuralları (Firestore Security Rules)

Firebase Firestore güvenlik kuralları, veritabanı erişim güvenliğini sağlamak için belirli kurallar tanımlamanıza olanak tanır. Bu kurallar, kullanıcıların veritabanında hangi verilere erişebileceğini ve bu verilerle neler yapabileceğini belirler. Firestore güvenlik kurallarını doğru yapılandırmak, veri güvenliğini sağlamak için kritik öneme sahiptir.

1. Temel Güvenlik Kuralları

Firestore güvenlik kuralları, veritabanınıza yapılan tüm okuma ve yazma işlemlerini kontrol etmek için kullanılır. Bu kurallar, allow anahtar kelimesi ile yapılandırılır ve read (okuma) veya write (yazma) işlemlerini izin vermek için kullanılır.

  • Kuralların Tanımlanması:
  service cloud.firestore {
    match /databases/{database}/documents {
      match /{document=**} {
        allow read, write: if true; // Herkese açık erişim sağlar (örnek olarak)
      }
    }
  }

Bu kural, veritabanındaki tüm belgelere okuma ve yazma izni verir. Ancak gerçek uygulamalarda herkese açık erişim önerilmez.

  • Önerilen Yapı: Her belgeye yalnızca belirli kullanıcıların veya rollerin erişim sağlaması için güvenlik kuralları daha kısıtlayıcı olacak şekilde tanımlanmalıdır.
2. Kimlik Doğrulamaya Dayalı Erişim

Kullanıcıların kimlik doğrulamasını temel alarak, veritabanına erişimlerini düzenlemek Firestore’un sağladığı güçlü güvenlik özelliklerinden biridir. Firestore güvenlik kurallarında, yalnızca kimliği doğrulanmış kullanıcıların belirli işlemleri yapmasına izin verebilirsiniz.

  • Örnek Kimlik Doğrulama Kuralı:
  service cloud.firestore {
    match /databases/{database}/documents {
      match /{document=**} {
        allow read, write: if request.auth != null;
      }
    }
  }

Bu örnekte, yalnızca kimliği doğrulanmış kullanıcıların (request.auth != null) okuma ve yazma işlemi yapmasına izin verilir. Bu kural, tüm veritabanı için geçerli olup kimliği doğrulanmamış kullanıcıların erişimini engeller.

3. Koşullu Güvenlik Kuralları

Firestore güvenlik kurallarında koşullar ekleyerek daha ayrıntılı ve dinamik erişim kontrolü sağlayabilirsiniz. Örneğin, her kullanıcının yalnızca kendi verisine erişmesine izin vermek için koşullu güvenlik kuralları oluşturabilirsiniz.

  • Kendi Verisine Erişim Kuralları: Firestore’da her kullanıcı, yalnızca kendisine ait verilere erişebilmelidir. Bunu sağlamak için kullanıcı kimliğini verilerle ilişkilendirebilir ve yalnızca belirli bir koşul sağlandığında verilere erişim izni verebilirsiniz.
  service cloud.firestore {
    match /databases/{database}/documents {
      // Kullanıcının yalnızca kendi verisine erişebilmesi için kurallar
      match /users/{userId} {
        allow read, write: if request.auth != null && request.auth.uid == userId;
      }
    }
  }

Bu örnekte, users koleksiyonundaki her bir belge userId ile tanımlanmıştır. Bu kural sayesinde, kimliği doğrulanmış her kullanıcı yalnızca userId değeri kendi kimlik doğrulama kimliği ile eşleştiğinde okuma ve yazma işlemi yapabilir.

4. Gelişmiş Koşullu Güvenlik Kuralları

Firestore güvenlik kurallarını daha da dinamik hale getirerek koşullar ekleyebilirsiniz. Örneğin, belirli bir alanın değeri veya belgenin içindeki diğer verilerle koşul belirleyebilirsiniz.

  • Rol Tabanlı Erişim Kontrolü: Örneğin, yalnızca yöneticilerin belirli verilere erişmesini sağlamak için bir role alanı kontrolü ekleyebilirsiniz:
  service cloud.firestore {
    match /databases/{database}/documents {
      match /adminData/{documentId} {
        allow read, write: if request.auth.token.role == 'admin';
      }
    }
  }

Bu kuralda, adminData koleksiyonundaki verilere yalnızca role değeri admin olan kullanıcıların erişmesi sağlanmıştır. role değeri Firebase Authentication kullanıcı token’ında saklanır.

  • Alan Kontrolü ile Koşul Belirleme: Belli bir alanın durumuna göre kullanıcıya okuma veya yazma izni vermek için kullanılır.
  service cloud.firestore {
    match /databases/{database}/documents {
      match /posts/{postId} {
        allow read: if resource.data.isPublished == true;
      }
    }
  }

Bu örnekte, posts koleksiyonundaki yalnızca isPublished alanı true olan belgelere okuma izni verilir. Bu, yayınlanmamış gönderilere kullanıcıların erişmesini engeller.

5. Firestore Güvenlik Kurallarını Test Etme

Güvenlik kurallarını test etmek, uygulamanızın güvenliğini sağlamak için önemlidir. Firestore, kurallarınızı Firebase konsolundaki “Rules Playground” bölümünde test etmenizi sağlar.

  1. Rules Playground Kullanımı: Firestore güvenlik kurallarınızı Firebase konsolundan test edebilir, simüle edilen bir kullanıcı ile kuralların doğru çalışıp çalışmadığını kontrol edebilirsiniz.
  2. Simülasyon ile Test Yapma: Kimlik doğrulaması yapılmış veya yapılmamış kullanıcılar için okuma veya yazma işlemlerini test ederek kuralların beklendiği gibi çalıştığından emin olun.
6. Güvenlik Kuralları Yazarken Dikkat Edilmesi Gerekenler
  • Varsayılan Erişim Kısıtlamaları: Kuralları yazarken, erişimi varsayılan olarak kısıtlayın ve yalnızca belirli kullanıcıların verilere erişimini açın.
  • Gizlilik İlkeleri: Kullanıcıların yalnızca kendileriyle ilgili verilere erişebildiğinden emin olun.
  • Güncelleme ve İzleme: Güvenlik kurallarınızı düzenli olarak gözden geçirip güncelleyin ve gerektiğinde test edin.

Veri Yapısı ve İlişkiler

Firebase Firestore’da verileri düzenlemek ve ilişkisel veri yapıları oluşturmak için etkili veri yapılandırma stratejileri kullanmak önemlidir. Firestore, ilişkisel verilerle çalışmaya uygun esnek bir yapı sunar. Veri yapısının planlanması ve ilişkilerin belirlenmesi, performans ve verimlilik açısından oldukça önemlidir.

1. Alt Koleksiyonlar

Firestore, belgeler ve koleksiyonlar arasında hiyerarşik yapılar oluşturmanıza izin verir. Bu özellik, bir belgenin altına başka bir koleksiyon eklemeyi mümkün kılar ve bu koleksiyonlar ile ilişkisel veri yapıları kurmanıza yardımcı olur.

  • Alt Koleksiyon Kullanımı: Bir belgenin içinde alt koleksiyon oluşturarak daha detaylı bir veri modeli oluşturabilirsiniz. Bu, özellikle her bir öğeye bağımlı veya ona ait verilerin tutulması gerektiğinde idealdir. Örneğin, bir kullanıcı koleksiyonu ve her kullanıcının altında kullanıcıya ait siparişlerin tutulduğu bir alt koleksiyon olabilir.
  users (Koleksiyon)
   └── userId (Belge)
        └── orders (Alt Koleksiyon)
            └── orderId (Belge)

Bu yapıda orders alt koleksiyonu, her bir kullanıcının ayrı bir belge altında sipariş bilgilerini saklar.

  • Alt Koleksiyonlara Erişim: Alt koleksiyonları çağırmak ve verileri yönetmek için doc() ve collection() metotlarını kullanabilirsiniz:
  // Kullanıcının bir siparişine erişme
  FirebaseFirestore.instance
      .collection('users')
      .doc('userId')
      .collection('orders')
      .doc('orderId')
      .get();
2. Veri Normalizasyonu ve Denormalizasyonu

Firestore, NoSQL bir veritabanı olduğundan, ilişkisel veritabanları gibi normalizasyon ve denormalizasyon kararları performans açısından önemlidir. Veriyi daha hızlı ve daha verimli erişim için optimize edebilirsiniz.

  • Veri Normalizasyonu: Normalizasyon, verileri tekrar kullanmak için ayırarak her bir veriyi yalnızca bir yerde tutmayı amaçlar. Bu, verilerin az yer kaplamasını sağlar, ancak ilişkisel verilere erişim gereksinimi arttığında sorgu işlemleri karmaşık hale gelebilir. Firestore’da normalizasyon, genellikle ana koleksiyonlarda referanslar kullanarak gerçekleştirilir. Örneğin, bir gönderi koleksiyonu ve bir kullanıcı koleksiyonu varsa, her gönderi için kullanıcı bilgilerinin yerine kullanıcı kimliği referans olarak tutulabilir.
  {
    "postId": "12345",
    "content": "This is a post",
    "userId": "user_001"  // Normalizasyon ile yalnızca referans tutulur
  }
  • Veri Denormalizasyonu: Denormalizasyon, daha hızlı erişim sağlamak için ilgili verileri tek bir yerde toplar. Bu, özellikle veriye hızlı erişim gerektiren durumlarda tercih edilir. Ancak, veri güncellemelerinde her belgeyi güncellemek gerekebilir. Firestore gibi NoSQL veritabanlarında denormalizasyon, çokça kullanılan bir yöntemdir. Örneğin, posts koleksiyonundaki her belgeye kullanıcı adını ekleyerek doğrudan erişim sağlayabilirsiniz:
  {
    "postId": "12345",
    "content": "This is a post",
    "userId": "user_001",
    "userName": "John Doe"  // Denormalizasyon ile kullanıcının adını doğrudan kaydedin
  }
3. Veri Yapısı Tasarımı: Firestore’un En İyi Uygulamaları
  • Koleksiyon-Belge Yapısı: Firestore’un veri yapısı koleksiyonlar ve belgeler arasında bir hiyerarşi oluşturur. Her belge bir koleksiyonun parçası olmalı ve her koleksiyon alt koleksiyonlar oluşturabilir. Veriler, en temel parçaya ayrılarak mantıksal olarak anlamlı koleksiyonlar halinde düzenlenmelidir. Örneğin, bir blog uygulaması için posts ve comments koleksiyonları oluşturulabilir. comments, her gönderiye bağlı bir alt koleksiyon olarak tanımlanabilir.
  • Tek Koleksiyon veya Birden Fazla Alt Koleksiyon: Her bir alt koleksiyonun belirli bir belgeye ait olmasını istiyorsanız, Firestore’un alt koleksiyon desteğini kullanabilirsiniz. Ancak tüm verileri bir koleksiyonda saklamak ve sorgularken filtreleme yapmak daha basit bir yapı oluşturabilir.
4. Birden Fazla İlişki Türü

Firestore’da veri yapınızı oluştururken farklı ilişki türlerini göz önünde bulundurabilirsiniz:

  • Bir-Bir İlişki: Firestore’da bir belgeye bir başka belgenin ID’sini ekleyerek bir-bir ilişkiler kurabilirsiniz. Örneğin, bir profile koleksiyonu ile user koleksiyonu arasında bir ilişki kurmak için userId referansını profile belgesine ekleyebilirsiniz.
  • Bir-Çok İlişki: Bir kullanıcının birçok gönderisi olmasını istiyorsanız, her kullanıcı için alt koleksiyon olarak posts koleksiyonu oluşturabilir veya tüm gönderileri bir posts koleksiyonunda saklayıp userId ile ilişkilendirebilirsiniz.
  • Çok-Çok İlişki: Çok-çok ilişkiler için, örneğin kullanıcılar ve gruplar arasında, group_members adında bir yardımcı koleksiyon oluşturabilirsiniz. Bu koleksiyon, kullanıcı kimlikleri ve grup kimliklerini ilişkilendirir.
  // Yardımcı bir çok-çok ilişki koleksiyonu örneği
  {
    "groupId": "group_001",
    "userId": "user_001"
  }
5. Veri Erişim ve Performans Optimizasyonu
  • Sorgu Sayısını Azaltma: Denormalize edilmiş verileri kullanarak daha az sorguyla daha fazla veriye erişebilirsiniz. Örneğin, bir gönderiye ait yorumlar doğrudan gönderi belgesinde saklanırsa, ek bir sorgu yapılması gerekmez.
  • Veri Dağılımı ve Büyüklüğü: Firestore’da, her koleksiyonda sınırsız belge saklayabilirsiniz. Ancak büyük veri setlerinde performansı korumak için koleksiyonları düzenlerken dikkatli olunmalıdır.
6. Firestore’da Veri Yapısı ve İlişkiler: Senaryo Örneği

Diyelim ki, bir sosyal medya uygulaması geliştiriyorsunuz ve kullanıcıların profil bilgileri, gönderileri, beğenileri ve yorumları gibi verileriniz var. Bu veriler için yapabileceğiniz yapılandırma:

users (Koleksiyon)
 └── userId (Belge)
      └── name: "John Doe"
      └── email: "john@example.com"
      └── posts (Alt Koleksiyon)
          └── postId (Belge)
               └── content: "Hello World!"
               └── likes: ["user_001", "user_002"]
               └── comments (Alt Koleksiyon)
                   └── commentId (Belge)
                        └── userId: "user_003"
                        └── text: "Nice post!"

Bu yapı, kullanıcıların her gönderiye ve yoruma erişimini kolaylaştırır ve ilişkisel yapı sağlar.

Firestore’daki veri yapısını iyi planlamak ve optimize etmek, sorgu performansını artırır ve uygulamanın verimli çalışmasını sağlar. Firestore’un sunduğu esnek yapılar sayesinde hem ilişkisel veritabanı ilişkilerini hem de NoSQL veritabanı avantajlarını bir araya getirerek uygulamanızı geliştirebilirsiniz.

Performans Optimizasyonu

Firebase Firestore’da performans optimizasyonu, verilerin daha hızlı ve daha verimli bir şekilde sunulmasını sağlamak için çeşitli stratejilerin uygulanmasını içerir. Firestore gibi NoSQL veritabanlarında veri erişimini optimize etmek, büyük ölçekli uygulamalar için kritik öneme sahiptir.

1. Veri İndeksleme

Firestore’da indeksleme, veri erişimini hızlandırmanın en etkili yollarından biridir. İndeksler, sorguların daha hızlı gerçekleştirilmesini sağlar ve sonuç olarak performansı artırır. Firestore, bazı temel indeksleri otomatik olarak oluşturur, ancak karmaşık sorgular için özel indeksler oluşturmak gerekebilir.

  • Otomatik ve Kompozit İndeksler:
  • Otomatik İndeksler: Firestore, her belge için tek alan indeksleri oluşturur. Bu sayede alanlara göre yapılan temel sorgular hızlıca gerçekleştirilir.
  • Kompozit İndeksler: Birden fazla alan içeren karmaşık sorgular için kompozit indeksler gerekir. Örneğin, hem age hem de status alanlarına göre sıralama veya filtreleme yapılması gerektiğinde, Firestore bir kompozit indeks talep edebilir.
  • İndeks Oluşturma: Kompozit bir indeks, Firebase konsolundan veya hata mesajından kolayca oluşturulabilir. Eğer bir sorgu kompozit indeks gerektiriyorsa, Firestore bu durumu belirterek sizi indeks oluşturma ekranına yönlendirir. Örneğin:
  // Yaşa ve statüye göre filtreleme
  FirebaseFirestore.instance
      .collection('users')
      .where('age', isGreaterThan: 18)
      .where('status', isEqualTo: 'active')
      .get();

Bu sorguda hem age hem de status için kompozit indeks gerektiği belirtilir.

  • Özel İndeksler Yönetimi: Firebase konsolundan Firestore Indexes bölümünde, tüm kompozit indeksleri görebilir ve gerektiğinde kaldırabilir veya düzenleyebilirsiniz.
2. İstemci Taraflı Filtreleme Yerine Sunucu Taraflı Filtreleme

Firestore, veritabanında filtreleme yapmanıza olanak tanır, bu da yalnızca gerekli verilerin istemciye gönderilmesini sağlar. İstemci tarafında filtreleme, gereksiz veri çekimi yapar ve uygulamanın performansını düşürür, bu nedenle filtreleme işlemini mümkün olduğunca sunucu tarafında gerçekleştirmek önemlidir.

  • Sunucu Taraflı Filtreleme Yapmak: Filtreleme ve sıralama işlemlerini Firestore sorgularında gerçekleştirerek, yalnızca ihtiyaç duyduğunuz verileri çekebilirsiniz. Örneğin, belirli bir kullanıcının gönderilerini getirmek istiyorsanız, bu sorguyu Firestore üzerinde gerçekleştirin:
  // Belirli bir kullanıcının gönderilerini çekme
  FirebaseFirestore.instance
      .collection('posts')
      .where('userId', isEqualTo: 'user_001')
      .get();

Bu sorgu, yalnızca userId değeri user_001 olan gönderileri getirir, böylece istemciye gereksiz veri çekilmez.

3. Sorgu Yapısı ve Sayfa Yapma (Pagination)

Firestore, büyük veri kümelerinde gezinmeyi kolaylaştırmak için sayfa yapma (pagination) desteği sağlar. Sorgu sonuçlarını sayfa sayfa çekerek hem bellek kullanımını hem de performansı optimize edebilirsiniz.

  • Sayfa Yapma (Pagination) Kullanımı: startAt(), startAfter(), endAt(), ve endBefore() metodları ile sayfalama yapılabilir. Örneğin, bir limit() ile belirli sayıda belge çektikten sonra bir sonraki sayfaya geçişi sağlayabilirsiniz:
  // İlk 10 belgeyi getir
  QuerySnapshot firstPage = await FirebaseFirestore.instance
      .collection('posts')
      .orderBy('timestamp')
      .limit(10)
      .get();

  // Sonraki 10 belgeyi getir
  QuerySnapshot nextPage = await FirebaseFirestore.instance
      .collection('posts')
      .orderBy('timestamp')
      .startAfterDocument(firstPage.docs[firstPage.docs.length - 1])
      .limit(10)
      .get();

Bu sayede sayfa sayfa veri çekebilir ve bellekte gereksiz yer kaplayan verilerden kaçınabilirsiniz.

4. Denormalizasyon ile Veri Erişimini Hızlandırma

Firestore’da denormalizasyon, yani belirli verileri tekrarlayarak saklama, performansı artıran yaygın bir tekniktir. Her bir belgeye, başka bir belgeye bağımlı verilere ihtiyaç duyulmadan erişim sağlanabilir.

  • Denormalize Veri Yapısı Örneği: Bir gönderi belgesinde, gönderen kullanıcının adı ve profil resmi gibi bilgileri saklamak, her sorguda kullanıcı belgesine ihtiyaç duyulmasını engeller. Bu özellikle sık kullanılan ve güncellenme sıklığı düşük olan bilgiler için faydalıdır:
  {
    "postId": "12345",
    "content": "This is a post",
    "userId": "user_001",
    "userName": "John Doe",
    "userProfileImage": "https://example.com/profile.jpg"
  }

Bu yapı, kullanıcının adı ve profil resmi gibi bilgilerin her sorguda kullanıcı belgesinden çekilmesine gerek kalmadan hızlı erişim sağlar.

5. Veri Çekme Sıklığını ve Miktarını Azaltma

Firestore’da her veri çekme işlemi, hem okuma maliyetini artırır hem de performansı etkiler. Bu nedenle yalnızca gereken verileri çekmek ve fazla sorgulardan kaçınmak önemlidir.

  • Belirli Alanları Seçmek: Belirli alanlara ihtiyacınız varsa select() metodu ile sadece gerekli alanları çekerek veri transferini ve maliyeti azaltabilirsiniz:
  // Yalnızca kullanıcı adı ve e-posta alanlarını çek
  FirebaseFirestore.instance
      .collection('users')
      .doc('user_001')
      .select(['name', 'email'])
      .get();

Bu sorgu, yalnızca name ve email alanlarını çeker, böylece istemciye gereksiz veri indirilmez.

  • Cache Kullanımı ve Çevrimdışı Veri: Firestore’un çevrimdışı destek ve önbellekleme özelliğini kullanarak gereksiz veri çekiminden kaçınabilirsiniz. Bu, özellikle verilerin sık sık güncellenmediği durumlarda faydalıdır. Firestore otomatik olarak verileri önbelleğe alır, ancak source ayarını Source.cache olarak belirleyerek çevrimdışı veri okuma işlemlerini zorlayabilirsiniz:
  // Yalnızca önbellekten okuma
  FirebaseFirestore.instance
      .collection('posts')
      .get(GetOptions(source: Source.cache));
6. Veri Güncellemelerini Minimuma İndirme

Firestore’da sık veri güncellemeleri, maliyeti ve yükü artırır. Veriler sık değişmiyorsa, gereksiz güncellemelerden kaçınmak için yapıyı yeniden gözden geçirebilirsiniz.

  • Koşullu Güncelleme: Güncelleme işlemlerini yalnızca belirli bir koşul sağlandığında gerçekleştirmek için FieldValue kullanabilirsiniz. Örneğin, bir sayaç değeri güncellenecekse, yalnızca değişiklik olduğunda güncelleme yapılabilir:
  // Like sayısını artırma
  FirebaseFirestore.instance
      .collection('posts')
      .doc('postId')
      .update({
        'likes': FieldValue.increment(1),
      });
7. Kapsamlı Firestore Sorgu ve Performans Optimizasyonu Özeti
  • İndeksleri Yönetin: Sorgularınızın performansını artırmak için kompozit indekslerinizi yönetin.
  • Sunucu Taraflı Filtreleme Kullanın: Gereksiz veri çekiminden kaçınmak için sorgularınızı Firestore üzerinde filtreleyin.
  • Sayfa Sayfa Veri Çekimi Yapın: Büyük veri kümelerini sayfalayarak çekin.
  • Denormalize Edin: Sık kullanılan ve sabit kalan verileri doğrudan kaydedin.
  • Belirli Alanları Çekin: Sadece gerekli alanları seçerek istemciye gereksiz veri indirilmesinin önüne geçin.
  • Önbelleği Kullanın: Çevrimdışı veri ve önbellek özelliklerinden yararlanarak gereksiz okuma işlemlerinden kaçının.

Bu optimizasyon teknikleri sayesinde, Firestore veritabanınızın performansını artırabilir, maliyetleri azaltabilir ve kullanıcı deneyimini iyileştirebilirsiniz. Firestore gibi bir NoSQL veritabanında performansı artırmak için doğru veri yapısını ve sorgu tekniklerini kullanmak hayati öneme sahiptir.

Kaynakça:

Bütün merdivenleri görmek zorunda değilsiniz. Yapmanız gereken tek şey ilk adımı atmak.

Martin Luther King Jr.

Bir sonraki yazıda görüşmek dileğiyle!”

Leave a Reply

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir


9 + 5 = ?