.NET 為分層式架構 API 加入快取

February 27, 2025

要避免尖峰時,大量呼叫的 API 造成頻繁存取資料時,可以為常常重複取得的資料建立快取,使其可從快取直接取得資料,不必查詢資料庫。

簡介

以下用 .NET 的 IMemoryCache 建立單機的快取,如果要多個伺服器都使用相同快取,需考慮用 Redis 建立快取。

在分層式架構中,Presentation (展示) 層負責處理請求,可以將快取加入 Business (業務) 層,將 Persistence (持久) 層的資料暫存起來重複利用。

設定快取的步驟

1 在 Program.cs 註冊快取

var builder = WebApplication.CreateBuilder(args);

// 註冊記憶體快取
builder.Services.AddMemoryCache();

// 註冊 Service
builder.Services.AddScoped<IProductService, ProductService>();

var app = builder.Build();
app.Run();

2 建立 Service (Business) 層的快取

這裡的 ProductService 會先嘗試從快取讀取資料,若快取內沒有資料,則透過 Repository 查詢資料庫,並將結果存入快取。

public interface IProductService
{
    Task<List<Product>> GetProductsAsync();
}

public class ProductService : IProductService
{
    private readonly IMemoryCache _cache;
    private readonly IProductRepository _productRepository;

    public ProductService(IMemoryCache cache, IProductRepository productRepository)
    {
        _cache = cache;
        _productRepository = productRepository;
    }

    public async Task<List<Product>> GetProductsAsync()
    {
        string cacheKey = "product_list";

        // 嘗試從快取讀取
        if (!_cache.TryGetValue(cacheKey, out List<Product> products))
        {
            // 如果快取沒有,則從資料庫查詢
            products = await _productRepository.GetAllProductsAsync();

            // 設定快取,有效時間 10 分鐘
            var cacheOptions = new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromMinutes(10));

            _cache.Set(cacheKey, products, cacheOptions);
        }

        return products;
    }
}

3 建立 Repository (Business) 層

public interface IProductRepository
{
    Task<List<Product>> GetAllProductsAsync();
}

public class ProductRepository : IProductRepository
{
    public async Task<List<Product>> GetAllProductsAsync()
    {
        // 模擬資料庫查詢
        await Task.Delay(500); // 模擬延遲
        return new List<Product>
        {
            new Product { Id = 1, Name = "產品 A" },
            new Product { Id = 2, Name = "產品 B" }
        };
    }
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

4 Controller (Presentation) 層使用快取的 Service

[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet]
    public async Task<ActionResult<List<Product>>> GetProducts()
    {
        var products = await _productService.GetProductsAsync();
        return Ok(products);
    }
}

如果需要在多處設定快取,可以考慮建立自己的快取服務類別,統一管理快取邏輯,並減少重複的程式碼。

參考資料