最近花了點時間在玩 Dapr 這套非常優異的開發工具,當你想要開發分散式應用程式或想實現微服務架構,都可以深入瞭解看看,保證不虛此行。本篇文章我打算分享如何在本機使用 Dapr 開發微服務分散式應用程式,幫助大家更容易的上手這套分散式應用開發工具!👍
初始化 Dapr
-
安裝 Dapr CLI 命令列工具
請依據官網文件 Install the Dapr CLI 進行安裝。
安裝好之後你可以執行 dapr -v
取得版本資訊:
CLI version: 1.4.0
Runtime version: n/a
你會看到 CLI 版本,但是第一次安裝還不會看到 Runtime 版本,因此你還需要對 Dapr 進行初始化!
-
安裝 Docker Desktop 工具
由於 Dapr 支援兩種運行模式:Self-Hosted 與 Kubernetes
如果要用 Self-Hosted 模式在本機執行 Dapr Runtime 就需要先安裝好 Docker Desktop 才能順利運作,這也是最簡單環境配置了!
-
初始化 Dapr 執行環境 (Dapr Runtime)
請依據官網文件 Initialize Dapr in your local environment 進行安裝。
其實也就是執行以下命令就可以完成,非常容易:
dapr init
Making the jump to hyperspace...
Installing runtime version 1.4.3
Downloading binaries and setting up components...
Downloaded binaries and completed components set up.
daprd binary has been installed to C:\Users\User\.dapr\bin.
dapr_placement container is running.
dapr_redis container is running.
dapr_zipkin container is running.
Use `docker ps` to check running containers.
Success! Dapr is up and running. To get started, go here: https://aka.ms/dapr-getting-started
初始化過程會建立三個 Docker 容器,還會在你的家目錄下新增一個 .dapr
目錄,裡面會有 Dapr 重要的元件定義檔與 dapr
執行檔。
執行完成後你可以立即使用 docker ps
查看 Docker 正在運行中的容器:
docker ps
預設會有三個重要的容器會執行起來:
dapr_redis
: 用來提供 Dapr 架構下的「狀態元件」使用
dapr_zipkin
: 用來提供 Dapr 架構下的「可觀測性元件」使用
dapr_placement
: 用來提供 Dapr 架構下「服務呼叫元件」使用
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
721cd994441d redis "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:6379->6379/tcp dapr_redis
55d8466e19f6 daprio/dapr:1.4.3 "./placement" About a minute ago Up About a minute 0.0.0.0:6050->50005/tcp dapr_placement
b50ab3bf9f7f openzipkin/zipkin "start-zipkin" About a minute ago Up About a minute (healthy) 9410/tcp, 0.0.0.0:9411->9411/tcp dapr_zipkin
體驗 Dapr 開發過程:使用狀態管理元件
由於微服務(分散式)應用程式彼此之間都是獨立運行的,每個微服務都應該採「無狀態」(Stateless)的方式開發,但是有些商業邏輯還是需要透過「狀態」來保存處理過程的短暫資料。此時 Dapr 提供了一個 State management 構成要素(Building Block),專門用來提供「狀態管理」等服務,有了這層微服務,你就不用在你自己的微服務下實作狀態管理,大幅簡化狀態管理的複雜度。
以下我會使用 .NET 6 來做示範開發。
-
建立 Console 專案
mkdir DaprCounter && cd DaprCounter
dotnet new console
dotnet run
-
加入 Dapr .NET SDK 的 Dapr.Client 套件 (GitHub)
dotnet add package Dapr.Client
-
使用 DaprClient 讀寫狀態管理元件
using Dapr.Client;
// 這裡要設定 ~/.dapr/components/statestore.yaml 裡面定義的 metadata.name 名稱
const string storeName = "statestore";
// 這裡要設定應用程式會用到的「鍵名」,若要跨微服務存取相同狀態則要用相同的鍵名
const string key = "counter";
var daprClient = new DaprClientBuilder().Build();
var counter = await daprClient.GetStateAsync<int>(storeName, key);
while (true)
{
Console.WriteLine($"Counter = {counter++}");
await daprClient.SaveStateAsync(storeName, key, counter);
await Task.Delay(1000);
}
-
透過 Dapr CLI 啟動微服務 (並存取狀態服務元件)
dapr run --app-id DaprCounter dotnet run
-
透過 Dapr CLI 停止微服務
dapr stop --app-id DaprCounter
每個透過 dapr run
執行起來的「微服務」都會有個 Sidecar (邊車) 程序,他的主要職責就是幫助你跟其他微服務進行溝通,你不用特別在意這些服務在哪裡,也不用知道這些服務用到了哪些元件(例如狀態管理元件),你的程式只要直接跟這個 Sidecar 進行溝通即可,而溝通的通訊協定同時包含了 HTTP 與 gRPC 這兩種。
事實上,你的應用程式可以直接透過 HttpClient 直接對 Sidecar 進行呼叫,他就會自動幫你將要求發送到正確的另一個微服務。不過,由於 Dapr .NET SDK 已經都幫我們封裝好這些呼叫,所以寫 .NET 的朋友是不太需要這麼做的,但是透過其他程式語言撰寫的微服務,就可以很輕易的直接透過 HttpClient 進行呼叫。所以 Dapr 才會說他是 Distributed APplication Runtime,任何程式語言框架都可以跟 Dapr 進行整合!
你可以做一個有趣的實驗看看:
-
啟動 Dapr 但不啟動 DaprCounter 應用程式
dapr run --app-id DaprCounter --dapr-http-port 3500
這段命令的意思,就是單純啟動 Dapr Sidecar 程序,而不執行任意微服務應用程式,你可以藉此測試 Dapr Sidecar 的能力。
-
透過 curl 直接存取狀態服務元件的狀態內容
curl http://localhost:3500/v1.0/state/statestore/counter
其中 3500
是 Dapr Sidecar 程序的接聽 Port,而 statestore
則是狀態服務元件的名稱,還有 counter
則是先前透過 DaprCounter 應用程式寫入的鍵名。
體驗 Dapr 開發過程:在 ASP.NET Core 使用狀態管理元件
-
建立一個全新的 ASP.NET Core 專案
mkdir DaprCounterASPNET && cd DaprCounterASPNET
dotnet new webapi --no-https
dotnet run
這裡加入 --no-https
蠻重要的,因為 Dapr 所管理的微服務預設是走 HTTP 協定,但也可視狀況啟用 mTLS 加密連線。
-
加入 Dapr .NET SDK 的 Dapr.AspNetCore 套件 (GitHub)
dotnet add package Dapr.AspNetCore
-
設定 Dapr 到 DI 服務集合中
找到 builder.Services.AddControllers();
並直接加上 .AddDapr()
即可,完成結果如下:
builder.Services.AddControllers().AddDapr();
-
修改 Controllers\WeatherForecastController.cs
檔案並加入一個新的 Action 動作方法
你的 Action 可以利用路由參數的模型繫結(Model Binding)功能,自動將繫結到的 {key}
帶入到 [FromState("statestore", "key")]
的第二個參數中,讓你的 counter
參數可以直接接收到狀態服務元件中的資料!
以下三段程式都是相同的意思,你選一種使用即可:
[HttpGet("{key}")]
public IActionResult GetCounter([FromState("statestore", "key")] Dapr.StateEntry<int> counter)
{
return Ok(counter.Value);
}
[HttpGet("{key}")]
public IActionResult GetCounter([FromState("statestore", "key")] int counter)
{
return Ok(counter.Value);
}
[HttpGet("{key}")]
public IActionResult GetCounter([FromState("statestore")] Dapr.StateEntry<int> key)
{
return Ok(counter.Value);
}
你也可以注入 DaprClient
來操作狀態管理元件,當你不需要使用路由參數來繫結參數時可以這樣用:
[HttpGet("counter")]
public async Task<IActionResult> GetCounter([FromServices] DaprClient daprClient)
{
var counter = await daprClient.GetStateEntryAsync<int>("statestore", "counter");
return Ok(counter.Value);
}
[HttpGet("counter")]
public async Task<IActionResult> GetCounter([FromServices] DaprClient daprClient)
{
int counter = await daprClient.GetStateAsync<int>("statestore", "counter");
return Ok(counter);
}
-
啟動微服務
我為了要能夠測試先前透過 DaprCounter 寫入的參數,可以透過這個 ASP.NET Core 應用程式取得一樣的狀態,我這邊在啟動時刻意設定了 --app-id
為 DaprCounter
,如此一來就可以取得到相同狀態管理元件下的資訊。這件事意味著 Dapr 在啟動時會參考 --app-id
做為「微服務」的應用程式編號,所有狀態服務皆相依於特定微服務。
實務上來說,我們不應該在不同的微服務下共用狀態,比較正確的作法,應該是避免共用狀態,當你需要另一個服務的狀態,應該透過該微服務提供的 API 來取得狀態,而非直接存取狀態管理元件下的資料。
dapr run --app-id DaprCounter --app-port 5000 -- dotnet run --no-launch-profile
注意: 我特別加上 --no-launch-profile
的目的是為了讓 ASP.NET Core 可以跑在預設的 HTTP 5000 Port 底下。這裡的指定 --app-port
的目的,是為了讓 Dapr Sidecar 可以知道你的應用程式接聽的 Port 號,好讓其他微服務日後可以順利的連上。實際上 Dapr Sidecar 會定時檢查 --app-port
是否可以連上,用以確保服務間通訊可以順利進行。
-
測試服務執行結果
curl http://localhost:5000/WeatherForecast/counter
-
透過 Dapr CLI 停止微服務
dapr stop --app-id DaprCounter
體驗 Dapr 開發過程:從 DaprCounter 呼叫 DaprCounterASPNET 服務
請先用 dapr list
確認上述兩個微服務已經全部停止。
-
先調整 ASP.NET Core 專案的 Controllers\WeatherForecastController.cs
控制器
請確認程式碼中有加入以下兩個 Action 動作方法:
[HttpGet("counter")]
public async Task<IActionResult> GetCounter([FromServices] DaprClient daprClient)
{
int counter = await daprClient.GetStateAsync<int>("statestore", "counter");
return Ok(counter);
}
[HttpPut("counter")]
public async Task<IActionResult> PutCounter([FromServices] DaprClient daprClient)
{
var counter = await daprClient.GetStateEntryAsync<int>("statestore", "counter");
counter.Value += 1;
if (await counter.TrySaveAsync())
{
return Ok(counter.Value);
}
else
{
return BadRequest();
}
}
-
啟動 DaprCounterASPNET
微服務
dapr run --app-id DaprCounterASPNET --app-port 5000 -- dotnet run --no-launch-profile
-
調整 Console 專案的 Program.cs
檔案
你可以從 daprClient.InvokeMethodAsync
的呼叫方式發現,當你對另一個微服務發出 API 呼叫,只需要知道服務名稱(--app-id
)就可以連上!
using Dapr.Client;
var daprClient = new DaprClientBuilder().Build();
while (true)
{
var counter = await daprClient.InvokeMethodAsync<int>(HttpMethod.Put, "DaprCounterASPNET", "WeatherForecast/counter");
Console.WriteLine($"Counter = {counter}");
await Task.Delay(1000);
}
這裡的 daprClient.InvokeMethodAsync()
方法是非常萬用的,第一個參數 httpMethod
就是準備發出的 HTTP 方法,第二個參數 appId
則是輸入微服務的 App Id,第三個參數 methodName
對於 API 服務來說,就是端點 (Endpoint)。你只要準備好這三個參數,就可以對 Dapr 底下的 DaprCounterASPNET
微服務發出 API 呼叫,完全不用在意對方在哪裡!
-
啟動 DaprCounter
微服務
dapr run --app-id DaprCounter dotnet run
此時你會發現,兩個微服務之間已經相當順利的運作中!👍
移除 Dapr Runtime
相關連結