地端測試所需軟體在Windows/Linux/macOS都有支援:
在Windows環境建議先安裝scoop這個指令列套件管理工具,假如你的系統槽(C:)太小空間已剩不多的話,可以設定Scoop的安裝路徑到其他磁碟的空間然後再安裝scoop。
再來使用 scoop install nvm 指令安裝nvm for windows,然後用 nvm install lts; nvm use lts安裝並指定命令列採用LTS版本的nodejs & npm版本,然後繼續用npm方式安裝Azurite指令列工具:
npm install -g azurite
mkcert也可用scoop安裝:
scoop bucket add extras
scoop install mkcert
這種安裝方式的好處是不需要系統Admin權限就可使用,且之後不需要這些指令列工具時,直接反安裝scoop即可。
Azure Storage Explorer在Windows環境可直接用 winget install Microsoft.AzureStorageExplorer 這個 winget 指令安裝:
建立一個空目錄來放置Azure Storage Emulator的資料,例如 d:\azurite,然後在這個目錄下執行以下指令來啟動純http的Azure Storage Emulator:
azurite --location data --debug debug.log
當出現如下圖所示的訊息時,表示Azure Storage Emulator已啟動成功,並且在azurite目錄底下會產生 data 目錄用來存放模擬服務的資料,需要有訊息記錄log時也可從 debug.log 紀錄檔查找:
要結束服務,按Ctrl+C即可
我們用一個新的 ASP.NET Core WebApi專案來示範如何使用Azure Table Storage Provider:
dotnet new webapi --framework net6.0 --name RpcDemo.Hosting.AspNetCoreWebApi
並將此專案加入至根目錄的OrleansDemo.sln方案中Program.cs 檔案,為以下內容:
using Orleans.Hosting;
var builder = WebApplication.CreateBuilder(args);
// Add Orleans co-hosting
builder.Host.UseOrleans(siloBuilder =>
{
    siloBuilder.UseLocalhostClustering();
    siloBuilder.AddAzureTableGrainStorage(
      name: "demo_counters", options =>
      {
        options.UseJson = true;
        options.ConfigureTableServiceClient("UseDevelopmentStorage=true");
      });
});
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
修改的部分為新增了Orleans的 ASP.NET Core co-hosting(使用擴充方法 UseOrleans())配置程式碼:
AddAzureTableGrainStorage() 擴充方法是位於 Microsoft.Orleans.Persistence.AzureStorage Nuget套件之中,第一個 name 參數要跟 RpcDemo.Grains.Counters 專案內 CounterGrain 建構子宣告的Grain State所裝飾的PersistentStateAttribute 屬性語法第二個參數的值一致:
using Microsoft.AspNetCore.Mvc;
using Orleans;
using RpcDemo.Interfaces.Counter;
namespace RpcDemo.Hosting.AspNetCoreWebApi.Controllers;
[ApiController]
[Route("[controller]")]
public class CounterController
{
    private readonly IGrainFactory _grainFactory;
    public CounterController(IGrainFactory grainFactory)
    {
        _grainFactory = grainFactory;
    }
    [HttpGet]
    public async Task<int> GetCurrent(Guid id)
    {
        var counter = _grainFactory.GetGrain<ICounterGrain>(id);
        return await counter.GetCountAsync();
    }
    [HttpPost]
    public async Task Increment(Guid id)
    {
        var counter = _grainFactory.GetGrain<ICounterGrain>(id);
        await counter.IncrementAsync();
    }
    [HttpPatch]
    public async Task Reset(Guid id)
    {
        var counter = _grainFactory.GetGrain<ICounterGrain>(id);
        await counter.ResetAsync();
    }
}
這個CounterController的程式碼裡,藉由注入Orleans的 IGrainFactory 物件,配合Action方法輸入的參數來取得 ICounterGrain 的Grain實體,並呼叫其RPC方法來操作記數器(也就是Grain State的資料)。{
  "label": "build counter webapi",
  "command": "dotnet",
  "type": "process",
  "args": [
      "build",
      "${workspaceFolder}/src/Hosting/Server/RpcDemo.Hosting.AspNetCoreWebApi/  RpcDemo.Hosting.AspNetCoreWebApi.csproj",
      "/property:GenerateFullPaths=true",
      "/consoleloggerparameters:NoSummary"
  ],
  "problemMatcher": "$msCompile"
}
{
   "name": "Launch WebApi",
   "type": "coreclr",
   "request": "launch",
   "preLaunchTask": "build webapi",
   // If you have changed target frameworks, make sure to update the program path.
   "program": "${workspaceFolder}/src/Hosting/Server/RpcDemo.Hosting.AspNetCoreWebApi/bin/Debug/net6.0/RpcDemo.Hosting.AspNetCoreWebApi.dll",
   "args": [],
   "cwd": "${workspaceFolder}",
   "stopAtEntry": false,
   // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
   "serverReadyAction": {
      "action": "openExternally",
      "pattern": "\\bNow listening on:\\s+https://\\S+:([0-9]+)",
      "uriFormat": "https://localhost:%s/swagger/index.html"
    },
    "env": {
            "ASPNETCORE_ENVIRONMENT": "Development"
    },
    "console": "integratedTerminal"
} 
然後開一個作業系統的命令列視窗啟動 Azurite,然後在Visual Studio Code的 Run And Debug 功能,選取 "Launch WebApi",讓WebApi專案跑起來,跳出的Swagger網頁可以進行手動呼叫WebApi進行測試:
(可使用Online GUID Generator來產生測試用GUID字串值)
呼叫幾次使記數器增加或重設的WebApi之後,用Azure Storage Explorer來查看記數器的Grain State資料:
經過一些實際WebApi呼叫的操作後,可以發現有兩個注意點:
AzureTableStorageOptions的 UseJson = true,所以才會序列化為JSON字串以便於觀察);所以,雖然理論上Orleans框架沒有限制Grain State的定義狀態資料的最大空間限制,但實際上還是會被其Silo的Persistence Storage Provider使用的儲存資料庫機制所限制。基本上跟使用Azure Table Storage Provider的步驟一樣,只需要把原本呼叫 AddAzureTableGrainStorage() 的Azure Table Storage Provider配置程式碼改成呼叫 AddAzureBlobGrainStorage() 指定用Azure Blob Storage Provider即可:
siloBuilder.AddAzureBlobGrainStorage(
    name: "demo_counters", options =>
    {
        options.UseJson = false;
        options.ConfigureBlobServiceClient("UseDevelopmentStorage=true");
    });
這邊使用 UseJson = false,然後跑起來之後嘗試幾個WebApi呼叫讓CounterGrain存資料至Azure Blob之後,用Azure Storage Explorer觀察:
可以看到預設的Azure Blob Storage Provider的行為是序列化成二進位資料之後,再存為Blob檔案內容,可以有效縮小儲存Grain State所需空間。
整個完成的範例程式GitHub專案在:https://github.com/windperson/OrleansRpcDemo/tree/day11
明天會繼續介紹當正式環境的雲端Azure Table/Blob Storage使用"受控識別(Managed Identity)"控制存取權限保護資源時,Orleans 的Silo要如何設定Table/Blob Storage Provider。