1. Memory Cache Simple sample
2. Memory Cache Interface sample (web XML Data)
- xml 데이타 : https://auctionpro.co.kr/airlinecode.xml
- xml 데이타 이용한 메모리 캐싱 샘픔
Web XML Data
UML
Program Files
AirlineController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
using Microsoft.AspNetCore.Mvc; using InMemoryCacheCore.Models; using InMemoryCacheCore.Services; namespace InMemoryCacheCore.Controllers { [ApiController] [Route("[controller]")] public class AirlineController : ControllerBase { private readonly ICodeService _codeService; private readonly ICacheService _cacheService; private readonly ILogger<AirlineController> _logger; public AirlineController(ICodeService codeService, ICacheService cacheService, ILogger<AirlineController> logger) { _codeService = codeService ?? throw new ArgumentNullException(nameof(codeService)); _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService)); _logger = logger; } [HttpGet(Name = "AirlineCode")] public async Task<IEnumerable<Code>> Get() { try { IEnumerable<Code> codes = _cacheService.GetCachedCode("_Codes"); if (codes == null || !codes.Any()) { codes = await _codeService.GetCodeAsync(); } return codes ?? new List<Code>(); //codes = codes.Where(s => s.Airlinecode.Equals("KE", StringComparison.Ordinal)) ?? new List<Code>(); //return codes; } catch (Exception ex) { _logger.LogError(ex, "정보를 가져오는 동안 오류가 발생했습니다."); throw; } } } } |
AirlineHttpClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
using InMemoryCacheCore.Models; using System.Xml.Serialization; namespace InMemoryCacheCore.Infrastructure { public interface IHttpClient { Task<IEnumerable<Code>> Get(); } public class AirlineHttpClient : IHttpClient { private const string API_URL = "https://auctionpro.co.kr/airlinecode.xml"; private readonly HttpClient _httpClient; public AirlineHttpClient(HttpClient httpClient) { _httpClient = httpClient; } public async Task<IEnumerable<Code>> Get() { HttpResponseMessage response = await _httpClient.GetAsync(API_URL); if (response.IsSuccessStatusCode) { await using var responseStream = await response.Content.ReadAsStreamAsync(); var serializer = new XmlSerializer(typeof(Root)); var codesResponse = serializer.Deserialize(responseStream) as Root; var codes = codesResponse?.Code ?? new List<Code>(); return codes; } else { // 예외 메시지에 실패 원인 추가 throw new Exception($"API 호출 실패: {response.StatusCode}"); } } } } |
CacheProvider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using Microsoft.Extensions.Caching.Memory; namespace InMemoryCacheCore.Infrastructure { public interface ICacheProvider { T? GetFromCache<T>(string key) where T : class; void SetCache<T>(string key, T value, MemoryCacheEntryOptions options) where T : class; void ClearCache(string key); } public class CacheProvider : ICacheProvider { private readonly IMemoryCache _cache; public CacheProvider(IMemoryCache cache) { _cache = cache; } public T? GetFromCache<T>(string key) where T : class { //_cache.TryGetValue(key, out T cachedResponse); //return cachedResponse as T; return _cache.Get<T>(key); } public void SetCache<T>(string key, T value, MemoryCacheEntryOptions options) where T : class { _cache.Set(key, value, options); } public void ClearCache(string key) { _cache.Remove(key); } } } |
Models->Airlines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using System.Xml.Serialization; namespace InMemoryCacheCore.Models { [XmlRoot(ElementName = "Root")] public class Root { [XmlElement(ElementName = "Code")] public List<Code>? Code { get; set; } } [XmlRoot(ElementName = "Code")] public class Code { [XmlElement(ElementName = "Airlinecode")] public string Airlinecode { get; set; } [XmlElement(ElementName = "Airline3code")] public string? Airline3code { get; set; } [XmlElement(ElementName = "Airlinekor")] public string? Airlinekor { get; set; } [XmlElement(ElementName = "Airlineeng")] public string? Airlineeng { get; set; } } } |
CachedCodeService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
using InMemoryCacheCore.Infrastructure; using InMemoryCacheCore.Models; using Microsoft.Extensions.Caching.Memory; namespace InMemoryCacheCore.Services { public class CachedCodeService : ICodeService { private readonly CodeService _codeService; private readonly ICacheProvider _cacheProvider; private const int CacheTTLInSeconds = 100; private readonly MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromSeconds(CacheTTLInSeconds)); private static readonly SemaphoreSlim GetUsersSemaphore = new(1, 1); public CachedCodeService(CodeService codeService, ICacheProvider cacheProvider) { _codeService = codeService; _cacheProvider = cacheProvider; } public async Task<IEnumerable<Code>> GetCodeAsync() { return await GetCachedResponse("_Codes", GetUsersSemaphore, () => _codeService.GetCodeAsync()); } private async Task<IEnumerable<Code>> GetCachedResponse(string cacheKey, SemaphoreSlim semaphore, Func<Task<IEnumerable<Code>>> func) { var users = _cacheProvider?.GetFromCache<IEnumerable<Code>>(cacheKey); if (users != null) return users; try { await semaphore.WaitAsync(); users = _cacheProvider?.GetFromCache<IEnumerable<Code>>(cacheKey); // Recheck to make sure it didn't populate before entering semaphore if (users != null) return users; users = await func(); _cacheProvider?.SetCache(cacheKey, users, cacheEntryOptions); } finally { semaphore.Release(); } return users; } } } |
CacheService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using InMemoryCacheCore.Infrastructure; using InMemoryCacheCore.Models; namespace InMemoryCacheCore.Services { public interface ICacheService { IEnumerable<Code> GetCachedCode(string cacheKey); } public class CacheService : ICacheService { private readonly ICacheProvider _cacheProvider; public CacheService(ICacheProvider cacheProvider) { _cacheProvider = cacheProvider; } public IEnumerable<Code> GetCachedCode(string cacheKey) { return _cacheProvider.GetFromCache<IEnumerable<Code>>(cacheKey); } public void ClearCache(string cachekey) { _cacheProvider.ClearCache(cachekey); } } } |
CodeService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
using InMemoryCacheCore.Infrastructure; using InMemoryCacheCore.Models; namespace InMemoryCacheCore.Services { public interface ICodeService { Task<IEnumerable<Code>> GetCodeAsync(); } public class CodeService : ICodeService { private readonly IHttpClient _httpClient; public CodeService(IHttpClient httpClient) { _httpClient = httpClient; } public Task<IEnumerable<Code>> GetCodeAsync() { return _httpClient.Get(); } } } |
Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
using InMemoryCacheCore.Infrastructure; using InMemoryCacheCore.Services; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // builder.Services.AddHttpClient(); builder.Services.AddSingleton<CodeService>(); //MainService builder.Services.AddSingleton<ICodeService, CachedCodeService>(); //User 연결 서비스 builder.Services.AddSingleton<ICacheService, CacheService>(); //캐싱 서비스 builder.Services.AddSingleton<ICacheProvider, CacheProvider>(); //infrastructure builder.Services.AddSingleton<IHttpClient, AirlineHttpClient>(); //infrastructure builder.Services.AddMemoryCache(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); |
Response body
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
[ { "airlinecode": "0B", "airline3code": null, "airlinekor": "Blue Air", "airlineeng": null }, { "airlinecode": "0J", "airline3code": null, "airlinekor": "Premium Jet AG", "airlineeng": null }, { "airlinecode": "0Y", "airline3code": null, "airlinekor": "FlyYeti", "airlineeng": null }, { "airlinecode": "1M", "airline3code": null, "airlinekor": "JSC Transport Auto Info Systems", "airlineeng": null }, { "airlinecode": "1X", "airline3code": null, "airlinekor": "Branson Air Express", "airlineeng": null }, { "airlinecode": "2A", "airline3code": null, "airlinekor": "Deutsche Bahn", "airlineeng": null }, { "airlinecode": "2B", "airline3code": null, "airlinekor": "Albawings", "airlineeng": null }, { "airlinecode": "2C", "airline3code": null, "airlinekor": "SNCF", "airlineeng": null }, { "airlinecode": "2D", "airline3code": null, "airlinekor": "Dynamic Airways", "airlineeng": null }, { "airlinecode": "2E", "airline3code": null, "airlinekor": "Smokey Bay Air", "airlineeng": null }, { "airlinecode": "2G", "airline3code": null, "airlinekor": "DEBONAIR AIRWAYS", "airlineeng": null }, { "airlinecode": "2I", "airline3code": null, "airlinekor": "Star Peru", "airlineeng": null }, { "airlinecode": "2J", "airline3code": null, "airlinekor": "Air Burkina", "airlineeng": null }, ............................ ] |