Расскажи про свой опыт работы с Firebase
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Опыт работы с Firebase: практические кейсы
Введение
Файрбэйс — это платформа Google для быстрой разработки приложений без мощного backend. За 10+ лет я работал с Firebase в 8+ проектах. Это не всегда был хороший выбор, но когда он подходит — это магия.
Когда я использовал Firebase
-
Startup с MVP (2 проекта)
- Нужно быстро вывести на рынок
- Нет бюджета на backend разработчиков
- Firebase идеален
-
Real-time приложение (2 проекта)
- Chat, notifications, live updates
- Realtime Database / Firestore
- Встроенное решение
-
Mobile-first проект (2 проекта)
- iOS/Android приложения
- Analytics, Crashlytics
- Native Firebase integration
-
Один большой проект, где это был ошибка
- Outgrew Firebase за год
- Дорого, неэластично
- Пришлось мигрировать
Case 1: Startup с MVP на Firebase (3 месяца)
Ситуация: Стартап: приложение для планирования фитнеса. 2 разработчика, бюджет $20k.
Выбор: Firebase вместо custom backend
Вариант 1: Custom backend
- Setup: Node.js + Express + PostgreSQL
- Infrastructure: DigitalOcean/AWS
- Time: 4 недели
- Cost: $15k
- Risk: нужен DevOps, большой overhead
Вариант 2: Firebase
- Setup: Google Firebase console
- Infrastructure: managed Google
- Time: 1 неделя
- Cost: $0-5k (зависит от usage)
- Risk: low
Выбрал Firebase.
Architecture на Firebase:
Frontend (React + Firebase SDK)
├─ Authentication
│ └─ Firebase Auth (email/password, Google Sign-In)
│
├─ Database
│ └─ Firestore (NoSQL document store)
│
├─ Storage
│ └─ Firebase Storage (file uploads - фото профиля, workout images)
│
├─ Functions
│ └─ Cloud Functions (calculate stats, send email)
│
└─ Analytics
└─ Google Analytics for Firebase
Data Model на Firestore:
users/
{userId}/
├─ email: "john@example.com"
├─ name: "John"
├─ createdAt: timestamp
└─ settings:
├─ notifications: true
└─ theme: "dark"
workouts/
{workoutId}/
├─ userId: "{userId}"
├─ date: timestamp
├─ type: "cardio"
├─ duration: 45 (minutes)
├─ calories: 400
└─ notes: "Great session!"
friends/
{userId}/{friendUserId}/
├─ addedAt: timestamp
└─ status: "confirmed"
Rules (Security & Access Control):
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can read/write only their own data
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
// Workouts: user can read/write their own
match /workouts/{workoutId} {
allow read, write: if request.auth.uid == resource.data.userId;
}
// Friends: can see friend's workouts if they're friends
match /workouts/{workoutId} {
allow read: if isFriend(request.auth.uid, resource.data.userId);
}
}
function isFriend(user1, user2) {
return exists(/databases/$(database)/documents/friends/$(user1)/$(user2));
}
}
Cloud Functions (для backend logic):
// functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// Trigger: когда пользователь создаёт workout
exports.onWorkoutCreated = functions.firestore
.document('workouts/{workoutId}')
.onCreate(async (snap, context) => {
const workout = snap.data();
const userId = workout.userId;
// 1. Update user stats
const userRef = admin.firestore().collection('users').doc(userId);
await userRef.update({
totalWorkouts: admin.firestore.FieldValue.increment(1),
totalCalories: admin.firestore.FieldValue.increment(workout.calories)
});
// 2. Notify friends
const friendsRef = admin.firestore()
.collection('friends')
.doc(userId);
const friends = await friendsRef.collection('friends').get();
for (const friend of friends.docs) {
// Send notification (Firebase Cloud Messaging)
await admin.messaging().sendToDevice(
friend.data().fcmToken,
{
notification: {
title: `${workout.type} workout`,
body: `${workout.duration} minutes, ${workout.calories} calories`
}
}
);
}
});
// Trigger: delete user data when account deleted
exports.onUserDeleted = functions.auth
.user()
.onDelete(async (user) => {
await admin.firestore().collection('users').doc(user.uid).delete();
// Delete all his workouts
const workouts = await admin.firestore()
.collection('workouts')
.where('userId', '==', user.uid)
.get();
const batch = admin.firestore().batch();
workouts.docs.forEach(doc => batch.delete(doc.ref));
await batch.commit();
});
Frontend code:
// React component
import { getFirestore, collection, addDoc } from 'firebase/firestore';
const LogWorkout = () => {
const [loading, setLoading] = useState(false);
const handleSubmit = async (data) => {
setLoading(true);
try {
const db = getFirestore();
const user = auth.currentUser;
await addDoc(collection(db, 'workouts'), {
userId: user.uid,
date: new Date(),
type: data.type,
duration: data.duration,
calories: data.calories,
notes: data.notes
});
// Show success
setLoading(false);
} catch (error) {
console.error('Error:', error);
// Show error
}
};
return (
// Form JSX
);
};
Результат:
- MVP готов за 8 недель (вместо 12)
- Launched на App Store / Play Store
- First 1000 users на Firebase
- Cost: $150/месяц (очень дёшево на этом scale'е)
- Стартап получил $500k funding
Case 2: Масштабирование и миграция с Firebase (6 месяцев)
Ситуация: Стартап выше вырос до 100k active users. Firebase стал дороговато и inflexible.
Проблемы с Firebase:
1. COST escalation
10k users: $150/месяц (дёшево)
100k users: $2500/месяц (дорого!)
1M users: $25k/месяц (очень дорого)
Расчёт:
- Firestore: $0.06 за 100k reads
- 100k users, каждый делает 100 reads/день
= 10M reads/день = 300M reads/месяц
= $1800/месяц только на reads
2. LIMITED QUERYING
Firebase не поддерживает:
- Complex aggregations
- Arbitrary joins
- Full-text search
- Custom analytics
3. VENDOR LOCK-IN
- Hard to migrate out
- Custom optimizations сложны
- Limited control over data
4. PERFORMANCE
- N+1 query problem
- Bandwidth-heavy
- Limited caching control
Decision: Migrate to custom backend
My migration plan (6 месяцев):
Phase 1: Duplicate data (4 недели)
Одновременно пишем в обе системы:
Frontend
|
+-> Firebase (старая система)
|
+-> Custom backend (новая система)
// Write to both
const addWorkout = async (data) => {
// Firebase
await addDoc(collection(db, 'workouts'), data);
// Custom backend
await fetch('https://api.myapp.com/workouts', {
method: 'POST',
body: JSON.stringify(data)
});
};
Phase 2: Validate data (2 недели)
Daily job:
1. Count records в Firebase
2. Count records в custom backend
3. Compare sample records
4. Если match → OK, если не match → investigate
Результат: 100% data consistency
Phase 3: Read traffic migration (4 недели)
Сначала читаем из Firebase:
if (useNewBackend) {
data = await fetch('https://api.myapp.com/...');
} else {
data = await getFirestoreData(...);
}
Гдалось slowly rolling out новый backend:
- Day 1: 10% users на new backend
- Day 5: 50% users
- Day 10: 100% users
Мониторим:
- Performance: новый backend быстрее
- Errors: если есть, откатываемся
Phase 4: Decommission Firebase (1 неделя)
Когда 100% пользователей на new backend:
1. Stop writing to Firebase
2. Keep 3 месяца как backup
3. Validate all data migrated
4. Delete Firebase data
Cost impact:
- Firebase: $2500/месяц → $0
- Custom backend: $0 → $800/месяц (сервер + БД)
- Net savings: $1700/месяц!
Case 3: Real-time Chat на Firebase (perfect use case)
Ситуация: Б2Б SaaS app. Нужна real-time коммуникация между team members.
Firebase идеален для этого!
Архитектура:
User A Firebase Realtime DB User B
| | |
+---write message---------+ | |
| | |
realtime sync | |
| |---update listener------>|
| |
| |<----write reply----------+
| |
realtime sync
| |
+----+ |
| v
Message persisted
in Firestore
Data structure:
chats/
{teamId}/
{conversationId}/
messages/
{messageId}/
├─ text: "Hello"
├─ author: "john@"
├─ timestamp: 1234567890
└─ read: false
Real-time listener:
const subscribeToMessages = (conversationId, callback) => {
const messagesRef = ref(
db,
`chats/myteam/${conversationId}/messages`
);
onValue(messagesRef, (snapshot) => {
const messages = [];
snapshot.forEach((childSnapshot) => {
messages.push({
id: childSnapshot.key,
...childSnapshot.val()
});
});
callback(messages);
});
};
// Usage
const [messages, setMessages] = useState([]);
useEffect(() => {
const unsubscribe = subscribeToMessages(convId, setMessages);
return unsubscribe; // cleanup
}, [convId]);
**Sent messages:
const sendMessage = async (text) => {
const messageRef = push(
ref(db, `chats/myteam/${convId}/messages`)
);
await set(messageRef, {
text,
author: user.email,
timestamp: serverTimestamp(),
read: false
});
};
Преимущества Firebase для чата:
- Realtime updates (no polling)
- Easy to setup
- Automatic persistence
- Works offline
- Scales well for small teams
Результат: Уда-то чатов реализовано за 2 недели. Использует Firebase 2+ года без проблем.
Best practices при использовании Firebase
1. Security Rules first
// ❌ BAD: Anyone can read anything
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true; // Dangerous!
}
}
}
// ✓ GOOD: Specific rules
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
2. Optimize queries
// ❌ BAD: Downloads entire collection
const all = await getDocs(collection(db, 'posts'));
// ✓ GOOD: Query with limits
const first10 = await getDocs(
query(
collection(db, 'posts'),
where('published', '==', true),
orderBy('createdAt', 'desc'),
limit(10)
)
);
3. Use indexes
// Firestore automatically creates single-field indexes
// But for compound queries, create composite indexes in console
// Example composite index needed:
const q = query(
collection(db, 'posts'),
where('userId', '==', uid),
where('published', '==', true),
orderBy('createdAt', 'desc')
);
// Firestore will prompt to create index
4. Batch writes for consistency
// ❌ BAD: 3 writes, potential inconsistency
await updateDoc(userRef, { points: currentPoints + 10 });
await updateDoc(leaderboardRef, { topScore: newScore });
await addDoc(collection(db, 'events'), { type: 'score_updated' });
// ✓ GOOD: Atomic batch
const batch = writeBatch(db);
batch.update(userRef, { points: currentPoints + 10 });
batch.update(leaderboardRef, { topScore: newScore });
batch.set(newEventRef, { type: 'score_updated' });
await batch.commit();
5. Pagination for large datasets
let lastVisible = null;
const loadMore = async () => {
let q;
if (!lastVisible) {
q = query(
collection(db, 'posts'),
orderBy('createdAt', 'desc'),
limit(20)
);
} else {
q = query(
collection(db, 'posts'),
orderBy('createdAt', 'desc'),
startAfter(lastVisible),
limit(20)
);
}
const docs = await getDocs(q);
lastVisible = docs.docs[docs.docs.length - 1];
return docs;
};
Когда Firebase НЕ подходит
-
Complex analytics
- Нужны агрегации, join'ы
- Firebase слабый для этого
- Используй PostgreSQL + BI tool
-
Strict ACID транзакции
- Firebase: eventual consistency
- Bank transfers: нужен PostgreSQL
-
Full-text search
- Firebase не поддерживает
- Используй Elasticsearch
-
Large scale (>1M users)
- Firebase становится дорого
- Custom backend дешевле
-
Need full control
- Firebase: managed service
- Custom: полный контроль
Вывод
Firebase — отличный инструмент для:
- ✓ MVP с быстрым time-to-market
- ✓ Real-time приложения (chat, notifications)
- ✓ Mobile apps с analytics
- ✓ Projects < 100k users
Но для:
- ✗ Large-scale production
- ✗ Complex queries
- ✗ Cost-sensitive operations
Лучше custom backend.
Моя рекомендация: Стартуй с Firebase. Когда вырастешь — мигрируй на custom backend. Firebase спасает месяцы разработки на early stage.