🐟
FirestoreのTransactionとBatch
トランザクション
Firestoreでは、トランザクションで管理されたデータの書き込みができます。
例えば、更新前が期待する状態であるかどうかを検査してから、書き込みを行うといったことができます。ドキュメントを取得したときのstate
がpreState
であるかどうか、lastUpdateTime
が一致するかを更新条件にしています。
import * as admin from "firebase-admin"
import {firestore} from "firebase-admin"
import Transaction = firestore.Transaction
const updateNegotiationState = async (
docId: string.
preState: "waiting" | "accepted" | "rejected",
state: "waiting" | "accepted" | "rejected",
lastUpdateTime: Timestamp,
): Promise<void> => {
const db = admin.firestore()
const success = await db.runTransaction(async (tx: Transaction) => {
const doc = db.collection("contractNegotiations").doc(docId)
const snapshot = await tx.get(doc)
if (preState === snapshot.get("state")) {
tx.update(doc, {state}, {lastUpdateTime})
return true
}
return false
})
// successがtrueなら更新した
}
lastUpdateTime
は、ドキュメントのスナップショットのupdateTime
から取得できる値です。
const lastUpdateTime = snapshot.updateTime
注意点としては、書き込みは、読み取り操作を行った後でのみ可能で、読み込みと書き込みを順番ずつ行うような記述をトランザクションではできません。
DIMBULA でも同様に、空き端末の問い合わせ等でトランザクションを活用しています。
Batch
読み込みが不要で、シンプルに複数の書き込みだけを一度に行いたい場合は、Batchが使えます。
DIMBULA では、GithubへPushやPull Requestが行われた際、DIMBULA E2E の要求を行いますが、テストする条件や手順をBatchで作成しています。
import * as admin from "firebase-admin"
const db = admin.firestore()
const batch = db.batch()
const testDoc = db.collection("tests").doc()
const testId = testDoc.id
["Pixel 4a Test", "Pixel 6a Test"].forEach(planName => {
const testPlanDoc = db.collection("tests").doc(testId)
.collection("testPlans").doc()
batch.create(testPlanDoc, {planName}})
})
batch.create(testDoc, {
// repo name, owner, head-sha etc
})
await batch.commit()
サブコレクションは、親のドキュメントを指定して作成できます。他にも、更新、削除の操作も可能です。
最後に
Firestoreでは、自由にロールバックをするといったことはできないですが、runTransaction
内でトランザクションで管理したドキュメントの操作を行ったり、batch
で必要なドキュメントをコミットすることが出来ます。もし整合性が合わないことが発生すれば、エラーがスローされます。
フロントエンドからトランザクションやバッチを行う場合、セキュリティルールを記述することで、より強化できると思います。
Discussion