Refit是一個用于.NET平臺的REST庫,它可以將REST API轉換為實時類型安全的接口。通過Refit,我們可以輕松實現微服務之間的跨服務調用,使服務間通信變得更加簡單和類型安全。本文將介紹如何在我們的項目中使用Refit來實現微服務間的通信。
一、什么是Refit
Refit是一個強大的REST客戶端庫,它能夠將普通的HTTP API接口轉換為類型安全的.NET接口。通過聲明式的接口定義,Refit可以自動處理HTTP請求的序列化和反序列化,使得微服務之間的通信變得更加簡單和可靠。它的工作原理是在運行時動態生成實現類,將接口方法轉換為對應的HTTP請求。
在使用Refit時,我們只需要定義一個包含所需API調用的接口,并使用特性(Attributes)來描述HTTP請求的細節,如URL、請求方法、請求頭等。Refit會自動處理底層的HTTP通信細節,包括請求的構建、發送以及響應的處理,讓開發者可以專注于業務邏輯的實現,而不必關心底層的通信實現細節。
相比傳統的HttpClient方式,Refit提供了更優雅的API調用方式,并且具有強類型檢查的優勢,可以在編譯時發現潛在的問題。此外,Refit還支持多種高級特性,如請求重試、超時控制、自定義序列化等,使其成為.NET微服務架構中不可或缺的工具之一。
1.1 Refit的主要特性
Refit作為一個強大的REST客戶端庫,提供了豐富的特性支持。它通過簡單的C#接口聲明方式來定義API調用,支持包括GET、POST、PUT、DELETE在內的各種HTTP方法,并可以通過特性靈活配置請求參數和請求頭。在類型安全方面,Refit提供了編譯時類型檢查機制,使用強類型的請求和響應模型,有效避免了運行時可能出現的類型錯誤。對于數據處理,Refit支持JSON、XML等多種格式的自動序列化和反序列化,可以自定義序列化器,并能自動處理復雜對象的轉換。在配置選項方面,Refit支持請求重試策略、超時時間設置、自定義消息處理器,以及集成認證和授權功能。性能方面,Refit通過高效的HTTP請求處理、請求緩存支持和連接池管理來確保最佳性能。
1.2 使用場景
Refit在多個場景中展現出其強大的實用價值。在微服務架構中,它能夠有效處理服務發現和注冊、負載均衡以及斷路器模式等微服務間的通信需求。對于外部API集成,Refit可以優雅地封裝第三方服務調用、RESTful API,并作為高效的WebAPI客戶端。在分布式系統中,Refit支持服務編排、數據聚合,并能夠處理跨服務事務,使得分布式系統的開發和維護變得更加簡單和可靠。
二、SP.FinanceService服務使用Refit
我們將以SP.FinanceService
服務為例,介紹如何使用Refit實現跨服務調用,其中包括了Refit接口定義、調用Refit接口、Refit接口與目標微服務的綁定。
2.1 Refit 接口
在這個服務中,修改和新增記賬記錄的時候需要調用SP.ConfigService
服務的/api/configs/by-type
接口獲取用戶配置的默認幣種。先來看一下這個接口,代碼如下:
///<summary>
/// 根據配置類型獲取配置
///</summary>
[HttpGet("by-type/{type}")]
public ActionResult<ConfigResponse> QueryByType([FromRoute] ConfigTypeEnum type)
{// more code ...
}
這個接口接受一個路由參數type
,這個參數用來區分獲取的配置類型,并返回ConfigResponse
類型的參數。type
參數是一個枚舉類型ConfigTypeEnum
,用于標識不同的配置類型,比如默認幣種、默認賬本等。接口通過[FromRoute]
特性將參數綁定到路由中,這意味著參數值將從URL路徑中獲取,而不是查詢字符串或請求體。返回的ConfigResponse
對象包含了配置的具體信息。
我們已經了解了這個接口,下一步我們就要使用Refit在SP.FinanceService
服務中調用它。首先,新建RefitClient
文件夾,并在里面新建Refit客戶端接口IConfigServiceApi
,我們不需要實現它,這是因為Refit已經為我們提供了默認的實現,這個默認實現已經滿足了大部分的情況。代碼如下:
using Refit;
using SP.Common.Model.Enumeration;
using SP.FinanceService.Models.Response;namespace SP.FinanceService.RefitClient;/// <summary>
/// 配置服務接口
/// </summary>
public interface IConfigServiceApi
{/// <summary>/// 根據類型獲取配置/// </summary>/// <param name="type"></param>/// <returns></returns>[Get("/api/configs/by-type/{type}")]Task<ApiResponse<ConfigResponse>> QueryByType(ConfigTypeEnum type);
}
接口上的[Get("/api/configs/by-type/{type}")]
特性是Refit提供的HTTP方法特性之一,它聲明了這個接口方法將通過HTTP GET請求來調用遠程服務。特性中的路徑"/api/configs/by-type/{type}"定義了實際的API端點,其中{type}是一個路由參數占位符,會在運行時被實際的參數值替換。當我們調用這個接口方法時,Refit會首先通過服務發現機制獲取目標服務的可用實例地址。然后,它會將獲取到的服務基地址與接口路徑組合,構建完整的請求URL。Refit會自動處理參數的序列化,將方法參數正確地映射到HTTP請求中,最后發送HTTP請求到目標服務。
Tip:在使用Refit定義接口時,一定要將接口定義為異步的,這是因為Refit調用數據跨服務調用,異步操作允許系統同時處理多個請求,而不是串行處理。其次,也會避免同步調用可能在高并發情況下出現的死鎖問題。再者,在等待網絡響應時,線程可以執行其他任務,提高CPU和內存的利用效率。
2.2 調用Refit接口
在編寫完成Refit接口,我們就要開始調用它了,調用的方式和調用本地方法一樣,先來看一下代碼:
// more code ../// <summary>
/// 記賬服務實現類
/// </summary>
public class AccountingServerImpl : IAccountingServer
{// more code ...///<summary>/// 用戶配置接口///</summary>private readonly IConfigServiceApi _configService;/// <summary>/// 記賬服務構造函數/// </summary>/// <param name="dbContext"></param>/// <param name="autoMapper"></param>/// <param name="configService"></param>/// <param name="accountBookServer"></param>/// <param name="rabbitMqMessage"></param>/// <param name="currencyServer"></param>public AccountingServerImpl(FinanceServiceDbContext dbContext, IMapper autoMapper,IConfigServiceApi configService, IAccountBookServer accountBookServer, RabbitMqMessage rabbitMqMessage,ICurrencyService currencyServer){_dbContext = dbContext;_autoMapper = autoMapper;_accountBookServer = accountBookServer;_rabbitMqMessage = rabbitMqMessage;_currencyServer = currencyServer;_configService = configService;}// more code .../// <summary>/// 從用戶配置中獲取用戶設置的目標幣種/// </summary>/// <returns>返回目標幣種ID</returns>private long GetUserTargetCurrencyId(){ApiResponse<ConfigResponse> apiResponse = _configService.QueryByType(ConfigTypeEnum.Currency).Result;// 檢查響應是否成功,并且內容不為空if (apiResponse.IsSuccessStatusCode && apiResponse.Content != null){return long.Parse(apiResponse.Content.Value ?? string.Empty);}throw new RefitException($"獲取匯率失敗: {apiResponse.StatusCode}");}
}
在上面的代碼中,我們實現了對SP.ConfigService
服務的調用,就像調用本地方法一樣簡單直觀。具體來說,我們在AccountingServerImpl
類的構造函數中注入了IConfigServiceApi
接口,這個接口是我們之前定義的Refit客戶端接口。在GetUserTargetCurrencyId
方法中,我們通過_configService.QueryByType(ConfigTypeEnum.Currency)
調用遠程服務的API,獲取用戶配置的默認幣種。這個調用過程中,Refit會自動處理HTTP請求的構建和發送,并將返回的結果封裝在ApiResponse<ConfigResponse>
對象中。通過檢查響應的狀態碼和內容,我們可以確保調用成功并獲取到所需的配置值。如果調用失敗或返回內容為空,則拋出RefitException
異常。
我們看到QueryByType
方法封裝了ConfigResponse
,這個類型定義在了當前服務中。我們為什么要在當前服務中重復定義ConfigResponse
,而不是將配置服務的ConfigResponse
提取為公共類型呢?這是因為服務的調用方不一定需要全部的屬性,而且這樣做可以降低服務之間的耦合度。在微服務架構中,每個服務應該是獨立的,能夠獨立部署和演進。如果我們將響應類型放在公共庫中,那么當配置服務的響應結構發生變化時,所有依賴這個公共類型的服務都需要更新和重新部署。通過在每個服務中定義自己需要的響應類型,我們可以更好地控制服務之間的依賴關系,實現服務的松耦合。另外,這種方式也允許不同的服務根據自己的需求定義不同的響應結構,提供了更大的靈活性。當然這也意味著我們需要在服務之間進行適當的數據映射,但這種額外的工作是值得的,因為它帶來了更好的服務獨立性和可維護性。
2.3 Refit接口與目標微服務綁定
最后一步,我們需要將編寫的Refit接口與目標微服務進行綁定,這是實現跨服務調用的關鍵環節。通過綁定告訴Refit應該在Nacos中查找哪個具體的服務實例。這個綁定過程通常在服務啟動時的依賴注入配置中完成。綁定完成后,當我們通過Refit接口發起調用時,框架會自動從Nacos獲取目標服務的可用實例列表,然后根據配置的負載均衡策略選擇合適的實例進行調用。在Program
中新增代碼如下:
// more code ...// 注冊 Refit 客戶端(基于通用服務發現 + Nacos,無需硬編碼 BaseUrl)
var nacosSection = builder.Configuration.GetSection("nacos");
var groupName = nacosSection.GetValue<string>("GroupName") ?? "DEFAULT_GROUP";
var clusterName = nacosSection.GetValue<string>("ClusterName") ?? "DEFAULT";builder.Services.AddNacosRefitClient<IConfigServiceApi>(serviceName: "SPConfigService",groupName: groupName,clusterName: clusterName,scheme: "http");// more code ...
在上面的代碼中,我們通過AddNacosRefitClient
擴展方法將Refit客戶端接口與Nacos服務發現進行了集成。這個方法需要幾個關鍵參數:serviceName
指定了要調用的目標服務在Nacos中注冊的名稱,在這里是"SPConfigService";groupName
和clusterName
分別從配置文件中獲取服務組名和集群名,如果配置文件中沒有指定這些值,則使用默認值"DEFAULT_GROUP"和"DEFAULT";scheme
參數指定了服務調用使用的協議,這里使用"http"。通過這種配置,當服務啟動時,框架會自動在Nacos中查找名為"SPConfigService"的服務實例,并在運行時根據服務發現的結果動態構建請求URL。這種方式避免了硬編碼服務地址,使得服務調用更加靈活和可維護。當目標服務的實例發生變化時,Nacos會自動更新可用實例列表,確保服務調用始終能夠找到正確的目標實例。
Tip:在這里,我們將服務間的調用配置為了http,這是因為服務間的內部調用基本上是安全的。在微服務架構中,服務通常部署在同一個內部網絡或VPC(Virtual Private Cloud)中,網絡環境是受控和隔離的。使用http而不是https可以減少TLS/SSL加密解密帶來的性能開銷,同時簡化了證書管理的復雜性。但是,如果服務需要跨越不同的網絡環境或需要更高的安全要求,我們也可以將scheme配置為https,并配置相應的證書來確保通信安全。在生產環境中,具體使用http還是https應該根據實際的安全需求和部署環境來決定。
三、總結
本文詳細介紹了在.NET微服務架構中使用Refit實現跨服務調用的方法。通過Refit這個強大的REST客戶端庫,我們可以將HTTP API轉換為類型安全的.NET接口,大大簡化了服務間通信的實現。文章以SP.FinanceService
服務為例,展示了如何定義Refit接口、實現跨服務調用以及將接口與目標微服務進行綁定。通過與Nacos服務發現的集成,實現了服務地址的動態發現和負載均衡,避免了硬編碼服務地址的問題。這種實現方式不僅提供了類型安全的服務調用,還保持了服務間的松耦合,使得微服務架構更加靈活和可維護。