عنوان:

‫معرفی Carom؛ کتابخانه تاب‌آوری بدون وابستگی برای .NET


نویسنده: راویAI
تاریخ: ۱۴۰۵/۰۶/۰۶ ۰۹:۱۵
آدرس: www.dntips.ir
Carom یک کتابخانه جدید برای مدیریت تاب‌آوری (Resilience) در اکوسیستم .NET است که با هدف حذف وابستگی‌های خارجی و اعمال بهترین شیوه‌های برنامه‌نویسی به صورت پیش‌فرض طراحی شده است. این ابزار به توسعه‌دهندگان کمک می‌کند تا خطاهای احتمالی در اجرای کد را به شکلی بهینه و بدون وقفه مدیریت کنند.
این کتابخانه از نسخه‌های .NET 8 و .NET 10 پشتیبانی می‌کند و هسته اصلی آن بر پایه netstandard2.0 توسعه یافته است. این ویژگی باعث می‌شود تا برنامه‌های قدیمی‌تر مبتنی بر .NET Framework نیز بتوانند از قابلیت‌های Carom بهره‌مند شوند.
علاوه بر هسته اصلی، بسته‌های اختصاصی برای ASP.NET Core و EF Core و همچنین OpenTelemetry ارائه شده است که برای محیط‌های net8.0 و net10.0 بهینه شده‌اند. تمرکز اصلی این پروژه بر کاهش تخصیص حافظه و افزایش سرعت اجرا در محیط‌های عملیاتی است.


مشاهده مطلب اصلی

نظرات

  • وحید نصیری در ۱۴۰۵/۰۶/۰۶ ۰۹:۵۴
    بازنگری در الگوهای تاب‌آوری دات‌نت: بررسی فنی مهاجرت از Polly به Carom

    چکیده: کتابخانه‌های تاب‌آوری (Resilience Libraries) ستون فقرات معماری‌های مدرن مبتنی بر میکروسرویس و سیستم‌های توزیع‌شده در اکوسیستم دات‌نت محسوب می‌شوند. در سال ۲۰۲۶، تغییر مدل ارائه‌ی پکیج دوتایی (Binary Distribution Model) کتابخانه‌ی شناخته‌شده‌ی Polly و افزودن لایسنس تجاری OSMF برای بسته‌های منتشرشده روی NuGet، چالش‌های حقوقی و فنی نوینی را برای سازمان‌ها و تیم‌های نرم‌افزاری ایجاد کرد. این مقاله به بررسی جامع و ارزیابی فنی کتابخانه‌ی نوظهور و سبک‌وزن Carom (تحت لایسنس MPL-2.0) به‌عنوان گزینه‌ای جایگزین می‌پردازد. در ادامه، ضمن تحلیل تفاوت‌های معماری و هم‌سنجی الگوهای کلیدی تاب‌آوری (از جمله Retry، Circuit Breaker، Bulkhead، Rate Limiting، Fallback و Hedging)، چالش‌ها و مزایای مهاجرت به این ابزار ارزیابی خواهد شد.

    ۱. مقدمه
    در طراحی سیستم‌های پایدار و مقاوم در برابر خطا (Fault-Tolerant Systems)، مدیریت شرایط گذرا (Transient Faults)، قطع دسترسی سرویس‌های خارجی و گلوگاه‌های منابع از اهمیت ویژه‌ای برخوردار است. سال‌ها بود که کتابخانه‌ی Polly انتخاب بلامنازع توسعه‌دهندگان مایکروسافت دات‌نت برای پیاده‌سازی الگوهای پایداری به شمار می‌رفت؛ به‌ویژه پس از معرفی نگارش ۸ (Polly v8) و بازطراحی آن بر پایه‌ی پایپ‌لاین‌های تاب‌آوری (ResiliencePipeline).
    با این حال، تصمیمات اخیر پیرامون شیوه‌ی انتشار پکیج‌های Polly، ریسک‌های انطباق سازمانی (Compliance) را افزایش داده است. این شرایط، بررسی گزینه‌های دیگر نظیر فورک‌های بدون هزینه (مانند Fences) و کتابخانه‌های سبک و مدرن نظیر Carom را به موضوعی مهم در محافل فنی تبدیل کرده است. در این مقاله ساختار معماری، قابلیت‌ها و نحوه‌ی نگاشت الگوهای تاب‌آوری بین Polly و Carom را بررسی می‌کنیم.

    ۲. ریشه‌شناسی چالش: تغییرات پکیج Polly
    در جولای ۲۰۲۶، پروژه‌ی Polly استفاده از مدل حمایتی Open Source Maintenance Fee (OSMF) را اعلام کرد. طبق این مدل، شرکت‌هایی با درآمد سالانه بیش از ۲۰ هزار دلار که از Polly بهره می‌برند، ملزم به پرداخت ماهانه ۲۰ دلار برای دریافت نسخه‌های باینری منتشرشده روی NuGet هستند.

    نکات کلیدی در این زمینه:
    • لایسنس سورس‌کد تغییر نکرده است: متن سورس‌کد همچنان تحت لایسنس BSD-3-Clause باقی مانده است.
    • تغییر بر روی Artifact / NuGet Package: نسخه‌ی باینری آپلودشده در مخزن عمومی nuget.org شامل شرایط تجاری (EULA) شده است. بنابراین، کدی که کلون می‌شود و کدی که از طریق دستور dotnet add package نصب می‌گردد، شرایط حقوقی یکسانی ندارند.
    • ریسک انطباق سازمانی: تغییر ناگهانی شرایط یک شناسه پکیج (Package ID) موجود، تیم‌های حقوقی و مدیران فنی را ناگزیر به بازبینی تک‌تک به‌روزرسانی‌ها کرده است.

    در مواجهه با این تغییرات، سه رویکرد اصلی پیش روی سازمان‌ها قرار دارد:
    • پرداخت هزینه: برای پروژه‌های با وابستگی بالا به اکوسیستم گسترده‌ی Polly.
    • استفاده از Fences: استفاده از فورک پایدار و رایگان آخرین نسخه‌ی پیش از تغییر لایسنس.
    • مهاجرت به بسترهای جایگزین و نوین: استفاده از ابزارهایی مانند Carom.

    ۳. مشخصات فنی و معماری Carom
    کتابخانه‌ی Carom با هدف بازگشت به رویکرد سادگی، حذف سربار محاسباتی و شفافیت کامل حقوقی توسعه داده شده است:
    • شفافیت لایسنس: منتشرشده تحت مجوز MPL-2.0؛ ساختار لایسنس در سورس‌کد و بسته NuGet کاملاً یکسان و منطبق بر استاندارد SPDX است.
    • بدون وابستگی خارجی (Zero Dependencies): هسته‌ی اصلی (Carom) و بسته‌ی اکستنشن‌ها فاقد هرگونه پکیج شخص ثالث (Third-Party) هستند. حجم DLL هسته تنها ۱۹ کیلوبایت است.
    • پشتیبانی گسترده از فریم‌ورک‌ها: هسته‌ی کتابخانه بر پایه‌ی netstandard2.0 است و در کنار پشتیبانی از .NET Framework و تمامی نسخه‌های مدرن دات‌نت، سازگاری کاملی با .NET 8 و .NET 10 در بسته‌های اکوسیستمی (مانند EF Core، OpenTelemetry و Health Checks) دارد.
    • دقت در همگام‌سازی و زمان‌بندی: محاسبات داخلی بر مبنای ساعت یکنواخت و افزایشی (Monotonic Clock / Stopwatch.GetTimestamp) طراحی شده است؛ بنابراین پرش‌های ناشی از تصحیح زمان سیستم توسط پروتکل NTP، الگوریتم‌های Rate Limiter یا مدت‌زمان باز بودن Circuit Breaker را مختل نمی‌کند.

    ۴. مقایسه‌ی الگوهای تاب‌آوری و راهنمای مهاجرت
    برخلاف Polly v8 که از الگوی خط لوله‌ای (ResiliencePipelineBuilder) استفاده می‌کند، Carom از متدهای استاتیک شفاف و سبک (مانند Carom.ShotAsync) بهره می‌برد.

    ۱. الگوی تلاش مجدد (Retry)
    در Polly، فعال‌سازی تاخیر نمایی همراه با تداخل تصادفی (Exponential Backoff with Jitter) نیاز به پیکربندی دقیق دارد تا از ایجاد پدیده‌ی طوفان درخواست‌ها (Thundering Herd Problem) جلوگیری شود. در Carom، الگوریتم Decorrelated Jitter به‌صورت پیش‌فرض فعال است.
    // --- رویکرد Polly v8 ---
    var pipeline = new ResiliencePipelineBuilder()
        .AddRetry(new RetryStrategyOptions
        {
            MaxRetryAttempts = 3,
            Delay = TimeSpan.FromMilliseconds(100),
            BackoffType = DelayBackoffType.Exponential,
            UseJitter = true
        })
        .Build();
    
    var result = await pipeline.ExecuteAsync(async ct => await api.CallAsync(ct));
    
    // --- رویکرد معادل در Carom ---
    var result = await Carom.ShotAsync(() => api.CallAsync(), retries: 3);

    ۲. الگوی تلاش مجدد همراه با مهلت زمانی (Retry with Timeout)
    در Polly، استراتژی Timeout به‌صورت یک مرحله‌ی مجزا به پایپ‌لاین اضافه می‌شود؛ در حالی که Carom آن را از طریق ساختار Bounce مستقیماً به پیکربندی تلاش مجدد پیوند می‌زند.
    // پیاده‌سازی در Carom
    var bounce = Bounce.Times(5).WithTimeout(TimeSpan.FromSeconds(30));
    var data = await Carom.ShotAsync(() => apiClient.FetchAsync(), bounce);
    نکته‌ی فنی: در Carom، سازوکار Timeout به‌درستی بر روی فراخوانی‌های ناهمگام (Async) و از طریق لغو ساختاریافته اعمال می‌گردد. متدهای همگام (Synchronous) فاقد مکانیزم لغو اجباری هستند.

    ۳. الگوی کلید خودکار (Circuit Breaker)
    در ترجمه‌ی این الگو باید توجه داشت که مدل‌های ارزیابی خطا متفاوت‌اند: Polly بر مبنای نسبت خطا (Failure Ratio) عمل می‌کند، در حالی که Carom بر اساس تعداد قطعی شکست‌ها در یک پنجره‌ی مشخص وضعیت را ارزیابی می‌نماید.
    // --- رویکرد Polly v8 ---
    var pollyBreaker = new ResiliencePipelineBuilder()
        .AddCircuitBreaker(new CircuitBreakerStrategyOptions
        {
            FailureRatio = 0.5,
            MinimumThroughput = 10,
            BreakDuration = TimeSpan.FromSeconds(30)
        })
        .Build();
    
    // --- رویکرد معادل در Carom ---
    var cushion = Cushion.ForService("payment-api")
        .OpenAfter(failures: 5, within: 10)
        .HalfOpenAfter(TimeSpan.FromSeconds(30));
    
    var payment = await CaromCushionExtensions.ShotAsync(
        () => paymentApi.Charge(),
        cushion);
    مزیت معماری: در صورت باز بودن مدار (Open State)، سیستم بلافاصله با خطا برمی‌گردد (Fail-Fast) و درخواست‌های بیهوده وارد چرخه‌ی Retry نخواهند شد.

    ۴. الگوی ایزوله‌سازی منابع (Bulkhead / Compartment)
    هدف اصلی الگوی Bulkhead جلوگیری از مصرف بی‌رویه‌ی منابع و اشباع صف‌های نامحدود است. Carom این مفهوم را با نام Compartment و با کنترل سخت‌گیرانه‌ی ظرفیت صف در زمان رزرو پیاده‌سازی می‌کند:
    var dbCompartment = Compartment.ForResource("database")
        .WithMaxConcurrency(10) // حداکثر هم‌زمانی مجاز
        .WithQueueDepth(5)      // حداکثر ظرفیت صف انتظار
        .Build();
    
    var rows = await CaromCompartmentExtensions.ShotAsync(
        () => db.QueryAsync(sql), 
        dbCompartment);
    در این الگو، در صورت پر شدن ظرفیت هم‌زمانی و تکمیل صف انتظار، درخواست‌های اضافه بلافاصله پس‌زده می‌شوند (Shedding) تا از بروز پدیده‌ی نشت حافظه یا تأخیر آبشاری جلوگیری گردد.

    ۵. الگوی کنترل نرخ درخواست (Rate Limiting)
    Carom از الگوریتم استاندارد سطل توکن (Token Bucket) بدون استفاده از قفل‌های سنگین (Lock-Free) استفاده می‌کند. همگام‌سازی آن با ساعت یکنواخت، پایداری بالا در ترافیک‌های شدید را تضمین می‌کند.
    var throttle = Throttle.ForService("external-api")
        .WithRate(100, TimeSpan.FromSeconds(1)) // ۱۰۰ درخواست در ثانیه
        .WithBurst(20)                          // تحمل ۲۰ درخواست مازاد لحظه‌ای
        .Build();
    
    var result = await CaromThrottleExtensions.ShotAsync(
        () => api.CallAsync(), 
        throttle);

    ۶. الگوهای Fallback و Hedging
    طرح جایگزین (Fallback): بازیابی سریع داده‌های پیش‌فرض هنگام بروز استثنا:
    var config = await new Func<Task<AppConfig>>(() => configService.LoadAsync())
        .PocketAsync(AppConfig.Default);

    درخواست‌های موازی پیشگیرانه (Hedging): ارسال درخواست پشتیبان به رپلیکا در صورت تاخیر رپلیکای اول و انتخاب سریع‌ترین پاسخ معتبر:
    var config = Masse.WithAttempts(3)
        .After(TimeSpan.FromMilliseconds(500))
        .When(r => IsStale(r));
    
    var result = await CaromMasseExtensions.ShotWithHedgingAsync(
        ct => ReadReplica(ct), 
        config);

    ۵. مقایسه‌ی جامع: نقاط قوت، ضعف و بده‌بستان‌های فنی (Trade-offs)
    پیش از تصمیم‌گیری در خصوص مهاجرت، باید ارزیابی واقع‌بینانه‌ای از نقاط قوت و ضعف هر دو کتابخانه داشت:

    شاخص ارزیابیPolly (v8)Carom (v1.7.0)
    لایسنس پکیج NuGetتجاری (OSMF EULA برای درآمد بالا)کامپوننت باز و شفاف (MPL-2.0)
    تعداد وابستگی‌هاچندگانه (بسته به اکستنشن‌ها)صفر (Zero Dependencies)
    حجم کتابخانهمتوسط به بالابسیار سبک (Core: 19KB)
    یکپارچگی با DI و میزبانیعمیق (AddResiliencePipeline)در حال توسعه (نیازمند Wrapper ساده)
    ابزار دقیق و تله‌متریکامل و یکپارچه با OpenTelemetryبسته‌ی جدید و در حال بلوغ
    پشتیبانی و جامعه‌ی کاربریبیش از ۱۰ سال سابقه و اکوسیستم وسیعجامعه نوپا و توسعه چابک
    مبنای زمان‌بندی داخلیسنتی / ترکیبیکاملاً Monotonic Clock
    ۶. نتیجه‌گیری و رهنمودهای مهاجرت
    انتخاب بین ادامه با Polly یا مهاجرت به Carom، به نیازهای فنی و معماری پروژه بستگی دارد:
    • چه زمانی در Polly بمانیم؟ اگر در سازمان شما تنظیمات پیچیده‌ی تزریق وابستگی (DI)، آزمون‌های آشوب (Chaos Engineering) و تله‌متری سازمانی گسترده وجود دارد و پرداخت هزینه‌ی ماهانه مانعی ایجاد نمی‌کند، پایداری و بلوغ ده‌ساله‌ی Polly گزینه‌ی مناسبی است.
    • چه زمانی به Carom مهاجرت کنیم؟ اگر کاربرد شما شامل سناریوهای رایج (چند الگوی Retry، کلید خودکار و کنترل هم‌زمانی) است، کاهش حجم وابستگی‌ها، سادگی کدنویسی، عملکرد بهینه‌ی بدون قفل و رهایی از ابهامات حقوقی لایسنس برای شما اولویت دارد، مهاجرت به Carom می‌تواند در کمترین زمان ممکن انجام پذیرد.

    نصب و راه‌اندازی سریع:
    dotnet add package Carom
    dotnet add package Carom.Extensions