LoginSignup
29
24

More than 5 years have passed since last update.

Cloud Firestore で複数の DocumentReference に対し Transaction を実行する

Last updated at Posted at 2018-01-17

Firestore では、 トランザクションと一括書き込み  |  Firebase にあるように、下記のコードで transaction が実行できます。

var transaction = db.runTransaction(t => {
    return t.get(cityRef)
        .then(doc => {
            // Add one person to the city population
            var newPopulation = doc.data().population + 1;
            t.update(cityRef, { population: newPopulation });
        });
})
.then(result => {
    console.log('Transaction success!');
})
.catch(err => {
    console.log('Transaction failure:', err);
});

この例だと transaction は1つの DocumentReference に対してのみ行われていますが、複数の reference に対しても Transaction を張ってみます。

複数 Transaction

3つの商品があり、同時に1つずつ在庫数を減らしているサンプルコードです。
※ TypeScript です。

const decreaseStock = async (productRefs: FirebaseFirestore.DocumentReference[]) => {
  return admin.firestore().runTransaction(async (transaction) => {
    const promises: Promise<any>[] = []
    for (const product of productRefs) {
      const t = transaction.get(product).then(tproduct => {
        const newStock = tproduct.data().stock - 1
        transaction.update(product, { stock: newStock })
      })
      promises.push(t)
    }
    return Promise.all(promises)
  })
}

const productData = [...Array(3).keys()].map(index => {
  return { price: 5000, stock: 1000, index: index }
})
const productRefs = await Promise.all(productData.map(data => {
  return admin.firestore().collection('product').add(data)
}))

await decreaseStock(productRefs)

for の中で transaction.get を複数作成して、 Promise.all でそれらを実行するようにしています。

これで3つの商品の更新に対し Transaction を実行できました。3つの商品全て stock: 999 になっており、問題なさそうです。

本当に Transaction になっているのか検証する

本当に Transaction として機能しているのか、3つの商品のうち1つの更新が失敗する状況を作ってみましょう。
Transaction として実行しているので、1つの商品の更新がこけたら他の2つの商品の更新もされないはずです。

3つある商品のうち、 index === 2 だったら throw しています。

const decreaseStock = async (productRefs: FirebaseFirestore.DocumentReference[]) => {
  return admin.firestore().runTransaction(async (transaction) => {
    const promises: Promise<any>[] = []
    for (const product of productRefs) {
      const t = transaction.get(product).then(tproduct => {
          if (tproduct.data().index === 2) {
            throw `index 2`
          } else {
            const newStock = tproduct.data().stock - 1
            transaction.update(product, { stock: newStock })
          }
      })
      promises.push(t)
    }
    return Promise.all(promises)
  })
}

const productData = [...Array(3).keys()].map(index => {
  return { price: 5000, stock: 1000, index: index }
})
const productRefs = await Promise.all(productData.map(data => {
  return admin.firestore().collection('product').add(data)
}))

await decreaseStock(productRefs)

この結果は商品3つとも stock: 1000 になっていて、ちゃんと Transaction になっています。
Transaction 内部のすべてが成功しないと update されません。

おわり

ドキュメントには1つの DocumentReference に対してしかのサンプルがありませんが、複数の DocumentReference に対しても Transaction ができました。
複数に対し同時に Transaction を張るということはロック時間が長くなってしまうので、積極的に使うのは避けたいですね。

29
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
24