0%

[DotnetCore]Refit:類ApiGateway實作

前情提要

筆者在公司環境開發系統,依照公司規定有一些Secrurity Policy要遵守,尤其是對外站台(前台),因為是對外開放的站台,按照資安規定,需要完全是一個乾淨的站台,不連資料庫,不做商業邏輯處理,只接收將客戶端傳來的HttpRequest,轉發到商業邏輯處理站台(後台),因此筆者才在此篇的標題上打上類APIGateway的字言,因為沒有完全符合ApiGateway該有的功能,類似像LoadBalancingCachingRetry Policy等重要指標,ApiGateway完整功能可參考https://github.com/ThreeMammals/Ocelot的Features清單就可以略知一二。

內容

講到ApiGateway的其中一個主要功能是Request的轉發,這時候透過Refit轉發是最適合不過了,主要原因

  • 完全可以模仿像Ocelot這種ApiGateway的模式:用Json設定其ApiRoute資訊,換到Refit的世界來說,就是定義好Api Repository Interface,與json格式相比,多了一些Request、Response強型別物件的宣告作業,但也多了一份踏實感
  • Response的解析,可以利用Refit提供的ApiResponse<T>來做一些統一的ResponseArragement方法,可以想像一下,若自己透過HttpClientRequest,類似像ValidationErrorResponse,後台可能會透過400BadRequest的方式回應,對於該HttpRequest請求來說會是一個HttpException,因此必須要做很多的TryCatch來完成一個請求的Respsonse整理,看一下文章後面的程式碼會更有感覺。

ApiRepository的宣告

雖然沒有ApiGateway的一些高大尚的功能,還是要有基本的Authentication功能,因此會有兩個ApiResource要宣告

  • 內部AuthServer
  • 商業邏輯處站台(後台)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface IAuthServerApiRepository
{
// 取得AccessToken
[Post("/api/v1/oauth/token")]
Task<ApiResponse<GetAccessTokenResponseDto>> GetAccessToken([Body(BodySerializationMethod.UrlEncoded)] GetAccessTokenRequestDto request);
// 驗證Token
[Post("/api/v1/oauth/validate")]
Task<ApiResponse<GetPermissionResponseDto>> ValidateToken([Header("Authorization")] string token, [Header("X-Client-Id")] string clienId);
}

public interface IAppServerApiRepository
{
// 建立訂單
[Post("/api/v1/order")]
Task<ApiResponse<OrderResponseDto>> OrderCreate(OrderRequestDto orderRequestDto);
}

註冊ApiRepository

這節筆者在其他Refit介紹文章中已經解釋過,不另外特別解釋,直接貼出code

1
2
3
4
5
6
7
8
9
10
services.AddRefitClient<IAppServerApiRepository>()
.ConfigureHttpClient(x =>
{
x.BaseAddress = new Uri(Configuration["BaseUrl:AppServer"]);
});
services.AddRefitClient<IAuthServerApiRepository>()
.ConfigureHttpClient(x =>
{
x.BaseAddress = new Uri(Configuration["BaseUrl:AuthServer"]);
});

Response解析

開發思路大概是這樣,以前台來說從Api入口收到Request後,透過Refit轉發HttpRequest,收到HttpResponse後要轉成IActionResult的格式回傳ApiResponse,中間多了一些轉換。

筆者這邊都習慣會先抽一個BaseControllerBaseApiController放著,其他各自的Controller都先繼承該BaseController,好處馬上就用到了,這種統一解析Response的功能就可以寫在BaseApiController上面了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using Refit;
using System.Text.Json;

protected async Task<IActionResult> ResponseArrangement<T>(ApiResponse<T> response)
where T : class
{
// 以上省略

switch (response.StatusCode)
{
case System.Net.HttpStatusCode.OK:
return Ok(response.Content);
case System.Net.HttpStatusCode.BadRequest:
return BadRequest(JsonSerializer.Deserialize<Object>(response.Error.Content));
default:
return StatusCode(500, JsonSerializer.Deserialize<Object>(response.Error.Content));
}
}

看了以上code,不要說其他好處,光程式碼長度就已經是非用它不可的理由,程式碼邏輯清楚,簡短,對於錯誤追縱也是有很大的幫助的。

畢竟前台定位是左手進,右手出,因此不要處理過多的邏輯,出錯機率也會大大的降低,找問題也比較快,因此透過Refit提供的ApiResponse來包裝Response是最好不過的選擇了。

前台Api宣告

上面把Api處理所需的相關邏輯方法都宣告完畢,進入到最後一個環節,寫Api Controller邏輯,Refit的好處之一,就像呼叫方法一樣呼叫,其實是完成一個HttpRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[Route("api/v{version:apiVersion}/orders")]
[ApiController]
public class OrderController : BaseApiController
{
private readonly IAppServerApiRepository _appServiceApi;

public OrderController(IAppServerApiRepository appServiceApi, IAuthServerApiRepository authServerApiRepo) : base(authServerApiRepo)
{
_appServiceApi = appServiceApi;
}

/// <summary>
/// 訂單建立作業
/// </summary>
/// <param name="requestDto"></param>
/// <returns></returns>
[HttpPost("")]
public async Task<IActionResult> CreateOrder(OrderRequestDto requestDto)
{
var response = await _appServiceApi.OrderCreate(requestDto);
return await ResponseArrangement(response);

}
}

結論

筆者因文章篇幅及AuthServer的使用跟Refit無相關就不在此篇示範呼叫AuthServer的相關程式碼,因為ApiResponse<T>這個已經包含TryCatch的程序,因此只要判斷ResponseStatusCode,很輕易地處理BadRequest的回應,對於這種開發前台站台來說,應該是不用到一天就可以完成開發,藉由此篇,祝大家都能早早下班。

參考