Функции Firebase на круговой задаче обновления

У меня такая ситуация с круговой функцией, возникают проблемы с поиском решения.

Есть коллекция, где у меня есть флаг, который сообщает, изменились ли данные. Также хочу внести изменения.

export async function landWrite(change, context) {

  const newDocument = change.after.exists ? change.after.data() : null
  const oldDocument = change.before.data()

  const log = {
    time: FieldValue.serverTimestamp(),
    oldDocument: oldDocument,
    newDocument: newDocument
  }

  const landid = change.after.id
  const batch = db.batch()

  const updated = newDocument && newDocument.updated === oldDocument.updated

  if (!updated) {
    const landRef = db.collection('land').doc(landid)
    batch.update(landRef, {'updated': true })
  }
  const logRef = db.collection('land').doc(landid).collection('logs').doc()
  batch.set(logRef, log)

  return batch.commit()
  .then(success => {
    return true
  })
  .catch(error => {
    return error
  })

}

Проблема состоит в том, что это записывает журнал дважды, когда флаг UPDATED имеет значение false. Но также нельзя поместить запись журнала в инструкцию ELSE, поскольку флаг уже может быть ОБНОВЛЕН, и необходимо выполнить обновление нового документа, поэтому необходимо записать новый журнал.

Спусковой крючок:

import * as landFunctions from './lands/index'
export const landWrite = functions.firestore
.document('land/{land}')
.onWrite((change, context) => {
  return landFunctions.landWrite(change, context)
})

Ответ 1

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

Эта строка вводит в заблуждение:

  const updated = newDocument && newDocument.updated === oldDocument.updated

Должен быть назван:

  const updateStatusDidNotChange = newDocument && newDocument.updated === oldDocument.updated

Я понимаю, что вы хотите, чтобы обновленный флаг управлялся этой функцией, а не клиентом. Дайте мне знать, если это не так.

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

Вот моя попытка исправить ваш код в этом свете:

export async function landWrite(change, context) {

  const newDocument = change.after.exists ? change.after.data() : null
  const oldDocument = change.before.data()

  const updateStatusDidNotChange = newDocument && newDocument.updated === oldDocument.updated

  if (!updateStatusDidNotChange) return true; //this was a change made by me, ignore

  const batch = db.batch()

  if (!oldDocument.updated) {
    const landid = change.after.id
    const landRef = db.collection('land').doc(landid)
    batch.update(landRef, {'updated': true })
  }

  const log = {
    time: FieldValue.serverTimestamp(),
    oldDocument: oldDocument,
    newDocument: newDocument
  }

  const logRef = db.collection('land').doc(landid).collection('logs').doc()
  batch.set(logRef, log)

  return batch.commit()
  .then(success => {
    return true
  })
  .catch(error => {
    return error
  })

}

редактировать

У меня была точная проблема, и я должен был различать изменения по серверу и клиенту и игнорировать те, которые были с сервера. Я надеюсь, что вы попробуете мое предложение.

Ответ 2

Если я правильно понимаю, проблема в том, что updated флаг не указывает, на какое событие происходит обновление. Другими словами - у вас может быть несколько одновременных записей "первого этапа" в lands, и вам нужен способ устранения их неоднозначности.

Вот несколько возможных вариантов, которые я бы попробовал - от (ИМХО) худшего к лучшему:

  • Первый вариант не очень элегантен в реализации
  • Первый и второй варианты приводят к тому, что ваша функция вызывается дважды.
  • Третий вариант означает, что ваша функция вызывается только один раз, однако вы должны поддерживать отдельный параллельный документ/коллекцию вместе с lands.

Опция 1

Сохраните некоторый уникальный идентификатор в updated поле (например, хэш строкового события JSON - например, hash(JSON.stringify(oldDocument)) или пользовательский идентификатор события [если он у вас есть]).

Вариант 2

Попробуйте проверить свойство updateMask входящего события и отменить все события записи, которые влияют только на это свойство.

Вариант 3

Сохраните свой статус обновления в другом пути/коллекции документов (например, в коллекции landUpdates на том же уровне, что и в вашей коллекции lands) и настройте свою облачную функцию так, чтобы она не срабатывала по этому пути. (Если вам нужно, вы всегда можете создать вторую landUpdates функцию, которая landUpdates пути landUpdates и добавить к ней либо ту же логику, либо другую логику.)

Надеюсь это поможет!