プログラミング モバイルアプリ

【Firestore】arrayUnion、arrayRemoveを使って、Firestoreで配列への要素追加(削除)

スポンサーリンク

皆さん、こんにちは!

Firestoreで配列に要素を追加・削除するなら、arrayUnion , arrayRemoveを積極的に使っていくと良いかもしれません。

要素の追加・削除はなるべくarrayUnion、arrayRemoveを使った方がいい場合

それはドキュメントおよびフィールドの同時更新を考慮する必要がないからです。

arrayUnionarrayRemoveを使うことで、同時更新によって起こりうるデータ不整合を回避することができます。

※単純なupdateによる書き換えでもTransactionが使えますが、arrayUnionを使った方がコード量は少ないです。
(課金額が変わるのかはわかりません。。。)

arrayUnion(arrayRemove)を使うケース

分かりづらいかもしれませんが、簡単にまとめてみました。

新しく配列フィールドを追加する場合
→update

既にある配列フィールドに要素を追加したい場合
┗同時更新考慮なし → update
┗同時更新考慮あり → arrayUnion(arrayRemove) または update + Transaction

arrayUnion(arrayRemove)の書き方

配列に要素を追加

// 要素の追加
const db = firebase.firestore();
db.collection('users').doc('room1')
  .update({
    users: firebase.firestore.FieldValue.arrayUnion('user1', 'user2'), // usersフィールド(配列)に要素'user1','user2'を追加
  });

配列から要素を削除

// 要素の削除
const db = firebase.firestore();
db.collection('users').doc('room1')
  .update({
    users: firebase.firestore.FieldValue. arrayRemove('user1', 'user2'), // usersフィールド(配列)から要素'user1','user2'を削除
  });

なぜ、同時更新によるデータ不整合が発生するのか?

排他制御を聞いたことある方はあれかと思うはずです。

例えば、以下の流れでfirestoreの更新をするケースを考えてみます。

 

① ユーザーA(id:127)classコレクションのclass10ドキュメントを取得

② 取得したstudentsフィールド(配列)に127という要素を追加し、新しい配列を作成

③ 新しい配列でstudentsフィールド(配列)を更新する

コードは以下のようになります。

// 例
// 取得するドキュメントを指定
const docRef = db.collection('class').doc('class10'); 

const myId = 127;
// ①firestoreからドキュメントを取得する
docRef.get().then((doc) => {
  if (doc.exists) { // ドキュメントが存在するか
    // ②新しい配列を作成
      const newStudents = doc.data().students; // students: [101, 104, 123, 156]
      newStudents.push(127); 
      // ③studentsフィールド(配列)をnewStudentsで置き換える
      docRef.update({
        students: newStudents,
      }).then(() => {
        console.log('更新完了'); // students: [101, 104, 123, 156, 127]
    })
  } else {
      console.log('ドキュメントが存在しない');
  }
}).catch((error) => {
  console.log('ドキュメントの取得失敗:', error);
});

一見問題なさそうに見えます。

しかし、以下のケースでデータの不整合が発生してしまう可能性があります。

① ユーザーA(id:127)がclassコレクションのclass10ドキュメントを取得
students: [101, 104, 123, 156]

←←←①-2  ユーザーB(id:192)がclassコレクションのclass10ドキュメントを取得
students: [101, 104, 123, 156]

② ユーザーAがclass10ドキュメントのstudentsフィールド(配列)に127という要素を追加し、新しい配列を作成

③ ユーザーAが新しい配列でstudentsフィールド(配列)を更新する
students: [101, 104, 123, 156, 127]

←←←②-2 ユーザーBがclass10ドキュメントのstudentsフィールド(配列)に192という要素を追加し、新しい配列を作成

←←←③-2 ユーザーBが新しい配列でstudentsフィールド(配列)を更新する
students: [101, 104, 123, 156, 192]

ユーザーAの①開始直後〜③完了前の間にユーザーBの①-2が処理されてしまうと、
ユーザーBはユーザーAの反映される前の古いデータでフィールドを更新してしまいます。

コード上だと、以下の間で同時更新しようとすると、起こります

// データ不整合発生シナリオ
// 取得するドキュメントを指定
const docRef = db.collection('class').doc('class10'); 

const myId = 127;
// ①ユーザーA(id:127)がclassコレクションのclass10ドキュメントを取得
docRef.get().then((doc) => {  
// ----------ここから----------
    // この間、①-2ユーザーB(id:192)がclassコレクションのclass10ドキュメントを取得
    if (doc.exists) {
      // ②ユーザーAがclass10ドキュメントのstudentsフィールド(配列)に127という要素を追加し、新しい配列を作成
      const newStudents = doc.data().students; // students: [101, 104, 123, 156]
      newStudents.push(myId);
      // ③ユーザーAが新しい配列でstudentsフィールド(配列)を更新する
      docRef.update({
        students: newStudents,
// ----------ここまで----------
      }).then(() => {
        console.log('更新完了'); 
        // students: [101, 104, 123, 156, 127]に一度更新されるが、
        // 直後に②-2, ③-2の処理が実行され、
        // students: [101, 104, 123, 156, 192] とユーザーBのデータで上書きされてしまう
    })
  } else {
      console.log('ドキュメントが存在しない');
  }
}).catch((error) => {
  console.log('ドキュメントの取得失敗:', error);
});

 

このようにして、データの不整合が発生してしまいます。

これを防ぐために、先ほどトランザクションやarrayUnionなど を使います。

 

最後に・・

図がなくて、説明が分かりづらかったと思いますので、図を貼っておきます。

RDBの説明ですが、僕が説明したこととほとんど変わりません。

出典:@IT

 

スポンサーリンク

-プログラミング, モバイルアプリ
-,

© 2024 エンジニア×ライフハック Powered by AFFINGER5