With a Decorator: a class that implements the same interface, wraps the original and adds behavior (caching, logging, metrics, retry).
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 | // ====================================================================== // Q: How would you add caching to a service without changing it? // // With a Decorator: a class that implements the same interface, wraps the // original and adds behavior (caching, logging, metrics, retry). The original // code doesn't change (Open/Closed). // ====================================================================== public interface IPriceService { Task<decimal> GetPriceAsync(string sku, int customerId, CancellationToken ct); } public class CachedPriceService(IPriceService inner, HybridCache cache) : IPriceService { public async Task<decimal> GetPriceAsync(string sku, int customerId, CancellationToken ct) => await cache.GetOrCreateAsync( $"price:{customerId}:{sku}", // price per customer (B2B price lists) async token => await inner.GetPriceAsync(sku, customerId, token), cancellationToken: ct); } // Registration without a library builder.Services.AddHybridCache(); // .NET 9 builder.Services.AddScoped<ErpPriceService>(); builder.Services.AddScoped<IPriceService>(sp => new CachedPriceService( sp.GetRequiredService<ErpPriceService>(), sp.GetRequiredService<HybridCache>())); // or with Scrutor: builder.Services.Decorate<IPriceService, CachedPriceService>(); |