[C#]Weather Api串接氣象局OpenApi後加上Redis快取
這次的Api例子,以整合氣象局OpenApi為例
以政府機關的OpenApi而言,現在標準上都似乎會整合Swagger提供Api的Documentation,對於瞭解Api的參數與使用方法跟回傳格式還滿便利的
Swagger網址如下
關於氣象局目前公開的OpenApi只要加入氣象局會員就可以取得授權碼
接著創建我們的WebApiApplication,我將之命名為dotNetApiWithRedisCacheApplication
在程式的啟動點Program.cs 我們僅保留需要的feature,例如HttpClient, DI生成服務
using dotNetApiWithRedisCacheApplication.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
builder.Services.AddTransient<IWeatherService, OpenWeatherMapService>();
var app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("/weather", async (string city, IWeatherService weatherService) =>
{
var weather = await weatherService.GetWeatherForTwLocation36HrForecastAsync(city);
return weather is null ? Results.NotFound() : Results.Ok(weather);
}).WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
不看還好,這次一看,其實發現氣象局的天氣 Api回傳的格式相當的豐富,天氣資訊確實也包含了很多維度的資料,而這些分散的數值都被一般化的定義成Parameter
一一將回傳格式物件定義拆分出來如圖存放
OpenApi還有一個還不錯的特性,就是回傳值會包含Fields的型別定義。所以這一塊也可以視需求取用。
這次 透過IWeatherService去隔離實作,程式碼如下,我定義了一個介面與GetWeatherForTwLocation36HrForecastAsync方法
using System.Text.Json;
using dotNetApiWithRedisCacheApplication.Models;
public interface IWeatherService
{
Task<WeatherResponse?> GetWeatherForTwLocation36HrForecastAsync(string locationZhTw);
}
namespace dotNetApiWithRedisCacheApplication.Services;
public class OpenWeatherMapService : IWeatherService
{
private const string _authorizationKey = "氣象局會員授權碼 ";
private IHttpClientFactory _factory;
public OpenWeatherMapService(IHttpClientFactory factory)
{
_factory = factory;
}
public async Task<WeatherResponse?> GetWeatherForTwLocation36HrForecastAsync(string locationZhTw)
{
//一般天氣預報-今明36小時天氣預報
var getNext36HrWeatherForecastApiUrl = "https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-C0032-001";
var httpClient = _factory.CreateClient();
string requestUrl =
$"{getNext36HrWeatherForecastApiUrl}?Authorization={_authorizationKey}&locationName={HttpUtility.UrlEncode(locationZhTw)}";
var response = await httpClient.GetAsync(requestUrl);
if (response.IsSuccessStatusCode)
{
var responseObject =JsonSerializer.Deserialize<WeatherResponse>(await response.Content.ReadAsStringAsync());
return responseObject;
}
throw new Exception($"StatusCode:{response.StatusCode}, Message:{(await response.Content.ReadAsStringAsync())}");
}
}
實測參數傳入不同縣市後,也有回傳了(注意這邊有UrlEncoding)
接著來 為Api加上OutputCache,這邊需要視情況決定是否要加outCache,通常也會視資料的更新需求而定。
若需要即時性的資料,那麼快取的時間也要搭配設計。
先調整實測在Program.cs中加入OutputCache看看
nuget起來
加入Program.cs
builder.Services.AddOutputCache();
app.UseOutputCache();
app.MapGet("/weather", async (string city, IWeatherService weatherService) =>
{
var weather = await weatherService.GetWeatherForTwLocation36HrForecastAsync(city);
return weather is null ? Results.NotFound() : Results.Ok(weather);
}).CacheOutput(a=>a.Expire(TimeSpan.FromMinutes(5)))
.WithName("GetWeatherForecast")
.WithOpenApi();
實測看看第一次仍有進中斷點
第二次確實 沒有進入中斷點了
比較一下networks
從outputcache輸出是200ms,快了 應該有5倍左右
當然使用了dotNet的快取,若沒有依賴外部的服務,快取就會在Server端的內存,除非參數不一樣或是Expire到期,否則的話,就會從內存拉資料。
不過若是面臨到多台主機的話,就有可能發生資料內存不一致的情況,例如a主機未到期,b主機已到期,b主機重新拉取資料後,就會與a主機的內存不一致了。
因此若有考慮分散式api的快取,需要集中快取源的話,可以考慮 dotNet原生的redis快取插件(大陸用語?)
首先先用Docker做一台Redis服務
docker run -d --name "/redis-stack-server" -p 6379:6379 redis/redis-stack-server:latest
接著我們在方案中nuget一個Redis套件 Microsoft.Extensions.Caching.StackExchangeRedis
接著在program.cs的AddOutputCache“後面”直接 加入擴充
builder.Services.AddOutputCache().AddStackExchangeRedisCache(a => { a.InstanceName = "paulApi"; a.Configuration = "localhost:6379"; } ); //沒用, 要加在AddOutputCache後面 //builder.Services.AddStackExchangeRedisOutputCache(a => a.Configuration = "localhost:6379");
實測2次請求一樣是相差滿大的,一個沒快取重取openapi,另一次就會走outputcache
實際用RedisManager看,真的有看到outputCache轉存到redis的情況
其RedisKey的結構為 paulApi__MSOCV_GETHTTPSLOCALHOST:7039/WEATHERQCITY=台北市
看的出來是以Url Request的結構來作儲存,因此若你打的QueryString若有變化的話,就不會從這個快取拉資料了。但若又有一樣參數請求命中的話,就可以從Redis拉 上次的Response出來吐回去,而不用重新打一次 你的資料庫或OpenApi
Github:https://github.com/pin0513/dotNetApiWithRedisCacheApplication
本次的教學影片來自於 Nick Chapsas
從影片學dotNet 8的快取: