NodeJS 基於 Dapr 構建雲原生微服務應用,從 0 到 1 快速上手指南

2022-07-23 06:00:40

Dapr 是一個可移植的、事件驅動的執行時,它使任何開發人員能夠輕鬆構建出彈性的、無狀態和有狀態的應用程式,並可執行在雲平臺或邊緣計算中,它同時也支援多種程式語言和開發框架。Dapr 確保開發人員專注於編寫業務邏輯,不必分神解決分散式系統難題,從而顯著提高了生產力。Dapr 降低了構建微服務架構類現代雲原生應用的門檻。

系列

安裝 Dapr CLI

MacOS & Dapr 1.8:

sudo curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | /bin/bash

Linux/Windows 安裝方式:

本地環境中初始化 Dapr

Dapr 初始化包括:

  1. 執行一個用於狀態儲存和訊息代理的 Redis 容器範例
  2. 執行一個用於提供可觀察性的 Zipkin 容器範例
  3. 建立具有上述元件定義的預設元件資料夾
  4. 執行用於本地 actor(我們的服務) 支援的 Dapr placement 服務容器範例

執行初始化 CLI 命令

dapr init

驗證 Dapr 版本

dapr -v
CLI version: 1.8.0
Runtime version: 1.8.0

驗證容器是否正在執行

如前所述,dapr init 命令會啟動幾個容器,這些容器將幫助您開始使用 Dapr。 驗證您有執行 daprio/dapropenzipkin/zipkinredis 映像的容器範例:

驗證元件目錄是否已初始化

dapr init 上,CLI 還會建立一個預設元件資料夾,其中包含幾個 YAML 檔案,其中包含狀態儲存、Pub/subZipkin 的定義。Dapr sidecar 將讀取這些元件並使用:

  • 用於狀態管理和訊息傳遞的 Redis 容器。
  • 用於收集蹤跡的 Zipkin 容器。

通過開啟您的元件目錄進行驗證:

  • Windows, 在 %UserProfile%\.dapr
  • Linux/MacOS, 在 ~/.dapr
ls $HOME/.dapr
bin components config.yaml

使用 Dapr API

執行 Dapr sidecar 並試用 state API

執行 Dapr sidecar

dapr run 命令啟動一個應用程式,以及一個 sidecar。

啟動一個 Dapr sidecar,它將在埠 3500 上偵聽名為 myapp 的空白應用程式:

dapr run --app-id myapp --dapr-http-port 3500

由於沒有使用上述命令定義自定義元件資料夾,因此 Dapr 使用在 dapr init 流程期間建立的預設元件定義。

儲存狀態

使用物件更新狀態。新狀態將如下所示:

[
  {
    "key": "name",
    "value": "Bruce Wayne"
  }
]

請注意,包含在狀態中的每個物件都有一個分配有值為 namekey。您將在下一步中使用該 key

使用以下命令儲存新的狀態物件:

curl -X POST -H "Content-Type: application/json" -d '[{ "key": "name", "value": "Bruce Wayne"}]' http://localhost:3500/v1.0/state/statestore

獲取狀態

使用帶有 key 為 name 的狀態檢索您剛剛儲存在 state 中的物件。在同一終端視窗中,執行以下命令:

curl http://localhost:3500/v1.0/state/statestore/name

檢視狀態如何儲存在 Redis 中

docker exec -it dapr_redis redis-cli

列出 Redis 鍵以檢視 Dapr 如何使用您提供給 dapr run 的 app-id 作為 key 的字首建立鍵值對:

keys *
"myapp||name"

執行以下命令檢視狀態值:

hgetall "myapp||name"

1) "data"
2) "\"Bruce Wayne\""
3) "version"
4) "1"

使用以下命令退出 Redis CLI:

exit

刪除狀態

在同一終端視窗中,從狀態儲存中刪除 name 狀態物件。

curl -v -X DELETE -H "Content-Type: application/json" http://localhost:3500/v1.0/state/statestore/name

上手實戰指南

所有官方範例筆者均在 MacOS/NodeJs v16.16.0 下實戰完成。

1. 服務呼叫

使用 Dapr 的服務呼叫構建塊,您的應用程式可以與其他應用程式可靠且安全地通訊。

範例倉庫

git clone https://github.com/dapr/quickstarts.git

執行 order-processor 服務

從 quickstarts 的根目錄導航到 order-processor 目錄。

cd service_invocation/javascript/http/order-processor

安裝依賴項:

npm install

與 Dapr sidecar 一起執行 order-processor 服務。

dapr run --app-port 5001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- npm start
app.post('/orders', (req, res) => {
    console.log("Order received:", req.body);
    res.sendStatus(200);
});

執行 checkout 服務

在新的終端視窗中,從 quickstarts 根目錄導航到 checkout 目錄。

cd service_invocation/javascript/http/checkout

安裝依賴項:

npm install

與 Dapr sidecar 一起執行 checkout 服務。

dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- npm start

checkout 服務中,您會注意到無需重寫您的應用程式程式碼即可使用 Dapr 的服務呼叫。您可以通過簡單地新增 dapr-app-id header 來啟用服務呼叫,該 header 指定目標服務的 ID。

let axiosConfig = {
  headers: {
      "dapr-app-id": "order-processor"
  }
};
const res = await axios.post(`${DAPR_HOST}:${DAPR_HTTP_PORT}/orders`, order , axiosConfig);
console.log("Order passed: " + res.config.data);

檢視服務呼叫輸出

Dapr 在任何 Dapr 範例上呼叫應用程式。在程式碼中,sidecar 程式設計模型鼓勵每個應用程式與其自己的 Dapr 範例通訊。Dapr 範例隨後發現並相互通訊。

checkout & order-processor 服務輸出:

2. 狀態管理

讓我們看一下 Dapr 的狀態管理構建塊。您將使用 Redis 進行狀態儲存,來儲存、獲取和刪除你的狀態,您也可以將其換成任何一種受 Dapr 支援的狀態儲存。

操縱服務狀態

在終端視窗中,導航到 order-processor 目錄。

cd state_management/javascript/sdk/order-processor

安裝依賴項,其中將包括 JavaScript SDK 中的 dapr-client 包:

npm install

驗證您在服務目錄中包含以下檔案:

  • package.json
  • package-lock.json

與 Dapr sidecar 一起執行 order-processor 服務。

dapr run --app-id order-processor --components-path ../../../components/ -- npm run start

order-processor 服務將 orderId key/value 寫入、讀取和刪除到 statestore.yaml 元件中定義的 statestore 範例。一旦服務啟動,它就會執行一個迴圈。

  const client = new DaprClient(DAPR_HOST, DAPR_HTTP_PORT);

  // 將 state 儲存到 state store 中
  client.state.save(STATE_STORE_NAME, [
      {
          key: orderId.toString(),
          value: order
      }
  ]);
  console.log("Saving Order: ", order);

  // 從 state store 中獲取 state
  var result = client.state.get(STATE_STORE_NAME, orderId.toString());
  result.then(function(val) {
      console.log("Getting Order: ", val);
  });

  // 從 state store 中刪除 state
  client.state.delete(STATE_STORE_NAME, orderId.toString());    
  result.then(function(val) {
      console.log("Deleting Order: ", val);
  });

檢視 order-processor 輸出

請注意,正如上面程式碼中所指定的,程式碼將應用程式狀態儲存在 Dapr 狀態儲存中,讀取它,然後將其刪除

Order-processor 輸出:

statestore.yaml 元件檔案

當你執行 dapr init 時,Dapr 會建立一個預設的 Redis statestore.yaml 並在你的本地機器上執行一個 Redis 容器,它位於:

  • Windows,%UserProfile%\.dapr\components\statestore.yaml
  • Linux/MacOS ,~/.dapr/components/statestore.yaml

使用 statestore.yaml 元件,您可以輕鬆切換狀態儲存,而無需更改程式碼。

本快速入門包含的 Redis statestore.yaml 檔案包含以下內容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
  - name: actorStateStore
    value: "true"

在 YAML 檔案中:

  • metadata/name 是您的應用程式與元件對話的方式(在程式碼範例中稱為 DAPR_STORE_NAME)。
  • spec/metadata 定義到元件使用的 Redis 範例的連線。

3. 釋出和訂閱

開始使用 Dapr 的釋出和訂閱構建塊

讓我們看一下 Dapr 的釋出和訂閱 (Pub/sub) 構建塊。您將執行釋出者微服務和訂閱者微服務,以演示 Dapr 如何啟用釋出/訂閱模式。

  1. 使用釋出服務,開發人員可以重複釋出訊息到 topic。
  2. Pub/sub 元件對這些訊息進行排隊或代理。我們下面的範例使用 Redis,您可以使用 RabbitMQ、Kafka 等。
  3. 該 topic 的訂閱者從佇列中提取訊息並處理它們。

訂閱 topic

在終端視窗中,從 quickstarts 根目錄導航到 order-processor 目錄。

cd pub_sub/javascript/sdk/order-processor

安裝依賴項,其中將包括 JavaScript SDK 中的 dapr-client 包:

npm install

驗證您在服務目錄中包含以下檔案:

  • package.json
  • package-lock.json

與 Dapr sidecar 一起執行 order-processor subscriber 服務。

dapr run --app-port 5001 --app-id order-processing --app-protocol http --dapr-http-port 3501 --components-path ../../../components -- npm run start

在 order-processor 訂閱者中,我們訂閱名為 order_pub_sub 的 Redis 範例(如 pubsub.yaml 元件中所定義)和 topic orders。這使您的應用程式程式碼能夠通過 Dapr sidecar 與 Redis 元件範例通訊。

server.pubsub.subscribe("order_pub_sub", "orders", (data) => console.log("Subscriber received: " + JSON.stringify(data)));

釋出 topic

在新的終端視窗中,從 Quickstarts 克隆目錄的根目錄導航到 checkout 目錄。

cd pub_sub/javascript/sdk/checkout

安裝依賴項,其中將包括 JavaScript SDK 中的 dapr-client 包:

npm install

驗證您在服務目錄中包含以下檔案:

  • package.json
  • package-lock.json

與 Dapr sidecar 一起執行 checkout 釋出者服務。

dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 --components-path ../../../components -- npm run start

checkout 釋出者服務中,我們將 orderId 訊息釋出到名為 order_pub_sub 的 Redis 範例(在 pubsub.yaml 元件中定義)和 topic orders。服務一啟動,就會迴圈釋出:

const client = new DaprClient(DAPR_HOST, DAPR_HTTP_PORT);

await client.pubsub.publish(PUBSUB_NAME, PUBSUB_TOPIC, order);
   console.log("Published data: " + JSON.stringify(order));

檢視釋出/訂閱輸出

請注意,正如上面程式碼中所指定的,釋出者將一個亂數推播到 Dapr sidecar,而訂閱者接收它。

釋出者 & 訂閱者輸出:

pubsub.yaml 元件檔案

當你執行 dapr init 時,Dapr 會建立一個預設的 Redis pubsub.yaml 並在你的本地機器上執行一個 Redis 容器,它位於:

  • 在 Windows 上,在 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,在 ~/.dapr/components/pubsub.yaml

使用 pubsub.yaml 元件,您可以輕鬆更換底層元件,而無需更改應用程式程式碼。

本快速入門包含的 Redis pubsub.yaml 檔案包含以下內容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: order_pub_sub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

在 YAML 檔案中:

  • metadata/name 是您的應用程式與元件對話的方式。
  • spec/metadata 定義與元件範例的連線。
  • scopes 指定哪個應用程式可以使用該元件。

4. 輸入和輸出繫結

開始使用 Dapr 的 Binding 構建塊

讓我們看一下 Dapr 的 Bindings 構建塊。使用繫結,您可以:

  • 使用來自外部系統的事件觸發您的應用程式。
  • 與外部系統的介面。

接下來您將使用輸入 Cron binding 安排批次處理指令碼每 10 秒執行一次。該指令碼使用 PostgreSQL Dapr binding 處理 JSON 檔案並將資料輸出到 SQL 資料庫。

在本地執行 PostgreSQL Docker 容器

在您機器上的 Docker 容器中本地執行 PostgreSQL 範例。範例包含一個 Docker Compose 檔案,用於在本地自定義、構建、執行和初始化帶有預設 orders 表的 postgres 容器。

在終端視窗中,從 quickstarts 根目錄導航到 bindings/db 目錄。

cd bindings/db

執行以下命令來設定容器:

docker compose up

安排一個 Cron job 並寫入資料庫

在新的終端視窗中,導航到 SDK 目錄。

cd bindings/javascript/sdk/batch

安裝依賴項:

npm install

與 Dapr sidecar 一起執行 batch-sdk 服務。

dapr run --app-id batch-sdk --app-port 5002 --dapr-http-port 3500 --components-path ../../../components -- node index.js 

process_batch 函數內的程式碼每 10 秒執行一次(在 components 目錄的 binding-cron.yaml 中定義)。繫結觸發器在 Dapr sidecar 的 Flask 應用程式中查詢通過 HTTP POST 呼叫的路由。

async function start() {
    await server.binding.receive(cronBindingName,processBatch);
    await server.start();
}

batch-sdk 服務使用 binding-postgres.yaml 元件中定義的 PostgreSQL 輸出繫結將 OrderId、Customer 和 Price 記錄插入到 orders 表中。

async function processBatch(){
    const loc = '../../orders.json';
    fs.readFile(loc, 'utf8', (err, data) => {
        const orders = JSON.parse(data).orders;
        orders.forEach(order => {
            let sqlCmd = `insert into orders (orderid, customer, price) values (${order.orderid}, '${order.customer}', ${order.price});`;
            let payload = `{  "sql": "${sqlCmd}" } `;
            console.log(payload);
            client.binding.send(postgresBindingName, "exec", "", JSON.parse(payload));
        });
        console.log('Finished processing batch');
      });
    return 0;
}

檢視 job 的輸出

請注意,如上所述,程式碼使用 OrderId、Customer 和 Price 作為 payload 呼叫輸出繫結。

你的輸出繫結的 print 語句輸出:

在新終端中,驗證是否已將相同的資料插入到資料庫中。

cd bindings/db

啟動互動式 Postgres CLI:

docker exec -i -t postgres psql --username postgres  -p 5432 -h localhost --no-password

admin=# 提示符下,更改為 orders 表:

\c orders;

orders=# 提示符下,選擇所有行:

select * from orders;

輸出應如下所示:

components\binding-cron.yaml 元件檔案

當您執行 dapr run 命令並指定元件路徑時,Dapr sidecar:

  • 啟動 Cron 繫結構建塊
  • 每 10 秒呼叫一次繫結端點(批次處理)

binding-cron.yaml 檔案包含以下內容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: cron
  namespace: quickstarts
spec:
  type: bindings.cron
  version: v1
  metadata:
  - name: schedule
    value: "@every 10s"

注意:binding-cron.yaml 的後設資料部分包含一個 Cron 表示式,用於指定呼叫繫結的頻率。

component\binding-postgres.yaml 元件檔案

當您執行 dapr run 命令並指定元件路徑時,Dapr sidecar:

  • 啟動 PostgreSQL 繫結構建塊
  • 使用 binding-postgres.yaml 檔案中指定的設定連線到 PostgreSQL

使用 binding-postgres.yaml 元件,您可以輕鬆換出後端資料庫繫結,而無需更改程式碼。

本快速入門包含的 PostgreSQL binding-postgres.yaml 檔案包含以下內容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: sqldb
  namespace: quickstarts
spec:
  type: bindings.postgres
  version: v1
  metadata:
  - name: url
    value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"

在 YAML 檔案中:

  • spec/type 指定 PostgreSQL 用於此係結。
  • spec/metadata 定義到元件使用的 PostgreSQL 範例的連線。

5. Secrets 管理

開始使用 Dapr 的 Secrets Management 構建塊

Dapr 提供了一個專用的 secrets API,允許開發人員從 secrets store 中檢索 secrets。接下來:

  1. 執行帶有 secret 儲存元件的微服務。
  2. 在應用程式程式碼中使用 Dapr secrets API 檢索 secrets。

檢索 secrets

在終端視窗中,導航到 order-processor 目錄。

cd secrets_management/javascript/sdk/order-processor

安裝依賴項:

npm install

與 Dapr sidecar 一起執行 order-processor 服務。

dapr run --app-id order-processor --components-path ../../../components/ -- npm start

在幕後

order-processor 服務

請注意下面的 order-processor 服務如何指向:

  • 在 local-secret-store.yaml 元件中定義的 DAPR_SECRET_STORE。
  • 在 secrets.json 中定義的 secret。
// index.js
const DAPR_SECRET_STORE = "localsecretstore";
const SECRET_NAME = "secret";

async function main() {
    // ...
    const secret = await client.secret.get(DAPR_SECRET_STORE, SECRET_NAME);
    console.log("Fetched Secret: " + JSON.stringify(secret));
}

local-secret-store.yaml 元件

DAPR_SECRET_STORE 定義在 local-secret-store.yaml 元件檔案中,位於 secrets_management/components 中:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: localsecretstore
  namespace: default
spec:
  type: secretstores.local.file
  version: v1
  metadata:
  - name: secretsFile
    value: secrets.json
  - name: nestedSeparator
    value: ":"

在 YAML 檔案中:

  • metadata/name 是您的應用程式參照元件的方式(在程式碼範例中稱為 DAPR_SECRET_STORE)。
  • spec/metadata 定義與元件使用的 secret 的連線。

secrets.json 檔案

SECRET_NAME 在位於 secrets_management/javascript/sdk/order-processorsecrets.json 檔案中定義:

{
    "secret": "YourPasskeyHere"
}

檢視 order-processor 輸出

正如上面的應用程式程式碼中所指定的,order-processor 服務通過 Dapr secret 儲存檢索 secret 並將其顯示在控制檯中。

6. 官方範例倉庫(原始碼)