Firestore의transaction 사용 방법 및 사용 방법

19760 단어 FirebaseFirestoretech

네, 그것도 좋아요.


다음은Firestore의transaction(이하 거래라고 부른다)의 기능과 구체적인 예를 사용한 자바스크립트의 코드와 보안 규칙을 쓰는 방법을 소개한다.

독자 대상


주로 사무라는 단어에 익숙하지 않은 초보자를 대상으로 한다.
후반부 거래 사용 시 안전 규칙의 작성은 중급자도 참고할 수 있다고 생각합니다.

컨디션

  • firebase: 9.1.3
  • react: 17.0.2
  • Firestore 거래를 구성하는 요소


    파이어스토어의 거래에는 크게 두 가지 요소가 포함돼 있다.
  • 여러 문서의 대량 쓰기
  • 문서 잠금(배타 제어)
  • 각자 어떤 물건인지 한번 봅시다.

    여러 문서에 통합 쓰기


    여러 문서의 대량 쓰기 기능은 여러 문서를 작성할 때 모든 문서가 기록되거나 기록되지 않도록 합니다.반달구지 상태가 기록되는 것을 방지하는 기능이라는 얘기다.
    로큰롤의 사용 예로, 예를 들면 EC 사이트에서 상품이 잘 팔릴 때를 고려할 때.이때'상품 재고 감소','주문 정보 쓰기'두 가지 처리 방법이 있다.쓰기 중 오류가 발생하면 이전에 변경한 내용이 무시됩니다.거래를 이용하면 재고가 줄어들었지만 주문 정보가 없거나 상반되는 상황이 발생하지 않을 수 있다.
    rollback

    잠금(배타 제어)


    잠금(배타적 제어)이란 여러 사용자가 문서를 조작할 때 한 사용자만 편집할 수 있는 기능을 말한다.
    록 음악의 사용 예로 예를 들면 EC 사이트에서 상품이 잘 팔릴 때의 일을 다시 한 번 고려한다.A와 B가 재고품 10개 상품 5개, 3개를 동시에 샀다고 가정하자.이때 최종 재고수는 2개일 것이다.
    자물쇠를 사용하지 않고 시기가 좋지 않으면 다음과 같이 재고와 일치하지 않습니다.
    no_lock
    이거 막아. 록이야.자물쇠를 사용하면 두 사용자가 거의 동시에 업데이트를 하더라도 한 쪽이 끝난 후에 다른 쪽이 업데이트를 할 수 있습니다.
    with_lock
    로큰롤의 실시 방식에는 비관적인 로큰롤과 낙관적인 로큰롤이 있다.Firestore의 웹 SDK(브라우저의 자바스크립트로 이동하면)는 낙관적인 록을 채택했다.두 가지 로큰롤의 이미지로 대충 전달된다.(이미지이기 때문에 실제 설치와 다름)

    비관 록


    비관 록은 데이터를 읽을 때 다른 사용자가 읽기와 쓰기를 금지하는 방식이다.
    pesimistic_lock

    낙관 록


    낙관적인 록은 데이터를 읽을 때 미리 내용을 저장하고 업데이트할 때 읽은 데이터를 업데이트 전의 값과 비교하며 차이가 없으면 기록하는 방법이다.
    optimistic_lock
    RDB(관계 데이터베이스)에서 대부분의 경우 여러 문서의 대량 쓰기를'사무'라고 부른다.잠금 정보는 잠금 또는 트랜잭션 분리 레벨의 컨텍스트에서 설명됩니다.

    트랜잭션 사용 예


    번호판 발행 프로그램


    예를 들어 번호판을 발행하는 프로그램을 고려해 보자.
    issue-ticket
    사용자가 "번호표 취득"단추를 눌렀을 때
  • '다음 획득번호 추가1','획득한 정리권 정보 쓰기'두 작업을 동시에 진행한다.
  • 동시에 여러 사람이 정리권을 얻으려고 할 때 정리권 번호와 일치하지 않기 위해 자물쇠를 채워야 한다.
  • 따라서 거래를 통해 정리권을 얻는다.
    데이터 구조는 이렇다.
    data_structure
    번호표를 받았을 때 그랬어요.
    change_add_value

    사무의 실현


    번호표 획득 버튼이 눌렸을 때 진행된 일은 다음과 같다.
  • 업데이트 전 정리권 번호 취득(nextTicketNum)
  • 번호판에 1 업데이트
  • 추가
  • 정리권 재제작
  • 이 세 개를 한 거래에 합병해서 실현합시다.

    트랜잭션 준비 작업


    Firebase 연결의 초기화는 다음과 같습니다.
    firebase.js
    import { initializeApp } from "firebase/app"
    import { getAuth } from "firebase/auth"
    import { getFirestore } from "firebase/firestore"
    
    // ここの設定は自身の設定に置き換える
    const firebaseConfig = {
      apiKey: "xxxxxxxxxxxxxxxxxxxx",
      authDomain: "xxxxxxxxxxx.firebaseapp.com",
      projectId: "xxxxxxxxxxx",
      storageBucket: "xxxxxxxxxxx.appspot.com",
      messagingSenderId: "xxxxxxxxxxxxxxxxxx",
      appId: "xxxxxxxxxxxxxxxxxxxx"
    };
    
    // Initialize Firebase
    const app = initializeApp(firebaseConfig)
    export const auth = getAuth()
    export const db = getFirestore()
    
    사무 기능을 사용하려면 먼저 호출runTransaction()하십시오.
    import { doc, runTransaction } from "firebase/firestore"
    import { db, auth } from "./firebase"
    
    const EventTicket = () => {
      const ticketEventId = "xxx"
    
      const handleIssueTicket = async () => {
        await runTransaction(db, async (transaction) => {
          // TODO ここにトランザクションの内容を書く
        })
      }
    
      return (
        <button onClick={handleIssueTicket}>整理券を取得する</button>
      )
    }
    
    export default EventTicket
    

    잠금(배타 제어)을 위한 업데이트 전 값 수신

    transaction.get()를 사용하여 문서를 읽고 잠급니다.
      const handleIssueTicket = async () => {
        await runTransaction(db, async (transaction) => {
          // 更新前の値を取得
          const ticketEventsDocRef = doc(db, "ticket-events", ticketEventId)
          const ticketEventsDocSnap = await transaction.get(ticketEventsDocRef)
    
          if (!ticketEventsDocSnap.exists()) {
            throw "ticketEvent document does not exist!"
          }
        })
      }
    

    문서 업데이트


    사무 업데이트 문서transaction.update()를 사용하여 문서를 작성transaction.set()합니다.
      const handleIssueTicket = async () => {
        await runTransaction(db, async (transaction) => {
          // 更新前の値を取得
          const ticketEventsDocRef = doc(db, "ticket-events", ticketEventId)
          const ticketEventsDocSnap = await transaction.get(ticketEventsDocRef)
    
          if (!ticketEventsDocSnap.exists()) {
            throw "ticketEvent document does not exist!"
          }
    
          const ticketNum = ticketEventsDocSnap.data().nextTicketNum
    
          // nextTicketNumを更新
          transaction.update(ticketEventsDocRef, { nextTicketNum: ticketNum + 1, })
    
          // 整理券を作成
          const ticketDocRef = doc(db, "ticket-events", ticketEventId, "tickets", String(ticketNum))
          transaction.set(ticketDocRef, {
            user: auth.currentUser.uid,
          })
        })
      }
    
    이상의 JavaScript 측면 설치가 완료되었습니다.Firestore는 거래를 사용하지 않는 것과 비슷한 코드로 작성할 수 있어 편리하다.

    거래 중 보안 규칙 업데이트 보장


    거래에서 업데이트를 진행하여 일치하지 않는 것을 피하려면 보안 규칙에 같은 내용을 써서 불법 방문으로 인한 데이터의 일치하지 않도록 하십시오.
    정리권 받을 때.
  • 넥스트티켓Num 증가(다음에 획득한 정리권 번호) 1
  • 업데이트 전 넥스트 TicketNum을 아이디로 정리권 제작
  • 이 정리권의 사용자와 로그인한 사용자의 uid가 일치
  • 를 참고하십시오.
    change_add_value
    이것을 안전 규칙에 써라.
    일단 틀부터 준비해.
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /ticket-events/{ticketEvent} {
          // TODO ここにticket-eventsの条件を書く
          
          match /tickets/{ticket} {
            // TODO ここにticketsの条件を書く
          }
        }
      }
    }
    
    그리고 업무를 수행할 때
  • 넥스트티켓Num 증가(다음에 획득한 정리권 번호) 1
  • 업데이트 전 넥스트 TicketNum을 아이디로 정리권 제작
  • 이 정리권의 사용자와 로그인한 사용자의 uid가 일치
  • 이 조건을 충족시키기 위해 ticket-event의 안전 규칙을 추가합니다.
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /ticket-events/{ticketEvent} {
          allow get: if true;
    
          allow create: if request.auth != null && request.auth.uid == request.resource.data.owner;
          allow update: if request.auth != null &&
            request.resource.data.diff(resource.data).affectedKeys().hasOnly(["nextTicketNum"]) &&
            request.resource.data.nextTicketNum == resource.data.nextTicketNum + 1 &&
            request.auth.uid == getAfter(
              /databases/$(database)/documents/ticket-events/$(ticketEvent)/tickets/$(resource.data.nextTicketNum)
            ).data.user;
            
          match /tickets/{ticket} {
            // TODO ここにticketsの条件を書く
          }
        }
      }
    }
    
    규칙을 해설하다.
  • request.resource.data.diff(resource.data).affectedKeys().hasOnly(["nextTicketNum"])에 업데이트 대상의 필드가 포함되지 않음nextTicketNum 의외입니다.
  • request.resource.data.nextTicketNum == resource.data.nextTicketNum + 1에서 갱신nextTicketNum할 때 보증치는 반드시 1 증가합니다.
  • getAfter(path) 함수는 거래가 끝났을 때path의 내용을 되돌려줍니다.
  • request.auth.uid == getAfter(/databases/$(database)/documents/ticket-events/$(ticketEvent)/tickets/$(resource.data.nextTicketNum)).data.user에서 거래가 완료되었을 때 이ticketEvent의 /tickets/{更新前のnextTicketNum}user는 로그인한 사용자의 id와 같습니다.
  • 이 밖에 티켓의 경로에도 조건에 부합되는 안전 규칙이 기재되어 있다.
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /ticket-events/{ticketEvent} {
          // 上と同じ
            
          match /tickets/{ticket} {
            allow create: if request.auth != null && request.auth.uid == request.resource.data.user &&
    	int(request.resource.id) > 0 &&
            request.resource.id == string(
              getAfter(
                /databases/$(database)/documents/ticket-events/$(ticketEvent)
              ).data.nextTicketNum - 1
            );
          }
        }
      }
    }
    
    규칙을 해설하다.

  • 사용자가 request.auth != null && request.auth.uid == request.resource.data.user에 로그인하고 번호판의 사용자와 로그인한 사용자의 id가 같음을 보증합니다.
  • int(arg) 함수는 매개 변수를 정수로 변환합니다.
  • request.resource.id는 새 문서의 ID입니다.

  • 보증int(request.resource.id) > 0에 작성된 문서의 ID가 0보다 큽니다.
  • string(arg) 함수는 매개 변수를 문자열로 변환합니다.
  • getAfter(path) 함수는 거래가 끝났을 때path의 내용을 되돌려줍니다.
  • getAfter(/databases/$(database)/documents/ticket-events/$(ticketEvent)).data.nextTicketNum 거래 종료 시 부모 문서nextTicketNum를 받습니다.
  • request.resource.id == string(getAfter(/databases/$(database)/documents/ticket-events/$(ticketEvent)).data.nextTicketNum - 1)에서 부모 문서가 업데이트된 값에서 1을 빼고 문자열로 변환하면 ticket의 문서 ID와 같습니다.
  • 사무 처리를 하려면 모든 관련 두 개 이상의 문서에 거래의 안전 규칙을 기록해야 한다.그걸 잊어버리면 악의적인 사용자가 불완전한 상태로 기록될 위험이 있다.

    끝맺다


    번호표 응용 프로그램은 필자가 실제로 제작한 응용 프로그램이다.가능하면 이쪽 기사도 주세요.
    https://zenn.dev/yucatio/articles/f02ec58a4f54ce

    좋은 웹페이지 즐겨찾기