Firebase
Firestore real vaqt sinxronizatsiyasi va men yo'l qo'ygan xato
Ma'lumot ikkita qurilma o'rtasida o'zi yangilanadi — bu sehrli tuyuladi. Toki bir kun mahalliy holat buluddagi to'g'ri ma'lumotni yozib yuborganini ko'rmaguningizcha.
Nima uchun Firestore
Yetkazib berish ilovasida buyurtma bir vaqtning o'zida uch joyda ko'rinishi kerak: mijoz telefonida, oshxona ekranida va admin panelida. Har biri holat o'zgarishini darhol bilishi shart.
Buni WebSocket bilan yozish mumkin, lekin unda server kerak, uni ushlab turish kerak, uzilishlarni qayta ulashni o'zingiz yozasiz. Firestore da esa onSnapshot bitta qatorda shu ishni bajaradi.
Asosiy naqsh
Tinglovchi o'rnatiladi va har o'zgarishda funksiya chaqiriladi:
import { collection, query, where, onSnapshot }
from 'firebase/firestore';
const q = query(
collection(db, 'orders'),
where('status', 'in', ['new', 'cooking'])
);
const stop = onSnapshot(q, (snap) => {
const orders = snap.docs.map(d => ({ id: d.id, ...d.data() }));
renderKitchen(orders);
});
Yangi buyurtma qo'shilsa, holat o'zgarsa yoki hujjat o'chirilsa — funksiya o'zi chaqiriladi. Serverga so'rov yuborish, javob kutish, qayta so'rash kerak emas.
Muhim. onSnapshot obyekt qaytaradi — uni saqlang. Sahifa yopilganda yoki komponent olib tashlanganda tinglovchini to'xtatish kerak, aks holda ular to'planib boradi va o'qish limitini yeb qo'yadi.
Mening xatom
Unutilmas Ta'm da savat mahalliy holatda saqlanardi va bir vaqtning o'zida Firestore ga ham yozilardi. Mantiq shunday edi:
// ❌ noto'g'ri
onSnapshot(cartRef, (doc) => {
cart = doc.data(); // buluddan olamiz
render();
});
function addItem(item) {
cart.items.push(item); // mahalliy o'zgartiramiz
setDoc(cartRef, cart); // butun obyektni yozamiz
}
Bir qarashda mantiqiy. Amalda esa foydalanuvchi ikkita qurilmadan kirganda mahsulotlar yo'qola boshladi.
Nima sodir bo'lgan
Foydalanuvchi telefonda mahsulot qo'shadi. Shu paytda planshetda hali eski savat turibdi. Planshetda ham biror narsa qo'shiladi — va u butun obyektni yozib yuboradi. Telefondagi qo'shilgan mahsulot yo'qoladi.
Bu klassik "last write wins" muammosi. setDoc hujjatni to'liq almashtiradi, oradagi o'zgarishlarni hisobga olmaydi.
Yechim
Ikki narsani o'zgartirdim.
1. Butun obyektni emas, aniq maydonni yangilash
import { updateDoc, arrayUnion } from 'firebase/firestore';
async function addItem(item) {
await updateDoc(cartRef, {
items: arrayUnion(item) // faqat qo'shadi
});
}
arrayUnion serverda bajariladi. U mavjud massivga element qo'shadi va boshqa hech narsaga tegmaydi. Ikki qurilma bir vaqtda qo'shsa ham, ikkalasi ham saqlanadi.
2. Miqdor uchun tranzaksiya
Mahsulot sonini o'zgartirish murakkabroq, chunki eski qiymatni o'qib, yangisini hisoblash kerak. Bu yerda tranzaksiya ishlatiladi:
import { runTransaction } from 'firebase/firestore';
await runTransaction(db, async (tx) => {
const snap = await tx.get(cartRef);
const items = snap.data().items;
const i = items.findIndex(x => x.id === itemId);
items[i].qty += delta;
tx.update(cartRef, { items });
});
Agar tranzaksiya davomida ma'lumot o'zgarsa, Firestore uni avtomatik qayta bajaradi. Poyga holati yo'qoladi.
Oflayn rejim
Firestore ning kutilmagan foydali tomoni — u oflayn ishlaydi. Internet uzilganda yozuvlar mahalliy navbatda turadi va aloqa tiklanganda o'zi yuboriladi.
Lekin bu foydalanuvchiga ko'rinishi kerak. Aks holda u buyurtma berdim deb o'ylaydi, aslida u hali yuborilmagan:
onSnapshot(cartRef, { includeMetadataChanges: true }, (doc) => {
const pending = doc.metadata.hasPendingWrites;
showSyncBadge(pending ? 'yuborilmoqda' : 'saqlandi');
});
Xarajatni nazorat qilish
Firestore har o'qish uchun hisoblaydi. onSnapshot esa har o'zgarishda hujjatlarni qayta o'qiydi. Beparvo yozilgan kod hisobni tez o'stiradi.
Amalda foydali bo'lgan qoidalar:
- So'rovni
wherebilan toraytiring — barcha buyurtmalarni emas, faqat faollarini tinglang - Ro'yxatda hujjatning to'liq nusxasini emas, kerakli maydonlarni saqlang
- Tez o'zgaradigan ma'lumotni alohida hujjatga ajrating — u holda katta hujjat qayta o'qilmaydi
- Tarixiy ma'lumotni tinglamang, uni bir marta o'qing
Xavfsizlik qoidalari
Oxirgi va eng ko'p unutiladigan narsa. Sinov rejimida Firestore hammaga ochiq bo'ladi va ko'pchilik shu holda chiqaradi.
match /orders/{id} {
allow read: if request.auth != null
&& resource.data.uid == request.auth.uid;
allow create: if request.auth != null;
allow update: if request.auth.token.admin == true;
}
Qoidasiz baza — bu ochiq baza. Har kim ma'lumotni o'qishi va o'chirishi mumkin.
Xulosa
Firestore real vaqt sinxronizatsiyasini juda oson qiladi va aynan shu osonlik xavf tug'diradi: mahalliy holatni to'g'ridan-to'g'ri yozish odati bir necha qurilma paydo bo'lganda ma'lumot yo'qolishiga olib keladi.
Qoida: butun obyektni yozmang. Aniq maydonni yangilang, massivlar uchun arrayUnion, hisob-kitob uchun tranzaksiya ishlating.