Вопрос или проблема
useEffect(()=>{
if(!user){
return;
}
const creationTime = user.metadata?.creationTime;
if (!creationTime) {
setLoading(false);
return;
}
const userCreationDate = new Date(creationTime);
const currentDate = new Date();
const diffTime = Math.abs(currentDate.getTime() - userCreationDate.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
const db = getFirestore(app);
const userRef = doc(db, 'users', user.uid);
if (diffDays < 7) {
setIsSubscribed(true);
setLoading(false);
return;
}
const unsubscribeSnapshot = onSnapshot(userRef, (userDoc) => {
console.log('В unsubscribeSnapshot');
if (userDoc.exists()) {
const userData = userDoc.data();
const subscribed = userData.isSubscribed;
if (!subscribed) {
router.push('/signUp');
setLoading(false);
return;
}
setIsSubscribed(subscribed);
} else {
setIsSubscribed(false);
router.push('/signUp');
}
setLoading(false);
});
setUnsubscribeSnapshot(() => unsubscribeSnapshot);
return () => {
if (unsubscribeSnapshot) {
unsubscribeSnapshot();
}
};
},[router, user]);
import { FirebaseApp } from 'firebase/app';
import { getFirestore, doc, setDoc } from 'firebase/firestore';
import { getAuth } from 'firebase/auth';
const updateSubscriptionStatus = async (
app: FirebaseApp,
isSubscribed: boolean,
customerId: string,
) => {
const auth = getAuth(app);
const userId = auth.currentUser?.uid;
if (!userId) {
console.error('Пользователь не аутентифицирован');
throw new Error('Пользователь не аутентифицирован');
}
const db = getFirestore(app);
const userRef = doc(db, 'users', userId);
await setDoc(userRef, { isSubscribed, stripeCustomerId: customerId }, { merge: true });
};
export default updateSubscriptionStatus;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!stripe || !elements) {
return;
}
setIsLoading(true);
const result = (await stripe.confirmPayment({
elements,
redirect: 'if_required',
})) as PaymentIntentResult;
if (result.error) {
setMessage(result.error.message || 'Произошла неожиданная ошибка.');
} else if (result.paymentIntent && result.paymentIntent.status === 'succeeded') {
try {
await updateSubscriptionStatus(app, true, customerId);
setMessage('Платеж успешен!');
router.push('/thanks');
} catch (error) {
setMessage('Не удалось обновить статус подписки.');
console.error(error);
}
} else {
setMessage('Обработка платежа.');
}
setIsLoading(false);
};
В настоящее время я управляю системой подписки, в которой каждый пользователь получает семидневный бесплатный пробный период. В течение этого пробного периода локальная переменная isSubscribed устанавливается в значение true, что позволяет пользователю получать доступ ко всем страницам. После окончания пробного периода isSubscribed устанавливается в значение false, и соответствующая переменная в Firestore создается и обновляется до значения false. Это ограничивает доступ ко всем страницам, и пользователь перенаправляется на страницу регистрации.
После завершения пользователем процесса регистрации и настройки его ежемесячной подписки как переменная isSubscribed в Firestore, так и локальная переменная isSubscribed обновляются до значения true, восстанавливая доступ к страницам.
Я также настроил веб-хук Stripe API для автоматического обновления статуса isSubscribed в Firestore на основе успешности или неудачи ежемесячных платежей, что, в свою очередь, обновляет локальную переменную isSubscribed.
Мой вопрос в том, следует ли использовать Cloud Functions вместо onSnapshot, учитывая, что у меня будет примерно 1000 пользователей, каждый из которых пройдет семидневный бесплатный пробный период, и статус их isSubscribed будет определяться их ежемесячными платежами.
В настоящее время я использую onSnapshot, и он работает хорошо, но меня беспокоит его производительность и затраты.
Ответ или решение
При принятии решения между использованием onSnapshot
из Firestore и облачными функциями для управления подписками пользователей, необходимо учитывать несколько факторов, таких как производительность, стоимость и архитектура вашего приложения. Давайте разберем каждый из элементов этого выбора.
Использование onSnapshot
onSnapshot
предоставляет возможность прослушивать изменения в реальном времени для документа или коллекции в Firestore. Это означает, что когда данные о пользователе в базе данных обновляются, приложение автоматически получает эти изменения без необходимости вручную запрашивать данные.
Преимущества:
- Реальное время: Вы можете сразу же отобразить изменения в состоянии подписки и предоставлении доступа для пользователей.
- Упрощение логики: У вас будет меньше логики для обработки обновлений данных, так как Firestore сам управляет этими потоками.
Недостатки:
- Производительность: Каждый раз, когда вызывается
onSnapshot
, происходит подписка на отдельный документ. Если у вас 1000 пользователей, это может привести к значительному количеству одновременных соединений с Firestore и увеличить нагрузку на клиентское приложение. - Стоимость: Использование
onSnapshot
может привести к большим затратам, если пользователи часто обновляют свои данные или если данные часто изменяются в Firestore, так как вы будете платить за каждый документ, который прочитан и изменён.
Использование облачных функций
Облачные функции позволяют выполнять серверный код в ответ на события, такие как HTTP-запросы или изменения в Firestore. Вы можете использовать их для управления состоянием подписки каждого пользователя и обновления данных в Firestore при получении уведомлений от Stripe.
Преимущества:
- Эффективность: Облачные функции работают на сервере, что означает меньшую нагрузку на клиентскую часть вашего приложения, особенно с увеличением числа пользователей.
- Контроль и масштабируемость: Вы можете более гибко обрабатывать большой объём данных и события. Например, изменить бизнес-логику при разных условиях, таких как успешная или неудачная оплата.
- Сокращение затрат: Облачные функции могут быть более экономичной моделью, так как возможность платите только за выполнение функции, а не за каждое изменение в документе Firestore.
Недостатки:
- Сложность реализации: Требуется дополнительная логика для реализации взаимодействия облачных функций с Firestore и Stripe.
- Задержка: В некоторых случаях обработка событий через облачные функции может занять больше времени, чем использование
onSnapshot
, особенно если требуются дополнительные запросы к Firestore.
Рекомендации
С учетом всех вышеперечисленных факторов, если у вас 1000 пользователей с потенциально частыми изменениями состояния подписки, рекомендую рассмотреть использование облачных функций. Это не только поможет упростить управление состоянием подписки, но и уменьшит нагрузку на клиентскую часть и потенциальные затраты.
Вы можете использовать облачные функции для обработки вызовов от Stripe и обновления поля isSubscribed
в Firestore. После этого вы можете периодически запрашивать состояние подписки на клиенте или использовать статические методы для получения состояния при загрузке.
Кроме того, если в вашем приложении крайне важно мгновенное обновление данных, можно рассмотреть комбинированный подход: использовать onSnapshot
для уведомлений о более рутинных изменениях и облачные функции для более критических операций.
Заключение
Переход на облачные функции может повысить производительность и снизить затраты по мере роста числа пользователей. Поэтому, учитывая особенности вашего приложения и количество пользователей, использование облачных функций представляется более целесообразным решением на долгосрочную перспективу.