1. 總覽
在本實驗室中,您會將無伺服器資料庫(Spanner 和 Firestore) 與在 Cloud Run 中執行的應用程式(Go 和 Node.js) 整合。Cymbal Eats 應用程式包含多項服務,這些服務會在 Cloud Run 上執行。在下列步驟中,您將設定服務,以使用 Cloud Spanner 關聯式資料庫和 Cloud Firestore (NoSQL 文件資料庫)。使用無伺服器產品處理資料層和應用程式執行階段,可省去所有基礎架構管理工作,讓您專心建構應用程式,不必擔心固定成本。
2. 學習目標
在本實驗室中,您將瞭解如何執行下列操作:
- 整合 Spanner
- 啟用 Spanner 代管服務
- 整合至程式碼
- 部署連線至 Spanner 的程式碼
- 整合 Firestore
- 啟用 Firestore Managed Services
- 整合至程式碼
- 部署連線至 Firestore 的程式碼
3. 設定和需求
自修實驗室環境設定
- 登入 Google Cloud 控制台,然後建立新專案或重複使用現有專案。如果沒有 Gmail 或 Google Workspace 帳戶,請先建立帳戶。



- 專案名稱是這個專案參與者的顯示名稱。這是 Google API 未使用的字元字串。你隨時可以更新。
- 專案 ID 在所有 Google Cloud 專案中都是不重複的,而且設定後即無法變更。Cloud 控制台會自動產生專屬字串,通常您不需要在意該字串為何。在大多數程式碼研究室中,您需要參照專案 ID (通常標示為
PROJECT_ID)。如果您不喜歡產生的 ID,可以產生另一個隨機 ID。你也可以嘗試使用自己的名稱,看看是否可用。完成這個步驟後就無法變更,且專案期間會維持不變。 - 請注意,有些 API 會使用第三個值,也就是「專案編號」。如要進一步瞭解這三種值,請參閱說明文件。
- 接著,您需要在 Cloud 控制台中啟用帳單,才能使用 Cloud 資源/API。完成這個程式碼研究室的費用不高,甚至可能完全免費。如要關閉資源,避免在本教學課程結束後繼續產生費用,請刪除您建立的資源或專案。Google Cloud 新使用者可參加 $300 美元的免費試用計畫。
設定環境
- 建立專案 ID 變數
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
export SPANNER_INSTANCE=inventory-instance
export SPANNER_DB=inventory-db
export REGION=us-east1
export SPANNER_CONNECTION_STRING=projects/$PROJECT_ID/instances/$SPANNER_INSTANCE/databases/$SPANNER_DB
- 啟用 Spanner、Cloud Run、Cloud Build 和 Artifact Registry API
gcloud services enable \
compute.googleapis.com \
spanner.googleapis.com \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
firestore.googleapis.com \
appengine.googleapis.com \
artifactregistry.googleapis.com
- 複製存放區
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git
- 前往目錄
cd cymbal-eats/inventory-service/spanner
4. 建立及設定 Spanner 執行個體
Spanner 是庫存服務後端的關聯式資料庫。您將在下列步驟中建立 Spanner 執行個體、資料庫和結構定義。
建立執行個體
- 建立 Cloud Spanner 執行個體
gcloud spanner instances create $SPANNER_INSTANCE --config=regional-${REGION} \
--description="Cymbal Menu Inventory" --nodes=1
輸出範例
Creating instance...done.
- 確認 Spanner 執行個體設定是否正確
gcloud spanner instances list
輸出範例
NAME: inventory-instance DISPLAY_NAME: Cymbal Menu Inventory CONFIG: regional-us-east1 NODE_COUNT: 1 PROCESSING_UNITS: 100 STATE: READY
建立資料庫和結構定義
建立新資料庫,並使用 Google 標準 SQL 的資料定義語言 (DDL) 建立資料庫結構定義。
- 建立 DDL 檔案
echo "CREATE TABLE InventoryHistory (ItemRowID STRING (36) NOT NULL, ItemID INT64 NOT NULL, InventoryChange INT64, Timestamp TIMESTAMP) PRIMARY KEY(ItemRowID)" >> table.ddl
- 建立 Spanner 資料庫
gcloud spanner databases create $SPANNER_DB \
--instance=$SPANNER_INSTANCE \
--ddl-file=table.ddl
輸出範例
Creating database...done.
驗證資料庫狀態和結構定義
- 查看資料庫狀態
gcloud spanner databases describe $SPANNER_DB \
--instance=$SPANNER_INSTANCE
輸出範例
createTime: '2022-04-22T15:11:33.559300Z' databaseDialect: GOOGLE_STANDARD_SQL earliestVersionTime: '2022-04-22T15:11:33.559300Z' encryptionInfo: - encryptionType: GOOGLE_DEFAULT_ENCRYPTION name: projects/cymbal-eats-7-348013/instances/menu-inventory/databases/menu-inventory state: READY versionRetentionPeriod: 1h
- 查看資料庫的結構定義
gcloud spanner databases ddl describe $SPANNER_DB \
--instance=$SPANNER_INSTANCE
輸出範例
CREATE TABLE InventoryHistory ( ItemRowID STRING(36) NOT NULL, ItemID INT64 NOT NULL, InventoryChange INT64, TimeStamp TIMESTAMP, ) PRIMARY KEY(ItemRowID);
5. 整合 Spanner
在本節中,您將瞭解如何將 Spanner 整合至應用程式。此外,SQL Spanner 提供用戶端程式庫、JDBC 驅動程式、R2DBC 驅動程式、REST API 和 RPC API,可讓您將 Spanner 整合至任何應用程式。
在下一節中,您將使用 Go 用戶端程式庫,在 Spanner 中安裝、驗證及修改資料。
安裝用戶端程式庫
Cloud Spanner 用戶端程式庫會自動使用應用程式預設憑證 (ADC) 尋找服務帳戶憑證,讓您更輕鬆地與 Cloud Spanner 整合
設定驗證方法
Google Cloud CLI 和 Google Cloud 用戶端程式庫會自動偵測是否在 Google Cloud 上執行,並使用目前 Cloud Run 修訂版本的執行階段服務帳戶。這項策略稱為「應用程式預設憑證」,可讓程式碼在多個環境中攜帶使用。
不過,最佳做法是為函式指派由使用者管理的服務帳戶,而非預設服務帳戶,藉此建立專屬身分。
- 將 Spanner 資料庫管理員角色授予服務帳戶
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
--role="roles/spanner.databaseAdmin"
輸出範例
Updated IAM policy for project [cymbal-eats-6422-3462]. [...]
使用用戶端程式庫
Spanner 用戶端程式庫可簡化與 Spanner 整合的複雜性,並支援多種熱門程式設計語言。
建立 Spanner 用戶端
Spanner 用戶端是用於讀取資料和將資料寫入 Cloud Spanner 資料庫的用戶端。除了 Close 方法外,用戶端可安全地同時使用。
以下程式碼片段會建立 Spanner 用戶端
main.go
var dataClient *spanner.Client ... dataClient, err = spanner.NewClient(ctx, databaseName)
您可以將 Client 視為資料庫連線,所有與 Cloud Spanner 的互動都必須透過 Client。一般而言,您會在應用程式啟動時建立 Client,接著重複使用該 Client 進行讀取、寫入及執行交易。每個用戶端都會使用 Cloud Spanner 中的資源。
修改資料
您可以透過多種方式,在 Spanner 資料庫中插入、更新及刪除資料。以下列出可用的方法。
在本實驗室中,您將使用變異來修改 Spanner 中的資料。
Spanner 中的異動
Mutation 是變異作業的容器。Mutation 代表一系列的插入、更新和刪除作業,Cloud Spanner 會以不可分割的形式,將這些作業套用至 Cloud Spanner 資料庫中不同的資料列和資料表。
main.go
m := []*spanner.Mutation{}
m = append(m, spanner.Insert(
"inventoryHistory",
inventoryHistoryColumns,
[]interface{}{uuid.New().String(), element.ItemID, element.InventoryChange, time.Now()}))
這段程式碼會將新資料列插入商品目錄記錄表。
部署及測試
Spanner 設定完成並檢查過重要程式碼元素後,即可將應用程式部署至 Cloud Run。
將應用程式部署至 Cloud Run
Cloud Run 只要一個指令,就能自動建構、推送及部署程式碼。在下列指令中,您將在 run 服務上呼叫 deploy 指令,並傳遞執行中應用程式使用的變數,例如您先前建立的 SPANNER_CONNECTION_STRING。
- 按一下「開啟終端機」
- 將庫存服務部署至 Cloud Run
gcloud run deploy inventory-service \
--source . \
--region $REGION \
--update-env-vars SPANNER_CONNECTION_STRING=$SPANNER_CONNECTION_STRING \
--allow-unauthenticated \
--project=$PROJECT_ID \
--quiet
輸出範例
Service [inventory-service] revision [inventory-service-00001-sug] has been deployed and is serving 100 percent of traffic. Service URL: https://inventory-service-ilwytgcbca-uk.a.run.app
- 儲存服務網址
INVENTORY_SERVICE_URL=$(gcloud run services describe inventory-service \
--platform managed \
--region $REGION \
--format=json | jq \
--raw-output ".status.url")
測試 Cloud Run 應用程式
插入項目
- 在 Cloud Shell 中輸入下列指令。
POST_URL=$INVENTORY_SERVICE_URL/updateInventoryItem
curl -i -X POST ${POST_URL} \
--header 'Content-Type: application/json' \
--data-raw '[
{
"itemID": 1,
"inventoryChange": 5
}
]'
輸出範例
HTTP/2 200 access-control-allow-origin: * content-type: application/json x-cloud-trace-context: 10c32f0863d26521497dc26e86419f13;o=1 date: Fri, 22 Apr 2022 21:41:38 GMT server: Google Frontend content-length: 2 OK
查詢項目
- 查詢商品目錄服務
GET_URL=$INVENTORY_SERVICE_URL/getAvailableInventory
curl -i ${GET_URL}
回應範例
HTTP/2 200
access-control-allow-origin: *
content-type: text/plain; charset=utf-8
x-cloud-trace-context: b94f921e4c2ae90210472c88eb05ace8;o=1
date: Fri, 22 Apr 2022 21:45:50 GMT
server: Google Frontend
content-length: 166
[{"ItemID":1,"Inventory":5}]
6. Spanner 概念
Cloud Spanner 使用宣告式 SQL 陳述式查詢資料庫。SQL 陳述式會指出使用者想要的內容,不會說明如何取得結果。
- 在終端機中輸入這項指令,查詢先前建立的記錄。
gcloud spanner databases execute-sql $SPANNER_DB \
--instance=$SPANNER_INSTANCE \
--sql='SELECT * FROM InventoryHistory WHERE ItemID=1'
輸出範例
ItemRowID: 1
ItemID: 1
InventoryChange: 3
Timestamp:
查詢執行計畫
「查詢執行計畫」是 Spanner 用來取得結果的一連串步驟。取得特定 SQL 陳述式結果的方式可能有很多種。您可以在控制台和用戶端程式庫中存取查詢執行計畫。如要瞭解 Spanner 如何處理 SQL 查詢,請按照下列步驟操作:
- 在控制台中開啟 Cloud Spanner 執行個體頁面。
- 前往 Cloud Spanner 執行個體
- 按一下 Cloud Spanner 執行個體的名稱。在資料庫部分,選取要查詢的資料庫。
- 按一下「查詢」。
- 在查詢編輯器中輸入下列查詢
SELECT * FROM InventoryHistory WHERE ItemID=1
- 按一下「執行」
- 按一下「說明」
Cloud 控制台會顯示查詢的視覺化執行計畫。

查詢最佳化工具
Cloud Spanner 查詢最佳化工具會比較替代執行計畫,然後選取最有效率的計畫。隨著時間推移,查詢最佳化工具會不斷演進,擴大查詢執行計畫的選擇範圍,並提高估計值的準確度,進而產生更有效率的查詢執行計畫。
Cloud Spanner 會以新版查詢最佳化工具的形式,推出最佳化工具更新。根據預設,每個資料庫都會在最新版最佳化工具發布後 30 天內,開始使用該版本。
如要在 gcloud spanner 中執行查詢時查看所用版本,請將 –query-mode 旗標設為 PROFILE
- 輸入下列指令即可查看最佳化工具版本
gcloud spanner databases execute-sql $SPANNER_DB --instance=$SPANNER_INSTANCE \
--query-mode=PROFILE --sql='SELECT * FROM InventoryHistory'
輸出範例
TOTAL_ELAPSED_TIME: 6.18 msecs
CPU_TIME: 5.17 msecs
ROWS_RETURNED: 1
ROWS_SCANNED: 1
OPTIMIZER_VERSION: 3
RELATIONAL Distributed Union
(1 execution, 0.11 msecs total latency)
subquery_cluster_node: 1
|
+- RELATIONAL Distributed Union
| (1 execution, 0.09 msecs total latency)
| call_type: Local, subquery_cluster_node: 2
| |
| \- RELATIONAL Serialize Result
| (1 execution, 0.08 msecs total latency)
| |
| +- RELATIONAL Scan
| | (1 execution, 0.08 msecs total latency)
| | Full scan: true, scan_target: InventoryHistory, scan_type: TableScan
| | |
| | +- SCALAR Reference
| | | ItemRowID
| | |
| | +- SCALAR Reference
| | | ItemID
| | |
| | +- SCALAR Reference
| | | InventoryChange
| | |
| | \- SCALAR Reference
| | Timestamp
| |
| +- SCALAR Reference
| | $ItemRowID
| |
| +- SCALAR Reference
| | $ItemID
| |
| +- SCALAR Reference
| | $InventoryChange
| |
| \- SCALAR Reference
| $Timestamp
|
\- SCALAR Constant
true
ItemRowID: 1
ItemID: 1
InventoryChange: 3
Timestamp:
更新最佳化工具版本
本實驗室使用的最新版本為 4 版。接著,您將更新 Spanner 資料表,以便查詢最佳化工具使用第 4 版。
- 更新最佳化工具
gcloud spanner databases ddl update $SPANNER_DB \
--instance=$SPANNER_INSTANCE \
--ddl='ALTER DATABASE InventoryHistory
SET OPTIONS (optimizer_version = 4)'
輸出範例
Schema updating...done.
- 輸入下列指令,查看最佳化工具版本更新
gcloud spanner databases execute-sql $SPANNER_DB --instance=$SPANNER_INSTANCE \
--query-mode=PROFILE --sql='SELECT * FROM InventoryHistory'
輸出範例
TOTAL_ELAPSED_TIME: 8.57 msecs CPU_TIME: 8.54 msecs ROWS_RETURNED: 1 ROWS_SCANNED: 1 OPTIMIZER_VERSION: 4 [...]
在 Metrics Explorer 中以圖表呈現查詢最佳化工具版本
您可以使用 Cloud Console 中的 Metrics Explorer,以視覺化方式呈現資料庫執行個體的查詢次數。您可以查看每個資料庫使用的最佳化工具版本。
- 前往 Cloud 控制台的 Monitoring,然後在左選單中選取「指標探索工具」。
- 在「資源類型」欄位中,選取 Cloud Spanner 執行個體。
- 在「指標」欄位中,選取「查詢次數」並套用。
- 在「Group By」(依據分組) 欄位中,選取資料庫、optimizer_version 和狀態。

7. 建立及設定 Firestore 資料庫
Firestore 是 NoSQL 文件資料庫,專為自動調整資源配置、提供高效能,以及協助輕鬆開發應用程式而打造。雖然 Firestore 介面有許多與傳統資料庫相同的功能,但 NoSQL 資料庫說明資料物件關係的方式與傳統資料庫不同。
在下列工作中,您將建立由 Firestore 支援的訂購服務 Cloud Run 應用程式。訂購服務會呼叫上一節建立的商品目錄服務,在開始訂購前查詢 Spanner 資料庫。這項服務可確保有足夠的廣告空間,並完成訂單。

8. Firestore 概念
資料模型
Firestore 資料庫是由集合和文件組成。

文件
每份文件都含有鍵/值組合。Firestore 適合儲存大量小型文件。

集合
您必須將所有文件儲存在集合中。文件可以包含子集合和巢狀物件,包括字串等原始欄位,或清單等複雜物件。

建立 Firestore 資料庫
- 建立 Firestore 資料庫
gcloud firestore databases create --location=$REGION
輸出範例
Success! Selected Google Cloud Firestore Native database for cymbal-eats-6422-3462
9. 將 Firestore 整合至應用程式
在本節中,您將更新服務帳戶、新增 Firestore 存取服務帳戶、檢查及部署 Firestore 安全性規則,並查看 Firestore 中的資料修改方式。
設定驗證方法
- 將 Datastore 使用者角色授予服務帳戶
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
--role="roles/datastore.user"
輸出範例
Updated IAM policy for project [cymbal-eats-6422-3462].
Firestore 安全性規則
安全性規則提供存取權控管和資料驗證功能,格式簡單明瞭。
- 前往 order-service/starter-code 目錄
cd ~/cymbal-eats/order-service
- 在 Cloud 編輯器中開啟 firestore.rules 檔案
cat firestore.rules
firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents { ⇐ All database
match /{document=**} { ⇐ All documents
allow read: if true; ⇐ Allow reads
}
match /{document=**} {
allow write: if false; ⇐ Deny writes
}
}
}
警告:建議您限制 Firestore 儲存空間的存取權。為配合本實驗室的學習目標,我們允許所有讀取作業。不建議在正式環境中採用此設定。
啟用 Firestore Managed Services
- 按一下「開啟終端機」
- 使用目前的專案 ID 建立 .firebaserc 檔案。部署目標的設定會儲存在專案目錄的 .firebaserc 檔案中。
firebaserc.tmpl
sed "s/PROJECT_ID/$PROJECT_ID/g" firebaserc.tmpl > .firebaserc
- 下載 Firebase 二進位檔
curl -sL https://firebase.tools | upgrade=true bash
輸出範例
-- Checking for existing firebase-tools on PATH... Your machine already has firebase-tools@10.7.0 installed. Nothing to do. -- All done!
- 部署 Firestore 規則。
firebase deploy
輸出範例
=== Deploying to 'cymbal-eats-6422-3462'... i deploying firestore i cloud.firestore: checking firestore.rules for compilation errors... ✔ cloud.firestore: rules file firestore.rules compiled successfully i firestore: uploading rules firestore.rules... ✔ firestore: released rules firestore.rules to cloud.firestore ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/cymbal-eats-6422-3462/overview
修改資料
Firestore 會隱含建立集合和文件。只要將資料指派給集合中的文件即可。如果集合或文件不存在,Firestore 會建立該項目。
將資料新增至 Firestore
您可以透過多種方式將資料寫入 Cloud Firestore:
- 在集合中設定文件的資料,並明確指定文件 ID。
- 將新文件新增至集合。在這種情況下,Cloud Firestore 會自動產生文件 ID。
- 建立空白文件 (內含自動產生的 ID),稍後再指派資料。
下一節將說明如何使用 set 方法建立文件。
設定文件
使用 set() 方法建立文件。使用 set() 方法時,您必須指定要建立的文件 ID。
請參閱下方的程式碼片段。
index.js
const orderDoc = db.doc(`orders/123`);
await orderDoc.set({
orderNumber: 123,
name: Anne,
address: 555 Bright Street,
city: Mountain View,
state: CA,
zip: 94043,
orderItems: [id: 1],
status: 'New'
});
這段程式碼會建立文件,並指定使用者產生的文件 ID 123。如要讓 Firestore 代表您產生 ID,請使用 add() 或 create() 方法。
更新文件
您可以使用 update() 更新部分文件欄位,不必覆寫整份文件。
在下列程式碼片段中,程式碼會更新訂單 123
index.js
const orderDoc = db.doc(`orders/123`); await orderDoc.update(name: "Anna");
刪除文件
在 Firestore 中,您可以刪除集合、文件或文件中的特定欄位。如要刪除文件,請使用 delete() 方法。
以下程式碼片段會刪除訂單 123。
index.js
const orderDoc = db.doc(`orders/123`); await orderDoc.delete();
10. 部署及測試
在本節中,您將應用程式部署至 Cloud Run,並測試建立、更新和刪除方法。
將應用程式部署至 Cloud Run
- 將網址儲存在 INVENTORY_SERVICE_URL 變數中,以便與 Inventory Service 整合
INVENTORY_SERVICE_URL=$(gcloud run services describe inventory-service \
--region=$REGION \
--format=json | jq \
--raw-output ".status.url")
- 部署訂單服務
gcloud run deploy order-service \
--source . \
--platform managed \
--region $REGION \
--allow-unauthenticated \
--project=$PROJECT_ID \
--set-env-vars=INVENTORY_SERVICE_URL=$INVENTORY_SERVICE_URL \
--quiet
輸出範例
[...] Done. Service [order-service] revision [order-service-00001-qot] has been deployed and is serving 100 percent of traffic. Service URL: https://order-service-3jbm3exegq-uk.a.run.app
測試 Cloud Run 應用程式
建立文件
- 將訂單服務應用程式的網址儲存至變數,以供測試
ORDER_SERVICE_URL=$(gcloud run services describe order-service \
--platform managed \
--region $REGION \
--format=json | jq \
--raw-output ".status.url")
- 建立訂單要求,並將新訂單發布至 Firestore 資料庫
curl --request POST $ORDER_SERVICE_URL/order \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Jane Doe",
"email": "Jane.Doe-cymbaleats@gmail.com",
"address": "123 Maple",
"city": "Buffalo",
"state": "NY",
"zip": "12346",
"orderItems": [
{
"id": 1
}
]
}'
輸出範例
{"orderNumber":46429}
儲存訂單編號,稍後會用到
export ORDER_NUMBER=<value_from_output>
查看結果
在 Firestore 中查看結果
- 前往 Firestore 控制台
- 按一下「資料」

更新文件
提交的訂單未包含數量。
- 更新記錄並新增數量鍵/值組合
curl --location -g --request PATCH $ORDER_SERVICE_URL/order/${ORDER_NUMBER} \
--header 'Content-Type: application/json' \
--data-raw '{
"orderItems": [
{
"id": 1,
"quantity": 1
}
]
}'
輸出範例
{"status":"success"}
查看結果
在 Firestore 中查看結果
- 前往 Firestore 控制台
- 按一下「資料」

刪除文件
- 從 Firestore 訂單集合中刪除項目 46429
curl --location -g --request DELETE $ORDER_SERVICE_URL/order/${ORDER_NUMBER}
查看結果
- 前往 Firestore 控制台
- 按一下「資料」

11. 恭喜!
恭喜,您已完成本實驗室!
後續步驟:
探索其他 Cymbal Eats 程式碼研究室:
- 使用 Eventarc 觸發 Cloud Workflows
- 從 Cloud Storage 觸發事件處理程序
- 從 Cloud Run 連線至 Private CloudSQL
- 使用 Identity-Aware Proxy (IAP) 保護無伺服器應用程式
- 使用 Cloud Scheduler 觸發 Cloud Run 工作
- 安全地部署至 Cloud Run
- 保護 Cloud Run 傳入流量
- 從 GKE Autopilot 連線至私有 AlloyDB
清除所用資源
如要避免系統向您的 Google Cloud 帳戶收取本教學課程所用資源的費用,請刪除含有相關資源的專案,或者保留專案但刪除個別資源。
刪除專案
如要避免付費,最簡單的方法就是刪除您為了本教學課程所建立的專案。