前言
聲名式服務調用,己經不算是一個新鮮的話題了,畢竟都出來好些年了。
下面談談,最近項目中用到一個這樣的組件的簡單實踐。
目前部分項目用到的是Refit這個組件,都是配合HttpClientFactory來使用的。
關於HttpClientFactory的一些簡單介紹,可以參見官方文檔,也可以看看前面的兩篇比較粗略的相關介紹。
也簡單介紹一下背景,目前主要有兩類的API接口:
第一類是注冊到Eureka中的,可以通過服務發現的方式來請求的,這里的都是新的接口。
第二類是原始的接口,不能走服務發現,只能通過直連請求的方式來調用,這里的都是些老接口。
換句話就是說,要同時兼容這兩類接口。
由於用HttpClientFactory集成服務發現十分簡單,所以優先選了一個本身就帶有HttpClientFactory的組件--Refit。
什么是Refit
Refit是一個自動類型安全的REST庫,是RESTful架構的.NET客戶端實現,
它基於Attribute,提供了把REST API返回的數據轉化為(Plain Ordinary C# Object,簡單C#對象),POCO to JSON,網絡請求(POST,GET,PUT,DELETE等)封裝,內部封裝使用HttpClient,前者專注於接口的封裝,后者專注於網絡請求的高效,二者分工協作。
我們的應用程序通過 refit請求網絡,實際上是使用 refit接口層封裝請求參數、Header、Url 等信息,之后由 HttpClient完成后續的請求操作,在服務端返回數據之后,HttpClient將原始的結果交給 refit,后者根據用戶的需求對結果進行解析的過程。
更多細節可以參考Refit的官網
創建一個可調用的API接口
直接上控制器的代碼了〜〜
// GET: api/persons
[HttpGet]
public IEnumerable<Person> Get()
{
return new List<Person>
{
new Person{Id = 1 , Name = "catcher wong", CreateTime = DateTime.Now},
new Person{Id = 2 , Name = "james li", CreateTime = DateTime.Now.AddDays(-2)}
};
}
// GET api/persons/5
[HttpGet("{id}")]
public Person Get(int id)
{
return new Person { Id = id, Name = "name" };
}
// POST api/persons
[HttpPost]
public Person Post([FromBody]Person person)
{
if (person == null) return new Person();
return new Person { Id = person.Id, Name = person.Name };
}
// PUT api/persons/5
[HttpPut]
public string Put([FromBody]int id)
{
return $"put {id}";
}
// DELETE api/persons/5
[HttpDelete("{id}")]
public string Delete(int id)
{
return $"del {id}";
}
Refit的使用
先通過Nuget安裝Refit的包。
然后就是定義我們的interface了
public interface IPersonsApi
{
[Get("/api/persons")]
Task<List<Person>> GetPersonsAsync();
[Get("/api/persons/{id}")]
Task<Person> GetPersonAsync([AliasAs("id")]int personId);
[Post("/api/persons")]
Task<Person> AddPersonAsync([Body]Person person);
[Put("/api/persons")]
Task<string> EditPersonAsync([Body]int id);
[Delete("/api/persons/{id}")]
Task<string> DeletePersonAsync(int id);
}
來看看這個interface里面涉及到的部分內容。
- Get,Post等特性就表明了接口的請求方式,后面的值就是請求的相對路徑。
- 相對路徑中,可以使用占位符,來動態更新參數值。
- 如果方法名和請求參數名不一致,需要用AliasAs指明。
- 通過Body特性聲明一個對象作為請求體發送到服務器
- 返回值定義是Task
或者IObservable
然后是配合HttpClientFactory
再通過Nuget安裝一下Refit.HttpClientFactory
如果PersonApi是注冊到Euerka的,可以再添加Steeltoe的引用。
public void ConfigureServices(IServiceCollection services)
{
services.AddRefitClient<IPersonsApi>()
.ConfigureHttpClient(options =>
{
options.BaseAddress = new Uri(Configuration.GetValue<string>("personapi_url"));
//other settings of httpclient
})
//Steeltoe discovery
//.AddHttpMessageHandler<DiscoveryHttpMessageHandler>()
;
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
前面在定義IPersonApi的時候,我們只指定了相對路徑,而請求IP並沒有指定,這里是放到ConfigureHttpClient里面去指定了。
同時根據不同環境,配置不同的appsettings.{env}.json,達到切換的效果。
同樣的,如果想走服務發現,只需要放開注釋的AddHttpMessageHandler,同時修改BaseeAddress為服務名的形式就可以了。
說了這么多,都還只是配置階段,下面就來看看具體怎么用。
為了演示方便,就不在建一個Service層了,直接在控制器調用一下。
用法也很簡單,直接在控制器注入一下就可以使用了。
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IPersonsApi _api;
public ValuesController(IPersonsApi api)
{
this._api = api;
}
// GET api/values
[HttpGet]
public async Task<List<Person>> GetAsync()
{
return await _api.GetPersonsAsync();
}
// GET api/values/5
[HttpGet("{id}")]
public async Task<Person> Get(int id)
{
return await _api.GetPersonAsync(id);
}
// POST api/values
[HttpPost]
public async Task<Person> Post([FromBody] Person value)
{
return await _api.AddPersonAsync(value);
}
}
到這里,代碼層面的東西已經處理完了。
下面來看看使用Refit效果(這里只看兩個Get請求的):
都是能正常拿到我們期望的結果。
最后再看看輸出的日志,確認一下。
首先是訪問/api/values
的
確確實實是向我們前面的PersonApi發起了請求。
然后是訪問/api/values/5555
的
可見我們上面的別名(AliasAs)是起了效果的,能拼成正確的請求地址。
至於其他類型的請求,這里就不演示了,讓大家自己去嘗試一下吧。
總結
Refit用起來還是比較簡單的,運行了一段時間也還表現正常!
當然本文介紹的也只是一些基本的用法!它還具有不錯的擴展性,可以讓我們根據自身需求做一些定制化的東西。
本文的示例代碼RefitClientApi