获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

ชุดข้อมูล Cloud Firestore

ชุดข้อมูล Cloud Firestore เป็นไฟล์ข้อมูลคงที่ที่คุณสร้างขึ้นจากเอกสาร Cloud Firestore และสแนปชอตการสืบค้น และเผยแพร่โดยคุณบน CDN บริการโฮสติ้ง หรือโซลูชันอื่นๆ ชุดข้อมูลมีทั้งเอกสารที่คุณต้องการให้กับแอปไคลเอ็นต์และข้อมูลเมตาเกี่ยวกับการสืบค้นที่สร้าง คุณใช้ SDK ของไคลเอ็นต์เพื่อดาวน์โหลดบันเดิลผ่านเครือข่ายหรือจากที่จัดเก็บในตัวเครื่อง หลังจากนั้นคุณโหลดข้อมูลบันเดิลไปยังแคชในเครื่องของ Cloud Firestore เมื่อโหลดบันเดิลแล้ว แอปไคลเอนต์สามารถสืบค้นเอกสารจากแคชในเครื่องหรือแบ็กเอนด์ได้

ด้วยชุดข้อมูล แอปของคุณสามารถโหลดผลลัพธ์ของการสืบค้นทั่วไปได้เร็วกว่า เนื่องจากเอกสารจะพร้อมใช้งานเมื่อเริ่มต้นระบบโดยไม่จำเป็นต้องเรียกใช้แบ็กเอนด์ของ Cloud Firestore หากโหลดผลลัพธ์จากแคชในเครื่อง คุณจะได้รับประโยชน์จากค่าใช้จ่ายในการเข้าถึงที่ลดลงด้วย แทนที่จะจ่ายสำหรับอินสแตนซ์แอพนับล้านเพื่อสืบค้นเอกสาร 100 ฉบับเริ่มต้น คุณจะจ่ายเฉพาะการสืบค้นที่จำเป็นสำหรับการรวมกลุ่มเอกสาร 100 ฉบับเท่านั้น

ชุดข้อมูล Cloud Firestore สร้างขึ้นเพื่อให้ทำงานได้ดีกับผลิตภัณฑ์แบ็กเอนด์ Firebase อื่นๆ ดู โซลูชันที่ผสานรวม ซึ่งบันเดิลสร้างขึ้นโดย Cloud Functions และให้บริการแก่ผู้ใช้ด้วย Firebase Hosting

การใช้บันเดิลกับแอปของคุณมีสามขั้นตอน:

  1. การสร้างบันเดิลด้วย Admin SDK
  2. การให้บริการบันเดิลจากที่จัดเก็บในเครื่องหรือจาก CDN
  3. กำลังโหลดบันเดิลในไคลเอนต์

ชุดข้อมูลคืออะไร?

ชุดข้อมูลคือไฟล์ไบนารีแบบสแตติกที่คุณสร้างขึ้นเพื่อจัดแพ็คเกจ เอกสารและ/หรือสแน็ปช็อตการสืบค้น ตั้งแต่หนึ่งรายการขึ้นไป และคุณสามารถแยก การสืบค้นที่มีชื่อ ได้ ตามที่เราพูดถึงด้านล่าง SDK ฝั่งเซิร์ฟเวอร์ช่วยให้คุณสร้างบันเดิล และไคลเอ็นต์ SDK จัดเตรียมวิธีการเพื่อให้คุณโหลดบันเดิลไปยังแคชในเครื่องได้

การสืบค้นที่มีชื่อเป็นคุณสมบัติที่ทรงพลังเป็นพิเศษของบันเดิล การค้นหาที่มีชื่อคือออบเจ็กต์ Query ที่คุณดึงออกมาจากบันเดิล จากนั้นใช้การค้นหาข้อมูลทันทีไม่ว่าจะจากแคชหรือจากแบ็กเอนด์ เช่นเดียวกับที่คุณทำตามปกติในส่วนใดๆ ของแอปที่พูดคุยกับ Cloud Firestore

การสร้างบันเดิลข้อมูลบนเซิร์ฟเวอร์

การใช้ Node.js หรือ Java Admin SDK ช่วยให้คุณสามารถควบคุมสิ่งที่จะรวมไว้ในบันเดิลและวิธีการให้บริการได้อย่างสมบูรณ์

Node.js
var bundleId = "latest-stories";

var bundle = firestore.bundle(bundleId);

var docSnapshot = await firestore.doc('stories/stories').get();
var querySnapshot = await firestore.collection('stories').get();

// Build the bundle
// Note how querySnapshot is named "latest-stories-query"
var bundleBuffer = bundle.add(docSnapshot); // Add a document
                   .add('latest-stories-query', querySnapshot) // Add a named query.
                   .build()
      
Java
Firestore db = FirestoreClient.getFirestore(app);

// Query the 50 latest stories
QuerySnapshot latestStories = db.collection("stories")
    .orderBy("timestamp", Direction.DESCENDING)
    .limit(50)
    .get()
    .get();

// Build the bundle from the query results
FirestoreBundle bundle = db.bundleBuilder("latest-stories")
    .add("latest-stories-query", latestStories)
    .build();
      
Python
from google.cloud import firestore
from google.cloud.firestore_bundle import FirestoreBundle

db = firestore.Client()
bundle = FirestoreBundle("latest-stories")

doc_snapshot = db.collection("stories").document("news-item").get()
query = db.collection("stories")._query()

# Build the bundle
# Note how `query` is named "latest-stories-query"
bundle_buffer: str = bundle.add_document(doc_snapshot).add_named_query(
    "latest-stories-query", query,
).build()
      

ให้บริการชุดข้อมูล

คุณสามารถให้บริการบันเดิลแอปไคลเอ็นต์ของคุณจาก CDN หรือโดยการดาวน์โหลดจาก Cloud Storage เป็นต้น

สมมติว่าบันเดิลที่สร้างในส่วนก่อนหน้าได้รับการบันทึกลงในไฟล์ชื่อ bundle.txt และโพสต์บนเซิร์ฟเวอร์ ไฟล์บันเดิลนี้เหมือนกับเนื้อหาอื่นๆ ที่คุณสามารถแสดงผ่านเว็บ ดังที่แสดงไว้ที่นี่สำหรับแอป Node.js Express แบบธรรมดา

const fs = require('fs');
const server = require('http').createServer();

server.on('request', (req, res) => {
  const src = fs.createReadStream('./bundle.txt');
  src.pipe(res);
});

server.listen(8000);

กำลังโหลดบันเดิลข้อมูลในไคลเอนต์

คุณโหลดกลุ่ม Firestore โดยการดึงข้อมูลจากเซิร์ฟเวอร์ระยะไกล ไม่ว่าจะโดยการร้องขอ HTTP การเรียก API การจัดเก็บ หรือใช้เทคนิคอื่นใดในการดึงไฟล์ไบนารีบนเครือข่าย

เมื่อดึงข้อมูลแล้ว โดยใช้ SDK ไคลเอ็นต์ Cloud Firestore แอปของคุณจะเรียกใช้เมธอด loadBundle ซึ่งส่งคืนอ็อบเจ็กต์การติดตามงาน ซึ่งคุณสามารถตรวจสอบความสมบูรณ์ได้มากเมื่อคุณตรวจสอบสถานะของ Promise เมื่อเสร็จสิ้นภารกิจการโหลดบันเดิลที่สำเร็จ เนื้อหาบันเดิลจะพร้อมใช้งานในแคชในเครื่อง

เว็บ
// If you are using module bundlers.
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/firestore/bundle"; // This line enables bundle loading as a side effect.

// ...

async function fetchFromBundle() {
  // Fetch the bundle from Firebase Hosting, if the CDN cache is hit the 'X-Cache'
  // response header will be set to 'HIT'
  const resp = await fetch('/createBundle');

  // Load the bundle contents into the Firestore SDK
  await db.loadBundle(resp.body);

  // Query the results from the cache
  // Note: omitting "source: cache" will query the Firestore backend.
  const query = await db.namedQuery('latest-stories-query');
  const storiesSnap = await query.get({ source: 'cache' });

  // Use the results
  // ...
}
Swift
หมายเหตุ: ผลิตภัณฑ์นี้ไม่สามารถใช้ได้กับเป้าหมาย watchOS และ App Clip
// Utility function for errors when loading bundles.
func bundleLoadError(reason: String) -> NSError {
  return NSError(domain: "FIRSampleErrorDomain",
                 code: 0,
                 userInfo: [NSLocalizedFailureReasonErrorKey: reason])
}

// Loads a remote bundle from the provided url.
func fetchRemoteBundle(for firestore: Firestore,
                       from url: URL,
                       completion: @escaping ((Result<LoadBundleTaskProgress, Error>) -> ())) {
  guard let inputStream = InputStream(url: url) else {
    let error = self.bundleLoadError(reason: "Unable to create stream from the given url: \(url)")
    completion(.failure(error))
    return
  }

  // The return value of this function is ignored, but can be used for more granular
  // bundle load observation.
  let _ = firestore.loadBundle(inputStream) { (progress, error) in
    switch (progress, error) {

    case (.some(let value), .none):
      if value.state == .success {
        completion(.success(value))
      } else {
        let concreteError = self.bundleLoadError(
          reason: "Expected bundle load to be completed, but got \(value.state) instead"
        )
        completion(.failure(concreteError))
      }

    case (.none, .some(let concreteError)):
      completion(.failure(concreteError))

    case (.none, .none):
      let concreteError = self.bundleLoadError(reason: "Operation failed, but returned no error.")
      completion(.failure(concreteError))

    case (.some(let value), .some(let concreteError)):
      let concreteError = self.bundleLoadError(
        reason: "Operation returned error \(concreteError) with nonnull progress: \(value)"
      )
      completion(.failure(concreteError))
    }
  }
}

// Fetches a specific named query from the provided bundle.
func loadQuery(named queryName: String,
               fromRemoteBundle bundleURL: URL,
               with store: Firestore,
               completion: @escaping ((Result<Query, Error>) -> ())) {
  fetchRemoteBundle(for: store,
                    from: bundleURL) { (result) in
    switch result {
    case .success:
      store.getQuery(named: queryName) { query in
        if let query = query {
          completion(.success(query))
        } else {
          completion(
            .failure(
              self.bundleLoadError(reason: "Could not find query named \(queryName)")
            )
          )
        }
      }

    case .failure(let error):
      completion(.failure(error))
    }
  }
}

// Load a query and fetch its results from a bundle.
func runStoriesQuery() {
  let queryName = "latest-stories-query"
  let firestore = Firestore.firestore()
  let remoteBundle = URL(string: "https://example.com/createBundle")!
  loadQuery(named: queryName,
            fromRemoteBundle: remoteBundle,
            with: firestore) { (result) in
    switch result {
    case .failure(let error):
      print(error)

    case .success(let query):
      query.getDocuments { (snapshot, error) in

        // handle query results

      }
    }
  }
}
วัตถุประสงค์-C
หมายเหตุ: ผลิตภัณฑ์นี้ไม่สามารถใช้ได้กับเป้าหมาย watchOS และ App Clip
// Utility function for errors when loading bundles.
- (NSError *)bundleLoadErrorWithReason:(NSString *)reason {
  return [NSError errorWithDomain:@"FIRSampleErrorDomain"
                             code:0
                         userInfo:@{NSLocalizedFailureReasonErrorKey: reason}];
}

// Loads a remote bundle from the provided url.
- (void)fetchRemoteBundleForFirestore:(FIRFirestore *)firestore
                              fromURL:(NSURL *)url
                           completion:(void (^)(FIRLoadBundleTaskProgress *_Nullable,
                                                NSError *_Nullable))completion {
  NSInputStream *inputStream = [NSInputStream inputStreamWithURL:url];
  if (inputStream == nil) {
    // Unable to create input stream.
    NSError *error =
        [self bundleLoadErrorWithReason:
            [NSString stringWithFormat:@"Unable to create stream from the given url: %@", url]];
    completion(nil, error);
    return;
  }

  [firestore loadBundleStream:inputStream
                   completion:^(FIRLoadBundleTaskProgress * _Nullable progress,
                                NSError * _Nullable error) {
    if (progress == nil) {
      completion(nil, error);
      return;
    }

    if (progress.state == FIRLoadBundleTaskStateSuccess) {
      completion(progress, nil);
    } else {
      NSError *concreteError =
          [self bundleLoadErrorWithReason:
              [NSString stringWithFormat:
                  @"Expected bundle load to be completed, but got %ld instead",
                  (long)progress.state]];
      completion(nil, concreteError);
    }
    completion(nil, nil);
  }];
}

// Loads a bundled query.
- (void)loadQueryNamed:(NSString *)queryName
   fromRemoteBundleURL:(NSURL *)url
         withFirestore:(FIRFirestore *)firestore
            completion:(void (^)(FIRQuery *_Nullable, NSError *_Nullable))completion {
  [self fetchRemoteBundleForFirestore:firestore
                              fromURL:url
                           completion:^(FIRLoadBundleTaskProgress *progress, NSError *error) {
    if (error != nil) {
      completion(nil, error);
      return;
    }

    [firestore getQueryNamed:queryName completion:^(FIRQuery *query) {
      if (query == nil) {
        NSString *errorReason =
            [NSString stringWithFormat:@"Could not find query named %@", queryName];
        NSError *error = [self bundleLoadErrorWithReason:errorReason];
        completion(nil, error);
        return;
      }
      completion(query, nil);
    }];
  }];
}

- (void)runStoriesQuery {
  NSString *queryName = @"latest-stories-query";
  FIRFirestore *firestore = [FIRFirestore firestore];
  NSURL *bundleURL = [NSURL URLWithString:@"https://example.com/createBundle"];
  [self loadQueryNamed:queryName
   fromRemoteBundleURL:bundleURL
         withFirestore:firestore
            completion:^(FIRQuery *query, NSError *error) {
    // Handle query results
  }];
}

Java

public InputStream getBundleStream(String urlString) throws IOException {
    URL url = new URL(urlString);
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    return connection.getInputStream();
}

public void fetchBundleFrom() throws IOException {
    final InputStream bundleStream = getBundleStream("https://example.com/createBundle");
    LoadBundleTask loadTask = db.loadBundle(bundleStream);

    // Chain the following tasks
    // 1) Load the bundle
    // 2) Get the named query from the local cache
    // 3) Execute a get() on the named query
    loadTask.continueWithTask(new Continuation<LoadBundleTaskProgress, Task<Query>>() {
        @Override
        public Task<Query> then(@NonNull Task<LoadBundleTaskProgress> task) throws Exception {
            // Close the stream
            bundleStream.close();

            // Calling getResult() propagates errors
            LoadBundleTaskProgress progress = task.getResult(Exception.class);

            // Get the named query from the bundle cache
            return db.getNamedQuery("latest-stories-query");
        }
    }).continueWithTask(new Continuation<Query, Task<QuerySnapshot>>() {
        @Override
        public Task<QuerySnapshot> then(@NonNull Task<Query> task) throws Exception {
            Query query = task.getResult(Exception.class);

            // get() the query results from the cache
            return query.get(Source.CACHE);
        }
    }).addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
        @Override
        public void onComplete(@NonNull Task<QuerySnapshot> task) {
            if (!task.isSuccessful()) {
                Log.w(TAG, "Bundle loading failed", task.getException());
                return;
            }

            // Get the QuerySnapshot from the bundle
            QuerySnapshot storiesSnap = task.getResult();

            // Use the results
            // ...
        }
    });
}

Kotlin+KTX

@Throws(IOException::class)
fun getBundleStream(urlString: String?): InputStream {
    val url = URL(urlString)
    val connection = url.openConnection() as HttpURLConnection
    return connection.inputStream
}

@Throws(IOException::class)
fun fetchFromBundle() {
    val bundleStream = getBundleStream("https://example.com/createBundle")
    val loadTask = db.loadBundle(bundleStream)

    // Chain the following tasks
    // 1) Load the bundle
    // 2) Get the named query from the local cache
    // 3) Execute a get() on the named query
    loadTask.continueWithTask<Query> { task ->
        // Close the stream
        bundleStream.close()

        // Calling .result propagates errors
        val progress = task.getResult(Exception::class.java)

        // Get the named query from the bundle cache
        db.getNamedQuery("latest-stories-query")
    }.continueWithTask { task ->
        val query = task.getResult(Exception::class.java)!!

        // get() the query results from the cache
        query.get(Source.CACHE)
    }.addOnCompleteListener { task ->
        if (!task.isSuccessful) {
            Log.w(TAG, "Bundle loading failed", task.exception)
            return@addOnCompleteListener
        }

        // Get the QuerySnapshot from the bundle
        val storiesSnap = task.result

        // Use the results
        // ...
    }
}
C++
db->LoadBundle("bundle_name", [](const LoadBundleTaskProgress& progress) {
  switch(progress.state()) {
    case LoadBundleTaskProgress::State::kError: {
      // The bundle load has errored. Handle the error in the returned future.
      return;
    }
    case LoadBundleTaskProgress::State::kInProgress: {
      std::cout << "Bytes loaded from bundle: " << progress.bytes_loaded()
                << std::endl;
      break;
    }
    case LoadBundleTaskProgress::State::kSuccess: {
      std::cout << "Bundle load succeeeded" << std::endl;
      break;
    }
  }
}).OnCompletion([db](const Future<LoadBundleTaskProgress>& future) {
  if (future.error() != Error::kErrorOk) {
    // Handle error...
    return;
  }

  const std::string& query_name = "latest_stories_query";
  db->NamedQuery(query_name).OnCompletion([](const Future<Query>& query_future){
    if (query_future.error() != Error::kErrorOk) {
      // Handle error...
      return;
    }

    const Query* query = query_future.result();
    query->Get().OnCompletion([](const Future<QuerySnapshot> &){
      // ...
    });
  });
});

โปรดทราบว่าหากคุณโหลดการสืบค้นที่มีชื่อจากบันเดิลที่สร้างขึ้นก่อนหน้าน้อยกว่า 30 นาที เมื่อคุณใช้เพื่ออ่านจากแบ็กเอนด์แทนที่จะเป็นแคช คุณจะจ่ายเฉพาะการอ่านฐานข้อมูลที่จำเป็นในการอัปเดตเอกสารเพื่อให้ตรงกับสิ่งที่จัดเก็บไว้ในแบ็กเอนด์ ; นั่นคือคุณจ่ายเฉพาะเดลต้าเท่านั้น

อะไรต่อไป?