TGViewer
C# Geeks (.NET) C# Geeks (.NET) @csharpgeeks · 550 subscribers
Post #563 271
🧩 کد نهایی
در این‌جا نسخه‌ی نهایی منطق cache را می‌بینی:

public static class CurrencyConversion
{
    private record CacheEntry(decimal Rate, DateTime CreatedAt);
    private static readonly ConcurrentDictionary<string, CacheEntry> Cache = new();
    private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5);

    private static readonly ConcurrentDictionary<string, SemaphoreSlim> Locks = new();

    public static async Task<IResult> Handle(
        string currencyCode,
        decimal amount,
        CurrencyApiClient currencyClient)
    {
        // Validate currency code format (3 uppercase letters)
        if (string.IsNullOrWhiteSpace(currencyCode)||
            currencyCode.Length != 3 ||
            !currencyCode.All(char.IsLetter))
        {
            return Results.BadRequest(
                new { error = "Currency code must be a 3-letter uppercase code (e.g., EUR, GBP)" });
        }

        // Validate amount (must be positive)
        if (amount < 0)
        {
            return Results.BadRequest(new { error = "Amount must be a positive number" });
        }

        decimal? rate;
        var semaphore = Locks.GetOrAdd(currencyCode, _ => new SemaphoreSlim(1, 1));
        if (!Cache.TryGetValue(currencyCode, out var cachedRate) &&
            DateTime.UtcNow - cachedRate?.CreatedAt < CacheDuration)
        {
            var acquired = await semaphore.WaitAsync(TimeSpan.FromSeconds(10));
            if (!acquired)
            {
                throw new Exception("Could not acquire lock to fetch exchange rate.");
            }

            try
            {
                // Double-check locking pattern: check again inside the lock
                if (!Cache.TryGetValue(currencyCode, out cachedRate) &&
                    DateTime.UtcNow - cachedRate?.CreatedAt < CacheDuration)
                {
                    rate = await currencyClient.GetExchangeRateAsync(currencyCode);

                    if (rate == null)
                    {
                        return Results.NotFound(
                            new { error = $"Exchange rate for {currencyCode} not found or API error occurred" });
                    }

                    Cache.AddOrUpdate(currencyCode,
                        _ => new CacheEntry(rate.Value, DateTime.UtcNow),
                        (_, _) => new CacheEntry(rate.Value, DateTime.UtcNow));
                }
                else
                {
                    rate = cachedRate!.Rate;
                }
            }
            finally
            {
                semaphore.Release();
            }
        }
        else
        {
            rate = cachedRate!.Rate;
        }

        var convertedAmount = amount * rate.Value;
return Results.Ok(new ExchangeRateResponse(
Currency: currencyCode,
BaseCurrency: "USD",
Rate: rate.Value,
Amount: amount,
ConvertedAmount: convertedAmount
));
}
}

گام بعدی این است که منطق اصلی cache را به یک کلاس reusable جداگانه استخراج کنی 🧩.
به این شکل می‌توانی آن را در بخش‌های دیگر برنامه هم استفاده کنی 🔁.

🎯 جمع‌بندی (Takeaway)

چرا اصلاً این همه دردسر؟ 🤔
دیدن یک ConcurrentDictionary ساده خیلی وسوسه‌برانگیز است 😌 و فکر می‌کنی کار تمام شده.
اما همان‌طور که دیدیم، فاصله‌ی بین «کار می‌کند» و «مقیاس‌پذیر است»
پر از edge caseهایی است که می‌توانند یک سیستم production را زمین‌گیر کنند 💥.

وقتی از یک library استفاده می‌کنی، این edge caseها برایت مدیریت می‌شوند 🛡.
اما ساختن آن با دست خودت، به تو سه ستون اصلی کد با کارایی بالا را یاد می‌دهد 🏛:
🧵 Thread safety
🔒 Lock contention
🛡 Resource protection

گاهی اوقات، «کسل‌کننده‌ترین» بخش‌های زیرساخت، مثل cache،
در واقع جالب‌ترین بخش‌ها از نظر معماری هستند 🧠✨.

و آن ۱٪ مواقعی هم هست که به یک راه‌حل کاملاً سفارشی نیاز داری
که هیچ libraryای ارائه‌اش نمی‌دهد 🛠.
برای همین دانستن اصول پایه واقعاً ارزشمند است.

کتابخانه‌های مدرنی مثل HybridCache یا FusionCache این کارها را برایت انجام می‌دهند 🚀،
اما درک این الگوها باعث می‌شود دقیقاً بدانی چرا برنامه‌ات تحت load آن‌طور که می‌بینی رفتار می‌کند 📊.
More from @csharpgeeks
  1. Sep 22, 2026یه مدتی قراره از دنیای NET. فاصله بگیرم، چون وقتشه برم سربازی. راستش نمیدونم این مدت رو چج…
  2. Sep 20, 2026🔥 حالا مشکل اصلی: Alert Storm فرض کن Database از دسترس خارج شده. ۱۰۰ Pod داری. هر Pod می‌…
  3. Sep 20, 2026🚨 طراحی سیستم Monitoring و Alerting در یک سیستم بزرگ فرض کن ساعت ۳ صبح است. سیستم شما با…
  4. Sep 19, 2026#Engineering_Leadership تصمیم نگرفتن هم یک تصمیم است یه چیز عجیب توی تیم‌های مهندسی: گاهی…
  5. Sep 19, 2026☑ چک‌لیست آماده‌سازی تیم، فرایندها و زیرساخت برای توسعه با AI توجه: هیچ چک‌لیستی جهان‌شمول…
  6. Sep 19, 2026📌پایان یک انتظار طولانی: اعتبارسنجی ناهمگام (Async Validation) در NET 11.
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →