Транзакция Firestore последовательное создание коллекций и обновление документов

# #javascript #node.js #firebase #google-cloud-firestore

Вопрос:

Окружающая среда: nodejs, firebase-admin, firestore.

Структура базы данных (пространство): структура пространства

Структура базы данных (пользователь): введите описание изображения здесь

Создание нового пространства (пример):

 // init data
const userId = "someUserId";
// Create new space
const spaceRef = await db.collection("spaces").add({ name: "SomeName" });
// Get spaceId
spaceId = spaceRef.id;
// Get user Doc for upate their spaces
const userRef = await db.collection("users").doc(userId);
// Add "spaceId" to user spaces list
userRef.collection("spaces").doc(spaceId).set({ some: "data" });
// Create collection "members" in new space with "userId"
spaceRef.collection("members").doc(userId).set({role: "OWNER"})
 

Вопрос: Я хочу выполнить этот код внутри одной runTransaction, но, поскольку я вижу, что транзакции поддерживают только однократное чтение и многократное обновление, это меня не устраивает, поскольку я получаю spaceId то, что мне нужно, во время выполнения кода.

Почему я хочу использовать транзакцию: В моей структуре данных требуется связь между созданным пространством и наличием идентификатора этого пространства у пользователя. Если мы предположим, что во время выполнения этого кода произошла ошибка, например, пространство было создано, но это пространство не было добавлено внутри профиля пользователя, то это будет фатальной проблемой в структуре моей базы данных.

Как и в других базах данных, транзакции решают эту проблему, но я не могу понять, как это сделать с firestore.

Может быть, вы знаете лучший способ защитить себя от непротиворечивых данных в этом случае?

Ответ №1:

На самом деле, вам не нужна транзакция для этого, поскольку вы не читаете документы.

При db.collection("users").doc(userId); этом вы фактически не читаете документ, а просто вызываете «локально» doc() метод для создания DocumentReference . Этот метод не является асинхронным, поэтому вам не нужно использовать await . Чтобы прочитать документ, вы должны использовать асинхронный get() метод.


Итак, использование пакетной записи, которая атомарно фиксирует все ожидающие операции записи в базу данных, сделает свое дело:

   const userId = 'someUserId';
  const userRef = db.collection('users').doc(userId);

  const spaceRef = firestore.collection('spaces').doc();
  const spaceId = spaceRef.id;

  const writeBatch = firestore.batch();
  writeBatch.set(spaceRef, { name: "SomeName" });
  writeBatch.set(userRef.collection("spaces").doc(spaceId), { some: "data" });
  writeBatch.set(spaceRef.collection("members").doc(userId), {role: "OWNER"});

  await writeBatch.commit();
 

Вы должны включить этот код в try/catch блок, и если пакетная фиксация завершится неудачно, вы сможете справиться с этой ситуацией в catch блоке, зная, что ни одна запись не была зафиксирована.

Комментарии:

1. хм, я ошибочно полагал, что только после операции .add ({name:" Some Name "}) с ответом сервера я могу получить идентификатор вновь созданного пространства. Но получается, судя по вашему примеру, что .doc () функция подготавливает рандом id для ранее, вообще без запроса к серверу, и уже использует его при его создании. .doc () Функция работает не так, как я ожидал 🙂 Спасибо.

2. » doc() функция подготавливает случайный идентификатор для более раннего, без запроса к серверу» => это именно так!