如何在本機使用 Dapr 進行微服務應用程式開發 | The Will Will Web

The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

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

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

初始化 Dapr

  1. 安裝 Dapr CLI 命令列工具

    請依據官網文件 Install the Dapr CLI 進行安裝。

    安裝好之後你可以執行 dapr -v 取得版本資訊:

    CLI version: 1.4.0
    Runtime version: n/a
    

    你會看到 CLI 版本,但是第一次安裝還不會看到 Runtime 版本,因此你還需要對 Dapr 進行初始化!

  2. 安裝 Docker Desktop 工具

    由於 Dapr 支援兩種運行模式:Self-HostedKubernetes

    如果要用 Self-Hosted 模式在本機執行 Dapr Runtime 就需要先安裝好 Docker Desktop 才能順利運作,這也是最簡單環境配置了!

  3. 初始化 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
    

    預設會有三個重要的容器會執行起來:

    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 構成要素(Building Block),專門用來提供「狀態管理」等服務,有了這層微服務,你就不用在你自己的微服務下實作狀態管理,大幅簡化狀態管理的複雜度。

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

  1. 建立 Console 專案

    mkdir DaprCounter && cd DaprCounter
    dotnet new console
    dotnet run
    
  2. 加入 Dapr .NET SDKDapr.Client 套件 (GitHub)

    dotnet add package Dapr.Client
    
  3. 使用 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);
    }
    
  4. 透過 Dapr CLI 啟動微服務 (並存取狀態服務元件)

    dapr run --app-id DaprCounter dotnet run
    
  5. 透過 Dapr CLI 停止微服務

    dapr stop --app-id DaprCounter
    

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

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

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

  1. 啟動 Dapr 但不啟動 DaprCounter 應用程式

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

    這段命令的意思,就是單純啟動 Dapr Sidecar 程序,而不執行任意微服務應用程式,你可以藉此測試 Dapr Sidecar 的能力。

  2. 透過 curl 直接存取狀態服務元件的狀態內容

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

    其中 3500 是 Dapr Sidecar 程序的接聽 Port,而 statestore 則是狀態服務元件的名稱,還有 counter 則是先前透過 DaprCounter 應用程式寫入的鍵名。

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

  1. 建立一個全新的 ASP.NET Core 專案

    mkdir DaprCounterASPNET && cd DaprCounterASPNET
    dotnet new webapi --no-https
    dotnet run
    

    這裡加入 --no-https 蠻重要的,因為 Dapr 所管理的微服務預設是走 HTTP 協定,但也可視狀況啟用 mTLS 加密連線。

  2. 加入 Dapr .NET SDKDapr.AspNetCore 套件 (GitHub)

    dotnet add package Dapr.AspNetCore
    
  3. 設定 Dapr 到 DI 服務集合中

    找到 builder.Services.AddControllers(); 並直接加上 .AddDapr() 即可,完成結果如下:

    builder.Services.AddControllers().AddDapr();
    
  4. 修改 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);
    }
    
  5. 啟動微服務

    我為了要能夠測試先前透過 DaprCounter 寫入的參數,可以透過這個 ASP.NET Core 應用程式取得一樣的狀態,我這邊在啟動時刻意設定了 --app-idDaprCounter,如此一來就可以取得到相同狀態管理元件下的資訊。這件事意味著 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 是否可以連上,用以確保服務間通訊可以順利進行。

  6. 測試服務執行結果

    curl http://localhost:5000/WeatherForecast/counter
    
  7. 透過 Dapr CLI 停止微服務

    dapr stop --app-id DaprCounter
    

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

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

  1. 先調整 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();
        }
    }
    
  2. 啟動 DaprCounterASPNET 微服務

    dapr run --app-id DaprCounterASPNET --app-port 5000 -- dotnet run --no-launch-profile
    
  3. 調整 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 呼叫,完全不用在意對方在哪裡!

  4. 啟動 DaprCounter 微服務

    dapr run --app-id DaprCounter dotnet run
    

    此時你會發現,兩個微服務之間已經相當順利的運作中!👍

移除 Dapr Runtime

  • 解除安裝 Dapr Runtime

    dapr uninstall
    

    這個命令只會移除 dapr_placement 服務與 Dapr CLI 執行檔,不會移除 dapr_redisdapr_zipkin 容器,因為裡面可能還存有一些狀態。

  • 完整解除安裝 Dapr Runtime

    加上 --all 參數就可以一併移除所有跟 Dapr 相依的元件服務:

    dapr uninstall --all
    

相關連結