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
, birstream
alır ve veri her güncellendiğindebuilder
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çerisindecacheSizeBytes
parametresi ile bu boyutu özelleştirebilirsiniz:
FirebaseFirestore.instance.settings = Settings(
cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED, // Sınırsız önbellek
);
3. Çevrimdışı Destek Özeti
- Yerel Kopya: Tüm veriler, çevrimdışıyken kullanıcının erişebilmesi için cihazda saklanır.
- Otomatik Senkronizasyon: Çevrimdışıyken yapılan işlemler, çevrimiçi hale gelindiğinde senkronize edilir.
- Ö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.
- 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.
- 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()
vecollection()
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
vecomments
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 ileuser
koleksiyonu arasında bir ilişki kurmak içinuserId
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 birposts
koleksiyonunda saklayıpuserId
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 destatus
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()
, veendBefore()
metodları ile sayfalama yapılabilir. Örneğin, birlimit()
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:
- https://instaflutter.com/flutter-tutorials/firebase-firestore-flutter/
- https://devbrite.io/flutter-firebase-cheatsheet
- https://firebase.flutter.dev/docs/firestore/overview/
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!”