我做的 Firestore 交易是否正确?
Am I doing Firestore Transactions correct?
我已经按照与交易相关的 Firestore 文档进行操作,我想我已经正确地对它们进行了排序,但是在测试中我注意到我的文档有时没有得到正确更新的问题。有可能在很短的时间内将文档的多个版本提交给函数,但我只对保留最新版本感兴趣。
我的大体逻辑是这样的:
New/Updated 文档发送到云端功能
检查文档是否已存在于 Firestore 中,如果不存在,请添加它。
如果确实存在,请检查它是否比 firestore 中的实例“更新”,如果是,请更新它。
否则什么都不做
这是我的函数中试图实现此目的的代码...如果这是 correct/best 实现此目的的方法,我希望得到一些反馈:
const ocsFlight = req.body;
const procFlight = processOcsFlightEvent(ocsFlight);
try {
const ocsFlightRef = db.collection(collection).doc(procFlight.fltId);
const originalFlight = await ocsFlightRef.get();
if (!originalFlight.exists) {
const response = await ocsFlightRef.set(procFlight);
console.log("Record Added: ", JSON.stringify(procFlight));
res.status(201).json(response); // 201 - Created
return;
}
await db.runTransaction(async (t) => {
const doc = await t.get(ocsFlightRef);
const flightDoc = doc.data();
if (flightDoc.recordModified <= procFlight.recordModified) {
t.update(ocsFlightRef, procFlight);
console.log("Record Updated: ", JSON.stringify(procFlight));
res.status(200).json("Record Updated");
return;
}
console.log("Record isn't newer, nothing changed.");
console.log("Record:", JSON.stringify("Same Flight:", JSON.stringify(procFlight)));
res.status(200).json("Record isn't newer, nothing done.");
return;
});
} catch (error) {
console.log("Error:", JSON.stringify(error));
res.status(500).json(error.message);
}
虫虫
首先,您相信 req.body
的值是正确的形状。如果您还没有类型断言来反映 processOcsFlightEvent
中 /collection/someFlightId
的安全规则,您应该添加它们。这很重要,因为来自 Admin SDK 的任何数据库操作都会绕过您的安全规则。
下一个错误是在事务内向您的函数发送响应。一旦您将响应发送回客户端,您的函数就会被标记为不活动 - 资源受到严重限制,任何网络请求都可能无法完成或崩溃。由于如果检测到数据库冲突,事务可能会重试几次,因此您应确保仅在事务正确完成后才响应客户端。
您使用 set
将新航班写入 Firestore,这可能会在处理交易时出现问题,因为 set
操作将取消该位置的所有待处理交易。如果两个函数实例争夺同一个航班 ID,这将导致将错误数据写入数据库的问题。
在您当前的代码中,您将 return 对客户端的 ocsFlightRef.set()
操作结果作为 HTTP 201 Created
响应的 body。由于 DocumentReference#set()
的结果是 WriteResult
object,如果你想将它 return 发送给客户端,你需要正确地序列化它,即便如此,我认为它不会很有用,因为您似乎没有将它用于其他响应类型。相反,HTTP 201 Created
响应通常包括资源被写入的位置 Location
header 而没有 body,但这里我们将传递 [=60] 中的路径=].如果您开始使用多个数据库实例,包括相关数据库也可能会有用。
修复
获得预期结果的正确方法是在一个事务中执行整个读取->检查->写入过程,并且仅在事务完成后才响应客户端。
所以我们可以向客户端发送适当的响应,我们可以使用交易的 return 值将数据传递出去。我们将传递所做更改的类型 ("created" | "updated" | "aborted"
) 和数据库中存储内容的 recordModified
值。我们将 return 这些连同资源的 path
和适当的 message
.
如果出现错误,我们将 return 一条消息向用户显示 message
,错误的 Firebase 错误代码(如果可用)或一般消息显示为 error
属性.
// if not using express to wrangle requests, assert the correct method
if (req.method !== "POST") {
console.log(`Denied ${req.method} request`);
res.status(405) // 405 - Method Not Allowed
.set("Allow", "POST")
.end();
return;
}
const ocsFlight = req.body;
try {
// process AND type check `ocsFlight`
const procFlight = processOcsFlightEvent(ocsFlight);
const ocsFlightRef = db.collection(collection).doc(procFlight.fltId);
const { changeType, recordModified } = await db.runTransaction(async (t) => {
const flightDoc = await t.get(ocsFlightRef);
if (!flightDoc.exists) {
t.set(ocsFlightRef, procFlight);
return {
changeType: "created",
recordModified: procFlight.recordModified
};
}
// only parse the field we need rather than everything
const storedRecordModified = flightDoc.get('recordModified');
if (storedRecordModified <= procFlight.recordModified) {
t.update(ocsFlightRef, procFlight);
return {
changeType: "updated",
recordModified: procFlight.recordModified
};
}
return {
changeType: "aborted",
recordModified: storedRecordModified
};
});
switch (changeType) {
case "updated":
console.log("Record updated: ", JSON.stringify(procFlight));
res.status(200).json({ // 200 - OK
path: ocsFlightRef.path,
message: "Updated",
recordModified,
changeType
});
return;
case "created":
console.log("Record added: ", JSON.stringify(procFlight));
res.status(201).json({ // 201 - Created
path: ocsFlightRef.path,
message: "Created",
recordModified,
changeType
});
return;
case "aborted":
console.log("Outdated record discarded: ", JSON.stringify(procFlight));
res.status(200).json({ // 200 - OK
path: ocsFlightRef.path,
message: "Record isn't newer, nothing done.",
recordModified,
changeType
});
return;
default:
throw new Error("Unexpected value for 'changeType': " + changeType);
}
} catch (error) {
console.log("Error:", JSON.stringify(error));
res.status(500) // 500 - Internal Server Error
.json({
message: "Something went wrong",
// if available, prefer a Firebase error code
error: error.code || error.message
});
}
参考资料
我已经按照与交易相关的 Firestore 文档进行操作,我想我已经正确地对它们进行了排序,但是在测试中我注意到我的文档有时没有得到正确更新的问题。有可能在很短的时间内将文档的多个版本提交给函数,但我只对保留最新版本感兴趣。
我的大体逻辑是这样的:
New/Updated 文档发送到云端功能
检查文档是否已存在于 Firestore 中,如果不存在,请添加它。
如果确实存在,请检查它是否比 firestore 中的实例“更新”,如果是,请更新它。
否则什么都不做
这是我的函数中试图实现此目的的代码...如果这是 correct/best 实现此目的的方法,我希望得到一些反馈:
const ocsFlight = req.body;
const procFlight = processOcsFlightEvent(ocsFlight);
try {
const ocsFlightRef = db.collection(collection).doc(procFlight.fltId);
const originalFlight = await ocsFlightRef.get();
if (!originalFlight.exists) {
const response = await ocsFlightRef.set(procFlight);
console.log("Record Added: ", JSON.stringify(procFlight));
res.status(201).json(response); // 201 - Created
return;
}
await db.runTransaction(async (t) => {
const doc = await t.get(ocsFlightRef);
const flightDoc = doc.data();
if (flightDoc.recordModified <= procFlight.recordModified) {
t.update(ocsFlightRef, procFlight);
console.log("Record Updated: ", JSON.stringify(procFlight));
res.status(200).json("Record Updated");
return;
}
console.log("Record isn't newer, nothing changed.");
console.log("Record:", JSON.stringify("Same Flight:", JSON.stringify(procFlight)));
res.status(200).json("Record isn't newer, nothing done.");
return;
});
} catch (error) {
console.log("Error:", JSON.stringify(error));
res.status(500).json(error.message);
}
虫虫
首先,您相信 req.body
的值是正确的形状。如果您还没有类型断言来反映 processOcsFlightEvent
中 /collection/someFlightId
的安全规则,您应该添加它们。这很重要,因为来自 Admin SDK 的任何数据库操作都会绕过您的安全规则。
下一个错误是在事务内向您的函数发送响应。一旦您将响应发送回客户端,您的函数就会被标记为不活动 - 资源受到严重限制,任何网络请求都可能无法完成或崩溃。由于如果检测到数据库冲突,事务可能会重试几次,因此您应确保仅在事务正确完成后才响应客户端。
您使用 set
将新航班写入 Firestore,这可能会在处理交易时出现问题,因为 set
操作将取消该位置的所有待处理交易。如果两个函数实例争夺同一个航班 ID,这将导致将错误数据写入数据库的问题。
在您当前的代码中,您将 return 对客户端的 ocsFlightRef.set()
操作结果作为 HTTP 201 Created
响应的 body。由于 DocumentReference#set()
的结果是 WriteResult
object,如果你想将它 return 发送给客户端,你需要正确地序列化它,即便如此,我认为它不会很有用,因为您似乎没有将它用于其他响应类型。相反,HTTP 201 Created
响应通常包括资源被写入的位置 Location
header 而没有 body,但这里我们将传递 [=60] 中的路径=].如果您开始使用多个数据库实例,包括相关数据库也可能会有用。
修复
获得预期结果的正确方法是在一个事务中执行整个读取->检查->写入过程,并且仅在事务完成后才响应客户端。
所以我们可以向客户端发送适当的响应,我们可以使用交易的 return 值将数据传递出去。我们将传递所做更改的类型 ("created" | "updated" | "aborted"
) 和数据库中存储内容的 recordModified
值。我们将 return 这些连同资源的 path
和适当的 message
.
如果出现错误,我们将 return 一条消息向用户显示 message
,错误的 Firebase 错误代码(如果可用)或一般消息显示为 error
属性.
// if not using express to wrangle requests, assert the correct method
if (req.method !== "POST") {
console.log(`Denied ${req.method} request`);
res.status(405) // 405 - Method Not Allowed
.set("Allow", "POST")
.end();
return;
}
const ocsFlight = req.body;
try {
// process AND type check `ocsFlight`
const procFlight = processOcsFlightEvent(ocsFlight);
const ocsFlightRef = db.collection(collection).doc(procFlight.fltId);
const { changeType, recordModified } = await db.runTransaction(async (t) => {
const flightDoc = await t.get(ocsFlightRef);
if (!flightDoc.exists) {
t.set(ocsFlightRef, procFlight);
return {
changeType: "created",
recordModified: procFlight.recordModified
};
}
// only parse the field we need rather than everything
const storedRecordModified = flightDoc.get('recordModified');
if (storedRecordModified <= procFlight.recordModified) {
t.update(ocsFlightRef, procFlight);
return {
changeType: "updated",
recordModified: procFlight.recordModified
};
}
return {
changeType: "aborted",
recordModified: storedRecordModified
};
});
switch (changeType) {
case "updated":
console.log("Record updated: ", JSON.stringify(procFlight));
res.status(200).json({ // 200 - OK
path: ocsFlightRef.path,
message: "Updated",
recordModified,
changeType
});
return;
case "created":
console.log("Record added: ", JSON.stringify(procFlight));
res.status(201).json({ // 201 - Created
path: ocsFlightRef.path,
message: "Created",
recordModified,
changeType
});
return;
case "aborted":
console.log("Outdated record discarded: ", JSON.stringify(procFlight));
res.status(200).json({ // 200 - OK
path: ocsFlightRef.path,
message: "Record isn't newer, nothing done.",
recordModified,
changeType
});
return;
default:
throw new Error("Unexpected value for 'changeType': " + changeType);
}
} catch (error) {
console.log("Error:", JSON.stringify(error));
res.status(500) // 500 - Internal Server Error
.json({
message: "Something went wrong",
// if available, prefer a Firebase error code
error: error.code || error.message
});
}