﻿# 如何在本機使用 Dapr 進行微服務應用程式開發

最近花了點時間在玩 [Dapr](https://dapr.io/) 這套非常優異的開發工具，當你想要開發分散式應用程式或想實現微服務架構，都可以深入瞭解看看，保證不虛此行。本篇文章我打算分享如何在本機使用 Dapr 開發微服務分散式應用程式，幫助大家更容易的上手這套分散式應用開發工具！👍

![](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/aa84eb1c9bd8356c536e-137958004-fc2d116b-bff0-470e-b146-057595cd4933.png)

### 初始化 Dapr

1.  安裝 Dapr CLI 命令列工具
    
    請依據官網文件 [Install the Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) 進行安裝。
    
    安裝好之後你可以執行 `dapr -v` 取得版本資訊：
    
    ```
    CLI version: 1.4.0
    Runtime version: n/a
    ```
    
    你會看到 CLI 版本，但是第一次安裝還不會看到 Runtime 版本，因此你還需要對 Dapr 進行初始化！
    
2.  安裝 [Docker Desktop](https://docs.docker.com/get-docker/) 工具
    
    由於 Dapr 支援兩種運行模式：[Self-Hosted](https://docs.dapr.io/operations/hosting/self-hosted/) 與 [Kubernetes](https://docs.dapr.io/operations/hosting/kubernetes/)
    
    如果要用 [Self-Hosted](https://docs.dapr.io/operations/hosting/self-hosted/) 模式在本機執行 Dapr Runtime 就需要先安裝好 [Docker Desktop](https://docs.docker.com/get-docker/) 才能順利運作，這也是最簡單環境配置了！
    
3.  初始化 Dapr 執行環境 (Dapr Runtime)
    
    請依據官網文件 [Initialize Dapr in your local environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) 進行安裝。
    
    其實也就是執行以下命令就可以完成，非常容易：
    
    ```bash
    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 正在運行中的容器：
    
    ```bash
    docker ps
    ```
    
    預設會有三個重要的容器會執行起來：
    
    1.  `dapr_redis`: 用來提供 Dapr 架構下的「狀態元件」使用
    2.  `dapr_zipkin`: 用來提供 Dapr 架構下的「可觀測性元件」使用
    3.  `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](https://docs.dapr.io/developing-applications/building-blocks/state-management/state-management-overview/) 構成要素(Building Block)，專門用來提供「狀態管理」等服務，有了這層微服務，你就不用在你自己的微服務下實作狀態管理，大幅簡化狀態管理的複雜度。

> 以下我會使用 .NET 6 來做示範開發。

1.  建立 Console 專案
    
    ```bash
    mkdir DaprCounter && cd DaprCounter
    dotnet new console
    dotnet run
    ```
    
2.  加入 [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) 的 [Dapr.Client](https://www.nuget.org/packages/Dapr.Client) 套件 ([GitHub](https://github.com/dapr/dotnet-sdk))
    
    ```bash
    dotnet add package Dapr.Client
    ```
    
3.  使用 DaprClient 讀寫狀態管理元件
    
    ```csharp
    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);
    }
    ```
    
4.  透過 Dapr CLI 啟動微服務 (並存取狀態服務元件)
    
    ```bash
    dapr run --app-id DaprCounter dotnet run
    ```
    
5.  透過 Dapr CLI 停止微服務
    
    ```bash
    dapr stop --app-id DaprCounter
    ```
    

每個透過 `dapr run` 執行起來的「微服務」都會有個 Sidecar (邊車) 程序，他的主要職責就是幫助你跟其他微服務進行溝通，你不用特別在意這些服務在哪裡，也不用知道這些服務用到了哪些元件（例如狀態管理元件），你的程式只要直接跟這個 Sidecar 進行溝通即可，而溝通的通訊協定同時包含了 **HTTP** 與 **gRPC** 這兩種。

事實上，你的應用程式可以直接透過 HttpClient 直接對 Sidecar 進行呼叫，他就會自動幫你將要求發送到正確的另一個微服務。不過，由於 [Dapr .NET SDK](https://github.com/dapr/dotnet-sdk) 已經都幫我們封裝好這些呼叫，所以寫 .NET 的朋友是不太需要這麼做的，但是透過其他程式語言撰寫的微服務，就可以很輕易的直接透過 HttpClient 進行呼叫。所以 **Dapr** 才會說他是 **Distributed APplication Runtime**，任何程式語言框架都可以跟 Dapr 進行整合！

你可以做一個有趣的實驗看看：

1.  啟動 Dapr 但不啟動 DaprCounter 應用程式
    
    ```bash
    dapr run --app-id DaprCounter --dapr-http-port 3500
    ```
    
    這段命令的意思，就是單純啟動 Dapr Sidecar 程序，而不執行任意微服務應用程式，你可以藉此測試 Dapr Sidecar 的能力。
    
2.  透過 curl 直接存取狀態服務元件的狀態內容
    
    ```bash
    curl http://localhost:3500/v1.0/state/statestore/counter
    ```
    
    其中 `3500` 是 Dapr Sidecar 程序的接聽 Port，而 `statestore` 則是狀態服務元件的名稱，還有 `counter` 則是先前透過 DaprCounter 應用程式寫入的鍵名。
    

### 體驗 Dapr 開發過程：在 ASP.NET Core 使用狀態管理元件

1.  建立一個全新的 ASP.NET Core 專案
    
    ```bash
    mkdir DaprCounterASPNET && cd DaprCounterASPNET
    dotnet new webapi --no-https
    dotnet run
    ```
    
    > 這裡加入 `--no-https` 蠻重要的，因為 Dapr 所管理的微服務**預設**是走 HTTP 協定，但也可視狀況啟用 mTLS 加密連線。
    
2.  加入 [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) 的 [Dapr.AspNetCore](https://www.nuget.org/packages/Dapr.AspNetCore) 套件 ([GitHub](https://github.com/dapr/dotnet-sdk))
    
    ```bash
    dotnet add package Dapr.AspNetCore
    ```
    
3.  設定 Dapr 到 DI 服務集合中
    
    找到 `builder.Services.AddControllers();` 並直接加上 `.AddDapr()` 即可，完成結果如下：
    
    ```csharp
    builder.Services.AddControllers().AddDapr();
    ```
    
4.  修改 `Controllers\WeatherForecastController.cs` 檔案並加入一個新的 Action 動作方法
    
    你的 Action 可以利用**路由參數**的**模型繫結**(Model Binding)功能，自動將繫結到的 `{key}` 帶入到 `[FromState("statestore", "key")]` 的第二個參數中，讓你的 `counter` 參數可以直接接收到狀態服務元件中的資料！
    
    以下三段程式都是相同的意思，你選一種使用即可：
    
    ```bash
    [HttpGet("{key}")]
    public IActionResult GetCounter([FromState("statestore", "key")] Dapr.StateEntry<int> counter)
    {
        return Ok(counter.Value);
    }
    ```
    
    ```bash
    [HttpGet("{key}")]
    public IActionResult GetCounter([FromState("statestore", "key")] int counter)
    {
        return Ok(counter.Value);
    }
    ```
    
    ```bash
    [HttpGet("{key}")]
    public IActionResult GetCounter([FromState("statestore")] Dapr.StateEntry<int> key)
    {
        return Ok(counter.Value);
    }
    ```
    
    你也可以注入 `DaprClient` 來操作狀態管理元件，當你不需要使用路由參數來繫結參數時可以這樣用：
    
    ```csharp
    [HttpGet("counter")]
    public async Task<IActionResult> GetCounter([FromServices] DaprClient daprClient)
    {
        var counter = await daprClient.GetStateEntryAsync<int>("statestore", "counter");
        return Ok(counter.Value);
    }
    ```
    
    ```csharp
    [HttpGet("counter")]
    public async Task<IActionResult> GetCounter([FromServices] DaprClient daprClient)
    {
        int counter = await daprClient.GetStateAsync<int>("statestore", "counter");
        return Ok(counter);
    }
    ```
    
5.  啟動微服務
    
    我為了要能夠測試先前透過 DaprCounter 寫入的參數，可以透過這個 ASP.NET Core 應用程式取得一樣的狀態，我這邊在啟動時刻意設定了 `--app-id` 為 `DaprCounter`，如此一來就可以取得到相同狀態管理元件下的資訊。這件事意味著 Dapr 在啟動時會參考 `--app-id` 做為「微服務」的應用程式編號，所有狀態服務皆相依於特定微服務。
    
    > 實務上來說，我們**不應該**在不同的微服務下**共用狀態**，比較正確的作法，應該是**避免共用狀態**，當你需要另一個服務的狀態，應該透過該微服務提供的 API 來取得狀態，而非直接存取狀態管理元件下的資料。
    
    ```bash
    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` 是否可以連上，用以確保服務間通訊可以順利進行。
    
6.  測試服務執行結果
    
    ```bash
    curl http://localhost:5000/WeatherForecast/counter
    ```
    
7.  透過 Dapr CLI 停止微服務
    
    ```bash
    dapr stop --app-id DaprCounter
    ```
    

### 體驗 Dapr 開發過程：從 DaprCounter 呼叫 DaprCounterASPNET 服務

請先用 `dapr list` 確認上述兩個微服務已經全部停止。

1.  先調整 ASP.NET Core 專案的 `Controllers\WeatherForecastController.cs` 控制器
    
    請確認程式碼中有加入以下兩個 Action 動作方法：
    
    ```csharp
    [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();
        }
    }
    ```
    
2.  啟動 `DaprCounterASPNET` 微服務
    
    ```bash
    dapr run --app-id DaprCounterASPNET --app-port 5000 -- dotnet run --no-launch-profile
    ```
    
3.  調整 Console 專案的 `Program.cs` 檔案
    
    你可以從 `daprClient.InvokeMethodAsync` 的呼叫方式發現，當你對另一個微服務發出 API 呼叫，只需要知道**服務名稱**(`--app-id`)就可以連上！
    
    ```csharp
    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 呼叫，完全不用在意對方在哪裡！
    
4.  啟動 `DaprCounter` 微服務
    
    ```bash
    dapr run --app-id DaprCounter dotnet run
    ```
    
    此時你會發現，兩個微服務之間已經相當順利的運作中！👍
    

### 移除 Dapr Runtime

-   解除安裝 Dapr Runtime
    
    ```bash
    dapr uninstall
    ```
    
    > 這個命令只會移除 [`dapr_placement`](https://docs.dapr.io/concepts/dapr-services/placement/) 服務與 Dapr CLI 執行檔，不會移除 `dapr_redis` 與 `dapr_zipkin` 容器，因為裡面可能還存有一些狀態。
    
-   完整解除安裝 Dapr Runtime
    
    加上 `--all` 參數就可以一併移除所有跟 Dapr 相依的元件服務：
    
    ```bash
    dapr uninstall --all
    ```
    

### 相關連結

-   [The Dapr state management building block](https://learn.microsoft.com/en-us/dotnet/architecture/dapr-for-net-developers/state-management?WT.mc_id=DT-MVP-4015686)
-   [dapr/cli: Command-line tools for Dapr.](https://github.com/dapr/cli)
-   [dapr/quickstarts: Dapr quickstart code samples and tutorials showcasing core Dapr capabilities](https://github.com/dapr/quickstarts)
-   [dapr/dotnet-sdk: Dapr SDK for .NET](https://github.com/dapr/dotnet-sdk)
    -   [Dapr.Client](https://www.nuget.org/packages/Dapr.Client)
    -   [Dapr.AspNetCore](https://www.nuget.org/packages/Dapr.AspNetCore)
    -   [Dapr.Actors](https://www.nuget.org/packages/Dapr.Actors)
    -   [Dapr.Actors.AspNetCore](https://www.nuget.org/packages/Dapr.Actors.AspNetCore)
    -   [Dapr.Extensions.Configuration](https://www.nuget.org/packages/Dapr.Extensions.Configuration)
-   [Dapr .NET Core examples](https://github.com/dapr/dotnet-sdk/tree/master/examples)
    -   [Dapr Client examples](https://github.com/dapr/dotnet-sdk/tree/master/examples/Client)
    -   [Dapr Actor example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Actor)
    -   [ASP.NET Dapr integration examples](https://github.com/dapr/dotnet-sdk/tree/master/examples/AspNetCore)
-   [快速體驗建構在 Dapr 架構下的微服務應用程式並部署到 Kubernetes 叢集](/post/2021/03/22/Dapr-Microservice-on-Kubernetes)
