ชุดข้อมูล Cloud Firestore คือไฟล์ข้อมูลคงที่ที่คุณสร้างขึ้นจากเอกสาร Cloud Firestore และสแนปชอตการสืบค้น และเผยแพร่โดยคุณบน CDN บริการโฮสติ้ง หรือโซลูชันอื่นๆ ชุดข้อมูลมีทั้งเอกสารที่คุณต้องการมอบให้กับแอปไคลเอนต์ของคุณและข้อมูลเมตาเกี่ยวกับการสืบค้นที่สร้างขึ้น คุณใช้ไคลเอ็นต์ SDK เพื่อดาวน์โหลดบันเดิลผ่านเครือข่ายหรือจากที่จัดเก็บในตัวเครื่อง หลังจากนั้นคุณจะโหลดข้อมูลบันเดิลไปยังแคชในเครื่องของ Cloud Firestore เมื่อโหลดบันเดิลแล้ว แอปไคลเอ็นต์สามารถสืบค้นเอกสารจากแคชในเครื่องหรือแบ็กเอนด์ได้
ด้วยชุดข้อมูล แอปของคุณสามารถโหลดผลลัพธ์ของการสืบค้นทั่วไปได้เร็วกว่า เนื่องจากเอกสารจะพร้อมใช้งานเมื่อเริ่มต้นระบบโดยไม่จำเป็นต้องเรียกไปยังแบ็กเอนด์ Cloud Firestore หากโหลดผลลัพธ์จากแคชในเครื่อง คุณจะได้รับประโยชน์จากค่าใช้จ่ายในการเข้าถึงที่ลดลงอีกด้วย แทนที่จะจ่ายเงินสำหรับอินสแตนซ์ของแอปนับล้านเพื่อสืบค้นเอกสารเริ่มต้น 100 ฉบับเดียวกัน คุณจะจ่ายเฉพาะการสืบค้นที่จำเป็นในการรวมเอกสาร 100 ฉบับเหล่านั้นเข้าด้วยกัน
ชุดข้อมูล Cloud Firestore สร้างขึ้นเพื่อให้ทำงานได้ดีกับผลิตภัณฑ์แบ็กเอนด์ Firebase อื่นๆ ดู โซลูชันแบบผสานรวม ที่สร้างชุดรวมโดย Cloud Functions และให้บริการแก่ผู้ใช้ด้วย Firebase Hosting
การใช้บันเดิลกับแอปของคุณประกอบด้วยสามขั้นตอน:
- การสร้างบันเดิลด้วย Admin SDK
- ให้บริการชุดรวมจากที่จัดเก็บในตัวเครื่องหรือจาก CDN
- กำลังโหลดบันเดิลในไคลเอนต์
ชุดข้อมูลคืออะไร?
ชุดข้อมูลคือไฟล์ไบนารีแบบคงที่ที่คุณสร้างขึ้นเพื่อจัดทำแพ็กเก จเอกสารและ/หรือสแนปชอตการสืบค้น ตั้งแต่หนึ่งชุดขึ้นไป และคุณสามารถแยก การสืบค้นที่มีชื่อออก มาได้ ดังที่เราอธิบายไว้ด้านล่าง SDK ฝั่งเซิร์ฟเวอร์ช่วยให้คุณสร้างบันเดิลได้ และ SDK ไคลเอนต์จะมีวิธีการเพื่อให้คุณโหลดบันเดิลลงในแคชในเครื่องได้
การสืบค้นที่มีชื่อเป็นคุณลักษณะที่มีประสิทธิภาพอย่างยิ่งของกลุ่ม การสืบค้นที่มีชื่อคืออ Query
ต์การสืบค้นที่คุณสามารถแยกออกจากบันเดิลได้ จากนั้นใช้ทันทีเพื่อสืบค้นข้อมูลจากแคชหรือจากแบ็กเอนด์ ดังที่คุณทำตามปกติในส่วนใดๆ ของแอปที่พูดคุยกับ Cloud Firestore
การสร้างชุดข้อมูลบนเซิร์ฟเวอร์
การใช้ Node.js หรือ Java Admin SDK ช่วยให้คุณสามารถควบคุมสิ่งที่จะรวมไว้ในบันเดิลและวิธีการให้บริการได้อย่างสมบูรณ์
โหนด 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()
ชวา
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();
หลาม
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 // ... }
สวิฟท์
// 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
// 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 }]; }
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 // ... } }
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 // ... } }); }
ซี++
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 นาที เมื่อคุณใช้เพื่ออ่านจากแบ็กเอนด์แทนที่จะเป็นแคช คุณจะจ่ายเฉพาะการอ่านฐานข้อมูลที่จำเป็นในการอัปเดตเอกสารให้ตรงกับสิ่งที่จัดเก็บไว้ในแบ็กเอนด์ ; นั่นคือคุณจ่ายเฉพาะส่วนสันดอนเท่านั้น
อะไรต่อไป?
โปรดดูเอกสารอ้างอิง API บันเดิลข้อมูลสำหรับฝั่งไคลเอ็นต์ ( Apple , Android , เว็บ ) และฝั่งเซิร์ฟเวอร์ ( Node.js )
หากคุณยังไม่ได้ลองดู โซลูชัน Cloud Functions และ Firebase Hosting สำหรับการสร้างและให้บริการ Bundle