iT邦幫忙

2024 iThome 鐵人賽

DAY 24
0

前言

這篇章會把 gRPC 透過 BFF Gateway 轉換成常見的 REST API,並且使用 .http 快速做測試。

研究一下 Grpc.AspNetCore

不知道讀者是否有過好奇,怎麼只寫一個 Proto 檔案卻有一堆已經產好的類別可以直接做使用?答案是這個套件會自動幫你產生一些關於這個 Proto 的 CS 檔案,可以隨意開啟一個 gRPC 專案,在 obj\Debug\net8.0 的資料夾內會找到一些蛛絲馬跡,其實我們用到的所有相關類別都會在這幾個檔案內。

https://ithelp.ithome.com.tw/upload/images/20241007/201689533ubIX5bi7R.png

https://ithelp.ithome.com.tw/upload/images/20241007/201689534S4WF05YKY.png

所以我們這次的目標,就是利用 Grpc.AspNetCore 來幫 BFF Gateway 產生 Client 的工具。

Link Protos

為了避免管理兩份同樣的 Proto 檔案,我們使用 MSBuild 的 Link 屬性來建立連結,這樣就不需要複製 proto 檔案了,至於正式要 Deploy 到部屬環境的時候該如何處理檔案複製的問題,當然是讓公司 DevOps 來處理(笑),實作如下:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <!-- Link Account and Todo proto files -->
    <Protobuf Include="..\..\Account\Account.Grpc\Protos\account.proto" Link="Protos\account.proto" GrpcServices="Client" />
    <Protobuf Include="..\..\Todo\Todo.Grpc\Protos\todoList.proto" Link="Protos\todoList.proto" GrpcServices="Client" />
    <Protobuf Include="..\..\Todo\Todo.Grpc\Protos\todoItem.proto" Link="Protos\todoItem.proto" GrpcServices="Client" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
    <PackageReference Include="Grpc.AspNetCore" Version="2.57.0" />
  </ItemGroup>

</Project>

這邊除了 Link 了檔案路徑,還能將 GrpcServices 指定為 Client,這樣 Grpc.Tools 就只會產生客戶端的類別工具,另外還要記得安裝Grpc.AspNetCore

都做好之後要 dotnet cleandotnet build,確保有自動產生相關工具。

Controllers

新增 Controllers 這件事情我相信會讀到這裡的讀者都很熟悉了,這裡我就不展開太多,這裡我直接拿 Grpc 的 Request 和 Response 來用,這不是一個好作法,自己做的時候記得要建立相關的 DTOs 來做隔離。這裡推薦用 Mapster 為自動化 Mapper 的工具。話不多說,直接上 Code:

AccountController

using Account.Grpc;
using Microsoft.AspNetCore.Mvc;

namespace BFF.Gateway.Controllers;

[ApiController]
[Route("api/[controller]")]

public class AccountController : ControllerBase
{
    private readonly AccountGrpcService.AccountGrpcServiceClient _accountClient;

    public AccountController(AccountGrpcService.AccountGrpcServiceClient accountClient)
    {
        _accountClient = accountClient;
    }

    [HttpPost("register")]
    public async Task<IActionResult> Register([FromBody] RegisterRequest request)
    {
        var response = await _accountClient.RegisterAsync(request);
        return Ok(response);
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        var response = await _accountClient.LoginAsync(request);
        return Ok(response);
    }
}

TodoListController

using Todo.Grpc;
using Microsoft.AspNetCore.Mvc;

namespace BFF.Gateway.Controllers;

[ApiController]
[Route("api/[controller]")]
public class TodoListController : ControllerBase
{
    private readonly TodoListGrpcService.TodoListGrpcServiceClient _todoListClient;

    public TodoListController(TodoListGrpcService.TodoListGrpcServiceClient todoListClient)
    {
        _todoListClient = todoListClient;
    }

    [HttpPost("create")]
    public async Task<IActionResult> CreateTodoList([FromBody] CreateTodoListRequest request)
    {
        var response = await _todoListClient.CreateTodoListAsync(request);
        return Ok(response);
    }

    [HttpDelete("remove/{id}")]
    public async Task<IActionResult> RemoveTodoList(Guid id)
    {
        var response = await _todoListClient.RemoveTodoListAsync(new RemoveTodoListRequest() { Id = id.ToString() });
        return Ok(response);
    }
}

TodoItemController

using Todo.Grpc;
using Microsoft.AspNetCore.Mvc;

namespace BFF.Gateway.Controllers;

[ApiController]
[Route("api/[controller]")]
public class TodoItemController : ControllerBase
{
    private readonly TodoItemGrpcService.TodoItemGrpcServiceClient _todoItemClient;

    public TodoItemController(TodoItemGrpcService.TodoItemGrpcServiceClient todoItemClient)
    {
        _todoItemClient = todoItemClient;
    }

    [HttpPost("create")]
    public async Task<IActionResult> CreateTodoItem([FromBody] CreateTodoItemRequest request)
    {
        var response = await _todoItemClient.CreateTodoItemAsync(request);
        return Ok(response);
    }

    [HttpPost("finish")]
    public async Task<IActionResult> FinishTodoItem([FromBody] FinishTodoItemRequest request)
    {
        var response = await _todoItemClient.FinishTodoItemAsync(request);
        return Ok(response);
    }

    [HttpDelete("remove/{id}")]
    public async Task<IActionResult> RemoveTodoItem(Guid id)
    {
        var response = await _todoItemClient.RemoveTodoItemAsync(new RemoveTodoItemRequest() { Id = id.ToString() });
        return Ok(response);
    }
}

Start Up 和 DI

gRPC 套件有提供 Service Extension 讓我們輕鬆的 DI 我們的 gRPC Client,講白一點就是這套件甚麼都做好了,跟著做即可。

來修改我們的 Program.cs,把 gRPC Client 都 DI 進去,並加上 Controllers 和 Swagger,整理如下:

using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddControllers();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "GrpcRestGateway", Version = "v1" });
});

// gRPC client services for each gRPC service
builder.Services.AddGrpcClient<Account.Grpc.AccountGrpcService.AccountGrpcServiceClient>(o =>
{
    o.Address = new Uri("http://localhost:5077");
});

builder.Services.AddGrpcClient<Todo.Grpc.TodoListGrpcService.TodoListGrpcServiceClient>(o =>
{
    o.Address = new Uri("http://localhost:5144");
});

builder.Services.AddGrpcClient<Todo.Grpc.TodoItemGrpcService.TodoItemGrpcServiceClient>(o =>
{
    o.Address = new Uri("http://localhost:5144");
});

var app = builder.Build();

// Middleware
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "GrpcRestGateway v1"));
}

app.MapControllers();
app.Run();

大家 gRPC Service 的 port 可能不一樣,自己注意一下。

到這邊就成功將 gRPC Endpoint 轉為 REST Endpoint,並且統一由 Gateway 來轉發 Request 了。

Swagger 測試

Preparation

先把 Account.Grpc, Todo.Grpc, BFF.Gatewaydotnet run 起來。

Swagger

用 Browser 打開 http://localhost:[port]/swagger/index.html 檢查一下 Swagger 有沒有開啟

https://ithelp.ithome.com.tw/upload/images/20241007/20168953fLpWWpq0Ce.png

Register and Login

註冊後再登入看看是否有成功

https://ithelp.ithome.com.tw/upload/images/20241007/20168953TFnpHeSgBJ.png

https://ithelp.ithome.com.tw/upload/images/20241007/20168953UpOgkbZRis.png

REST API 測試文件 .http

在我們創建 WebAPI 專案的時候,dotnet 會自動在專案內加入 .http 的測試文件,它可以讓我們使用 REST Client 等套件直接在 IDE(VS Code) 上直接利用文件做測試,Swagger 甚麼的暫時先去旁邊蹲。在這裡,我們可以把 BFF.Gateway.http 改成以下這樣:

@HostAddress = http://localhost:5282

### AccountController - Register
POST {{HostAddress}}/api/Account/register
Content-Type: application/json

{
  "firstName": "string",
  "lastName": "string",
  "email": "string",
  "password": "string"
}

### AccountController - Login
POST {{HostAddress}}/api/Account/login
Content-Type: application/json

{
  "email": "string",
  "password": "string"
}

### TodoItemController - Create Todo Item
POST {{HostAddress}}/api/TodoItem/create
Content-Type: application/json

{
  "listId": "string",
  "content": "string"
}

### TodoItemController - Finish Todo Item
POST {{HostAddress}}/api/TodoItem/finish
Content-Type: application/json

{
  "id": "string"
}

### TodoItemController - Remove Todo Item
DELETE {{HostAddress}}/api/TodoItem/remove/B2D3DB93-6CF2-408A-8360-CFA8AE5AFC88

### TodoListController - Create Todo List
POST {{HostAddress}}/api/TodoList/create
Content-Type: application/json

{
  "userId": "string",
  "name": "string",
  "description": "string"
}

### TodoListController - Remove Todo List
DELETE {{HostAddress}}/api/TodoList/remove/B2D3DB93-6CF2-408A-8360-CFA8AE5AFC88

有了 .http 文件,除了可以快速設置更多參數外,我們也不用在 VS Code 和瀏覽器或 Postman 之間一直切換視窗了。

https://ithelp.ithome.com.tw/upload/images/20241007/20168953Lwe2BeYVhZ.png

結語

還記得在 Account Register 和 Account Login 的時候有介紹過 JWT 如何產生,並且放在 Response 回傳出來,下一篇會在 BFF Gateway 中驗證此 JWT 來當作保護 Endpoint 的安全機制。


上一篇
Day 23 - Integration Event:RabbitMQ 與 Consumer 開發實作
下一篇
Day 25 - BFF Gateway 實作:JWT Bearer Authentication 與 Authorization
系列文
DDD? Clean Architecture? Microservices? 帶你用.NET實作打造一個現代化微服務!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言