.NET 6 と Daprを使った分散サービス開発 その3 Service Invocationによるマイクロサービス間でのコールとステップ実行

# Service Invocation(サービス間起動)によるマイクロサービス間でのコール

今回は、Daprの機能の一つであるサービス間呼び出しを行います。前回の記事は以下からどうぞ

https://qiita.com/kazumihirose/items/57e3aeaab65a1a68977b

https://qiita.com/kazumihirose/items/f7811bf3e5abe405a551

おさらいします、今回やりたいのは、開発者がローカル環境でAppをコールした際に、バックエンドのServiceAとServiceBをコールして、レスポンスが返ってくる事です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/76e039ba-e2d3-8174-fc26-187ae8120262.png)

フロントREST APIを担当する Appのプロジェクト から バックエンドREST APIであるService A と同じく バックエンドREST APIであるService Bを呼び出し、その結果をマージして返却するような仕組みを作ります。

1. 開発者はローカルからAppのREST APIへGETをコール(1)
1. Service A バックエンドにService Invocation(サービス間起動)でコール(2)
1. Service B バックエンドにService Invocation(サービス間起動)でコール(3)
1. Appは結果をマージしてレスポンスを返却

## 各プロジェクトの編集

## Service A プロジェクト

ほぼ、プロジェクトのデフォルト状態サンプルであるWeatherForecastのそのままです。以下の一か所だけコメントします。 
**//app.UseHttpsRedirection();** フロントのAppはhttpsでトリガされますが、サービス間コールではhttpを使うようにします。このコメントを行った後は、このサービスにtyeで割り当てられたhttpsポートでのアクセスに意味が無くなる事も把握しておいてください。

```csharp: Program.cs 
var builder = WebApplication.CreateBuilder(args);

// 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();

```

## Service B プロジェクト
Aと同様で、プロジェクトのデフォルト状態サンプルであるWeatherForecastのそのままです。以下の一か所だけコメントします。 
**//app.UseHttpsRedirection();** フロントのAppはhttpsでトリガされますが、サービス間コールではhttpを使うようにします。このコメントを行った後は、このサービスにtyeで割り当てられたhttpsポートでのアクセスに意味が無くなる事も把握しておいてください。

```csharp: Program.cs 
var builder = WebApplication.CreateBuilder(args);

// 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();

```

## App プロジェクト

Appプロジェクトでは、Dapr越しに、他のサービスをコールしますので、Daprのクライアントライブラリを導入します。もちろん、Daprではドキュメントを読んでもらうとわかりますが、自前でcurlでPOSTしたり、GRPCでコールしたりなどできますが、SDKで便利にさぼります。現在SDKは、.NET , Python, Java, Go, PHP, Javascript(Node.JS)がstable、C++とRustが開発中です。

https://docs.dapr.io/developing-applications/sdks/

### Client SDK追加

コマンドラインからの場合、Appプロジェクトフォルダのルートに移動して、以下のコマンドを実行してください。
```bash
$ dotnet add package Dapr.AspNetCore
```

Visual Studio 2022では、以下の作業になります。パッケージを右クリックして、nugetですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/3e1d5755-bbb2-e9e3-d47f-820c4e7dc921.png)

.NET向けには二つのライブラリが提供されており。

- Dapr.AspNetCore　(ASP .NET Core向け)
- Dapr.Client (コンソールプログラムや他一般向け）

### Program.csの編集

Appプロジェクトの Program.cs を編集します。
**builder.Services.AddDaprClient();** の箇所で、DIコンテナにサービスとしてDapr Clientを登録しています。

```csharp: Program.cs 
var builder = WebApplication.CreateBuilder(args);
// 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();

// Add Dapr Client << 以下を追加
builder.Services.AddDaprClient();

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();

```

### WeatherForecastController.csの編集

WeatherForecastControllerで、Dapr clientを使い Service Invocation(サービス間起動)でServiceAとServiceBをそれぞれコールします。
それぞれのから得られた5件づつの結果をConcatして10件にして返しています。（すません、**手抜き**です。）

プロパティとして_daprClientを追加、コンストラクタでは、Program.csで登録したDIコンテナからdaprClientサービスを引っ張り出しています。
メソッド、GetWeatherForecastでは、この_daprClientを使ってサービス間起動を行っています。

```csharp: WeatherForecastController.cs
using Dapr.Client;
using Microsoft.AspNetCore.Mvc;

namespace App.Controllers;

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly ILogger<WeatherForecastController> _logger;

    private readonly DaprClient _daprClient;

    public WeatherForecastController(ILogger<WeatherForecastController> logger, DaprClient daprClient)
    {
        _logger = logger;
        _daprClient = daprClient;
    }

    [HttpGet(Name = "GetWeatherForecast")]
    public async Task<IEnumerable<WeatherForecast>> GetAsync()
    {
        IEnumerable<WeatherForecast>? forecastsA = await _daprClient.InvokeMethodAsync<IEnumerable<WeatherForecast>>(HttpMethod.Get, "service-a", "weatherforecast");
        IEnumerable<WeatherForecast>? forecastsB = await _daprClient.InvokeMethodAsync<IEnumerable<WeatherForecast>>(HttpMethod.Get, "service-b", "weatherforecast");
        IEnumerable<WeatherForecast>? forecastsC = forecastsA.Concat(forecastsB);
        return forecastsC.ToArray();
    }
}
```

## Service Invocation(サービス間起動)

もう少し、詳しくサービスを見てみます。
サービス名とメソッド名のみが記載されており、どのポートで起動しているかなどは一切コードに記載がありません。Daprでは、mDNS を使って**名前解決**を行っているからです。ポートやIPを意識する事無く、お互いのサービスをコールする事ができます。

```
_daprClient.InvokeMethodAsync<IEnumerable<WeatherForecast>>(HttpMethod.Get, "service-a", "weatherforecast");
_daprClient.InvokeMethodAsync<IEnumerable<WeatherForecast>>(HttpMethod.Get, "service-b", "weatherforecast");
```

何それ何がうれしいの？って思われるかもしれません。Kubernetes などにPodとして、複数プロセスを起動した事を想像してみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/d73ca7c4-b202-4d90-c809-41c34a6d4f8d.png)

### サービス名ってどこで設定した？ Service Invocation(サービス間起動)

**あれ？名前ってどこにあったっけ？**って思いますよね、tye.yamlを改めて見てみましょう。

```yaml:tye.yaml
~~~~前略~~~~
services:
- name: service-a
  project: ServiceA/ServiceA.csproj
- name: service-b
  project: ServiceB/ServiceB.csproj
- name: app
  project: App/App.csproj
```
ありましたよね？

tye runで実行した際のログには、Daprを起動しているログが残されていますが、ここの引数でサービス名を指定しています。ここの--app-id service-bってのがサービス名です。

```bash:tye run のログより
[20:37:04 INF] Launching service service-b-dapr_1c945e4a-7: C:/dapr/dapr.exe run --app-id service-b --dapr-grpc-port 56373 --dapr-http-port 56374 --metrics-port 56375 --app-port 56366 --log-level debug
```

## Service Invocation(サービス間起動)を試す

tyeで起動してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/02cf2559-c739-b79f-1c63-37a41101651a.png)

起動したら、ダッシュボードからAppのURLを確認して...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/ae45e371-40c9-f3fa-6d75-4a16dfd12ff4.png)

今回はポート65094が割り当てられていました、https://localhost:65094/swagger/index.html って感じでアクセスしてみます。見ての通り、ServiceAの値5つ、ServiceBの値5つを合わせた10個の値が返却されてきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/00257d81-157a-6366-6f0e-bf40f403c0ed.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/778f7392-7449-eec6-b6c3-f767b51eb284.png)

## デバッグとステップ実行を試す(VS 2022)

Visual Studio 2022を使ったステップ実行を試してみます。マイクロサービスでのデバッグの面倒さは、各サービスが複雑に絡み合ってサービス連携している点だと思います。

今回でいえば、以下の図のように、Appからリクエストをトリガして、ServiceAで設定したブレイクポイントで止まってほしいわけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/82c91a31-a4d4-5cf1-76eb-23ee26503ac1.png)

### プロセスをアタッチ
まず、tyeで起動したプロセスにアタッチします。.NETの場合デフォルトの場合はプロジェクト名.exeみたいに起動していると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/9d2cdea3-5906-9fcc-5c68-d73767de7069.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/e548bf12-cb63-f912-7079-00a59380c055.png)


### ブレイクポイントを張る
Visual Studio 2022からソリューションを開きます。ServiceAプロジェクトの WeatherForecastController.cs にブレイクポイントを張ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/cbe5188f-f76b-357f-3c9a-491cde87606c.png)

### トリガする
あとは、App側のSwaggerからリクエストを送れば、こんな感じでブレイクします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/8d09ef53-0baf-df23-39de-9c0cbb84decd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/3c7fcf0a-6b60-84b8-33b6-586d06ea9411.png)

## デバッグとステップ実行を試す(VS Code)

VS Codeは、拡張機能として、Dapr拡張とTye拡張が提供されています。まずは、インストールしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/3941f31d-cbfd-a60b-a70d-7a0b7ef10938.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/a6546c90-f02c-89a2-8e5b-051feaaf81da.png)

### VS Codeでフォルダとしてプロジェクトを開き、プロジェクトにタスクを追加

F1キー(もしくは、ctrl+shift+p)、Tye: Scaffold Tye Taksでタスクを生成します。.vscodeフォルダにはtasks.jsonとlaunch.jsonが生成されます。ついでに、workspaceとして保存もしておきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/41b959ef-915c-5ec6-6c17-e235ef3515fa.png)

### tyeの実行

F1キー(もしくは、ctrl+shift+p)、Task:runでタスクを実行しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/80a4e3fe-634b-bc9d-c0f3-fcf510f88da5.png)

タスクに定義されたtye-runが表示されているので、実行しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/5878eecb-ef7e-fad1-8779-55f4996247a1.png)

実行すると、以下のように統合ターミナルでtyeが実行されているのがわかります。停止する場合はターミナルからctrl+cで停止します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/a7fc06f1-d53b-a43c-3c39-e17005a52aa4.png)

### tye拡張による各サービスの確認

左のTyeのペインから起動している各サービスを確認する事ができます。ダッシュボード機能と同じよう感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/ae96ba55-eccb-d508-8081-f11093dc43d2.png)

ブラウザのマークをクリックすると、バインドされているポートでブラウザが起動し、デバッグマークでプロセスをアタッチしてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/43f8a0ec-db99-2f11-1f6a-4855119f19ef.png)

### ステップ実行

ここでは、Appプロジェクトの WeatherForecastController.cs のGETメソットコール時の所でブレイクポイントを設定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/d3fb300b-e473-8243-3959-fe3202e0c831.png)

F5キーでデバッグを開始します。**watchモード** というデバッグモードで起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/7b69fbb8-1dcd-ebdb-96a2-10fb3efce6a7.png)

tyeエクスプローラーからブラウザを起動しましょう。同じようにswaggerからリクエストを送ってみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/7526ea44-c1ab-493b-6cea-e943c28a8fa6.png)

このエクスプローラーからは、各サービス個別のログも確認できます。（一番右のメモ帳みたいなマーク）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/f034d28a-17f4-21be-a068-63ed157cf7c8.png)

上部のデバッグ用のペインからはアタッチするサービスプロセスを変更する事ができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/bc07b46b-98b7-3a0a-ca54-660fd3282ae1.png)

Swaggerからリクエストを送り、ブレイクさせます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24782/476bfa00-a29d-c1f9-87a5-68f4341a4138.png)