es
Feedback
Developer Advocate

Developer Advocate

Ir al canal en Telegram
Buy Ad
1 035
Suscriptores
Sin datos24 horas
Sin datos7 días
-130 días
Archivo de publicaciones
دسترسی به مقالات http://iVPN.Pro رایگان شد! در این روزهای سخت، کنار هم‌بودن از همیشه مهمتره. یک مقاله‌ی جدید و سد شکن هم تا ع
دسترسی به مقالات http://iVPN.Pro رایگان شد! در این روزهای سخت، کنار هم‌بودن از همیشه مهمتره. یک مقاله‌ی جدید و سد شکن هم تا عصر منتشر میشه. https://ivpn.pro/

Repost from gooyban🦆
به احتمال زیاد تو خونه‌تون یه گوشی اندرویدی پیدا می‌شه که به v2ray وصل بشه. با این روش می‌شه اون v2ray را برای دیگر دستگاه‌ها هم به اشتراک گذاشت. این ویدئو را قبلا در یوتیوب گذاشته بودم و امیدوارم الان تو این شرایط به کارتون بیاد، کم حجمش کردم و در تلگرام هم گذاشتم که اگه کسی نیازش می‌شه راحت‌تر دسترسی داشته باشه، دانلودش کنید و اگه فکر می‌کنید ممکنه به درد کسی بخوره به اشتراک بذارید. نمی‌دونم چطور ولی مراقب خودتون باشید ❤️ ویدئوهای مربوط: 📱📱 📱📱 وصل شدن به VPN روی لینوکس ــــــــــــــــــــــــــــــــــــــــــــــــــ 📱🔐🔐 آموزش V2ray روی لینوکس 📱 آموزش v2rayN روی لینوکس 🔐 به اشتراک گذاری v2rayNG گوشی اندرویدی با دیگر دستگاه‌ها ــــــــــــــــــــــــــــــــــــــــــــــــــ 🌐 آموزش پروکسی روی لینوکس 📱 تغییر DNS روی لینوکس ــــــــــــــــــــــــــــــــــــــــــــــــــ 💙 کانال برای دریافت کانفیگ رایگان: @Ln2ray

🔵 مقایسه عمیق: CSS-in-JS vs. Utility-first CSS (مانند Tailwind) در پروژه‌های frontend مدرن، انتخاب بین CSS-in-JS (مانند Emotion, Styled Components) و Utility-first CSS (مانند Tailwind CSS) فراتر از سلیقه شخصی، تأثیر مستقیم بر maintainability، performance و even team workflow دارد. بیایید وجوه کلیدی این رویکردها را بررسی کنیم: 1️⃣ Isolation و Encapsulation CSS-in-JS برای هر کامپوننت style منحصربه‌فرد جنریت می‌کند (scoped styles). این جداسازی مشکلات رایج مانند “global leakage” و “naming collision” را حذف می‌کند. Tailwind با سازوکار atomic کلاس‌ها و avoidance از overrideهای تصادفی به maintainability کمک می‌کند اما به طور طبیعی به isolation runtime دست نمی‌یابد. 2️⃣ Developer Experience (DX) در CSS-in-JS، themeها، مقادیر متغیر و حتی توابع منطقی ‌را مستقیم در style استفاده می‌کنید. مثال:
// نمونه‌ای از View model dynamic Styling
public class ButtonProps
{
    public bool IsPrimary { get; set; }
}
const Button = styled.button`
  background: ${props => props.IsPrimary ? theme.primary : theme.secondary};
  border-radius: 6px;
  transition: box-shadow .2s ease;
`;
در Tailwind، توسعه‌دهنده باید دانش utilityهای موجود و ترکیب آنها را کسب کند. سرعت و predictability معمولا بالاتر است اما منطق پویا نیازمند ترکیب conditionals برای افزودن کلاس‌هاست (غالباً با clsx یا twMerge). 3️⃣ Performance: Style Generation vs. Atomicity CSS-in-JS در build یا runtime، styleها را به DOM inject می‌کند؛ در SSR، مدیریت critical CSS می‌تواند پیچیده شود. در مقابل، Tailwind با تولید فایل CSS بسیار کوچک (purged)، مقدار CSS ارسال‌شده را به حداقل می‌رساند. اما استفاده افراطی از utilityها گاهی منجر به bloated HTML یا duplication می‌شود. 4️⃣ Maintainability و Refactoring در پروژه‌های تیمی بزرگ، CSS-in-JS ردیابی وابستگی‌ها و حذف styleهای بلااستفاده را ساده‌تر می‌کند؛ styleها دقیقاً کنار کد مرتبط قرار دارند. اما در Tailwind، فهم معنای مجموعه‌ای از utilityها گاهی دشوار است (مثلاً تفاوت px-4 py-2 text-indigo-600 با px-2 py-1 text-blue-500). Intellisenseها و ابزارهای مدرن این ضعف را تا حدی جبران کرده‌اند. 5️⃣ Integrations & Extensibility CSS-in-JS به صورت طبیعی با TypeScript، themeهای runtime و کدهای dynamic پیچیده ادغام می‌شود؛ درحالی‌که Tailwind بیشتر برای پیکربندی‌های design سیستم ثابت مناسب است، هرچند امکانات جدید (مانند arbitrary values) مرزها را جا‌به‌جا کرده‌اند. 👨‍💻 جمع‌بندی عملیاتی برای تیم‌ها: - اگر پروژه شما به styleهای dynamic و شخصی‌سازی عمیق نیاز دارد (به‌ویژه در React/Next.js)، یا complexity بالا دارید: CSS-in-JS انتخاب مناسبی است. - اگر سرعت، uniformity و onboarding سریع‌تر افراد جدید مهم‌تر است و design system شما atomic است: Tailwind معمولا گزینه سریع‌تر و مقیاس‌پذیرتری خواهد بود. انتخاب شما باید با معماری محصول و maturity تیم align باشد؛ در پروژه‌های enterprise واقعی، حتی گاهی ترکیب هوشمند این دو رویکرد (مثلاً استفاده از Tailwind با variantهای CSS-in-JS برای حالات خاص) ارزش بررسی دارد. @DeveloperAdvocate 🥑

اندیس‌گذاری در دیتابیس، بنیان کارایی کوئری‌های پیچیده است. فرض کنید جدولی به نام Users داریم با میلیون‌ها رکورد:
CREATE TABLE Users (
    Id INT PRIMARY KEY,
    Username NVARCHAR(100),
    Email NVARCHAR(320),
    CreatedAt DATETIME
)
حال کوئری زیر را در نظر بگیرید:
SELECT * FROM Users WHERE Email = 'ali@example.com'
بدون اندیس، موتور دیتابیس باید کل جدول را اسکن کند (Table Scan) که حتی در بهترین سخت‌افزارها هم می‌تواند بسیار کند باشد. افزودن ایندکس روی ستونی که جست‌وجوی زیادی روی آن می‌شود، مثل Email، کوئری را از Full Scan به Index Seek تبدیل می‌کند:
CREATE INDEX IX_Users_Email ON Users(Email)
در این حالت: - دیتابیس فقط به اندیس (ساختاری مانند B-Tree) رجوع می‌کند. - عملیات جست‌وجو از O(n) به O(log n) می‌رسد. - Latency کوئری حتی در دیتاهای چندگیگابایتی به میلی‌ثانیه محدود می‌شود. نکته ویژه برای آرشیتکت‌ها: اندیس‌ها برای کوئری‌های WHERE، JOIN و ORDER BY موظف هستند، اما هزینه‌های نوشتن (INSERT/UPDATE/DELETE) و نگهداری آن‌ها را هم باید در نظر بگیرید—تعادل بین سرعت خواندن و نوشتن حیاتی است. همیشه Query Plan را آنالیز کنید تا بدانید ایندکس‌های شما واقعاً اعمال می‌شوند. #DBIndexing #PerformanceTuning #SQLOptimization @DeveloperAdvocate 🥑

🎯 کار با Git فراتر از دستورات مرسوم: چرا و چطور از interactive rebase و cherry-pick استفاده کنیم؟ در تیم‌های حرفه‌ای، مدیریت تاریخچه گیت مثل یک هنر است. وقتی نیاز به تمیز و منسجم نگه داشتن log پروژه دارید یا باید هات‌فیکس‌ها را عمیقاً کنترل کنید، دو ابزار کلیدی دارید: interactive rebase و cherry-pick. ✅ interactive rebase فرض کنید چندین کامیت پشت سر هم روی feature branch دارید و حالا باید قبل از merge یا PR، تاریخچه را تمیز و منطقی (و البته اتمیک) کنید. با اجرای:
git rebase -i HEAD~5
می‌توانید ۵ کامیت آخر را در یک ویرایشگر باز کنید. اینجا دست باز دارید: - با pick، squash، یا reword پیام کامیت‌ها و ساختار آن‌ها را متناسب تیم بازنویسی کنید. - کامیت‌های کوچک و نامرتب را ترکیب یا با هم ادغام کنید تا تنها یک کامیت معنادار حاصل شود (squash). - پیام‌ها را به صورت استاندارد و با مرجع JIRA یا تیکت نگارش کنید. این کار نه فقط خوانایی log را بالا می‌برد، بلکه وقتی مشکل production پیش آمد، git bisect کارآمدتر می‌شود. ✅ cherry-pick گاه فقط یک commit خاص باید به branch دیگری انتقال یابد، بدون اینکه کل تاریخچه سرریز شود. مثلا یک هات‌فیکس در master باید همزمان وارد release/1.1 شود تا به مشتری برسد:
git checkout release/1.1
git cherry-pick 123abc4
این رویکرد، کاملاً قابل ردیابی و ایزوله است و باعث نمی‌شودکامیت‌هایی که هنوز باید code review شوند یا تست نشده‌اند را ناخواسته merge کنید. 👑 Best Practices Advanced - همیشه قبل از interactive rebase یا cherry-pick روی branchهای اشتراکی، با تیم هماهنگ باشید؛ زیرا rebase تاریخچه را بازنویسی می‌کند و force push اجتناب‌ناپذیر است. - از ویژگی --autosquash هنگام interactive rebase استفاده کنید تا کامیت‌های "fixup!" به طور خودکار بر کامیت والد‌شان، squash شوند. - در release trackهای حساس، برای هر cherry-pick شماره مرجع task یا تیکت را در پیام کامیت درج کنید:
  git cherry-pick -x <SHA>
  
تا پیام commit شامل رفرنس به کامیت اصلی باشد. اگر واقعا به معماری فرایند تحویل و traceability اهمیت می‌دهید، این ابزارها را به صورت روزمره به کار بگیرید و فرهنگ commit discipline را در تیم تقویت کنید. @DeveloperAdvocate 🥑

🎯 کد پیشرفته C#: کوئری آیتم‌های مشابه با استفاده از دیتابیس برداری (Vector Database) در سناریوهای RAG (Retrieval-Augmented Generation)، جستجو بر اساس بردار‌ (‌vector search) نقشی حیاتی دارد. مثال زیر نحوه کوئری‌ زدن به یک دیتابیس برداری (مثلاً Qdrant یا Milvus) و یافتن نزدیک‌ترین آیتم‌ها را با C# و HTTPClient نمایش می‌دهد. فرض کنید بردار Embedding متن سرچ شما قبلاً تولید شده است.
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;

var client = new HttpClient();
var baseUrl = "http://localhost:6333"; // آدرس Qdrant یا سرویس شما
var collection = "documents";

float[] queryVector = /* بردار کوئری */ new float[] {0.12f, -0.87f, 0.23f, ...};
int topK = 5;

var requestBody = new
{
    vector = queryVector,
    top = topK,
    with_payload = true
};

var response = await client.PostAsJsonAsync(
    $"{baseUrl}/collections/{collection}/points/search",
    requestBody
);

response.EnsureSuccessStatusCode();
var resultJson = await response.Content.ReadAsStringAsync();

var result = JsonDocument.Parse(resultJson);
foreach (var point in result.RootElement.GetProperty("result").EnumerateArray())
{
    var payload = point.GetProperty("payload");
    var score = point.GetProperty("score").GetDouble();
    string docId = payload.GetProperty("id").GetString();
    string text = payload.GetProperty("text").GetString();
    Console.WriteLine($"Score: {score:F4} - Id: {docId} - Text: {text.Substring(0, Math.Min(80, text.Length))}...");
}
💡 نکته: برای embed متن از Azure OpenAI, HuggingFace یا سایر سرویس‌ها استفاده کنید. دستورات و قراردادهای API ممکن است بسته به دیتابیس برداری متفاوت باشند. Integrate کد در پترن‌های Clean Architecture و Task-based async برای مقیاس‌پذیری و نگهداری بهتر توصیه می‌شود. #VectorSearch #CSharp #RAG #NeuralRetrieval @DeveloperAdvocate 🥑

📊 دیتابیس‌های گرافی: وقتی ارتباط مهم‌تر از داده است در معماری سیستم‌هایی که داده‌ها با روابط پیچیده و پویا مدل می‌شوند، دیتابیس‌های گرافی مانند Neo4j یک انتخاب بدیهی‌اند. برخلاف دیتابیس‌های رابطه‌ای که برای روابط بسیاری-به-بسیار به JOINهای سنگین و کند متکی‌اند، پایگاه‌های گرافی به‌شکل بومی روابط را به‌عنوان first-class citizen ذخیره و جستجو می‌کنند. بیایید یک مثال واقعی ببینیم: موتور پیشنهاد محصول (Product Recommendation Engine) برای فروشگاه آنلاین. در این سناریو، کاربران محصولات متعددی را می‌خرند یا مشاهده می‌کنند. شما می‌خواهید بر پایه روابط غیرمستقیم—مثلاً کاربرانی که محصولات مشابهی خریده‌اند یا الگوهای رفتاری مشابه دارند—پیشنهادها را نمایش دهید. در Neo4j می‌توانید چنین گرافی بسازید: - گره‌ها (Nodes): User, Product - یال‌ها (Edges): PURCHASED, VIEWED, RECOMMENDED سوالی مثل «محصولاتی را که دوستانِ کاربر من (user) خریده‌اند اما خودش نخریده را پیدا کن» با کوئری Cypher (زبان Query گراف Neo4j) بسیار سریع و طبیعی انجام می‌شود:
MATCH (u:User {id: $userId})-[:FRIEND]->(f:User)-[:PURCHASED]->(p:Product)
WHERE NOT (u)-[:PURCHASED]->(p)
RETURN DISTINCT p LIMIT 10
در دیتابیس‌های رابطه‌ای، این کوئری نیازمند زیر-کوئری‌های تو در تو و چندین JOIN سنگین است. اما در Neo4j، پیمایش از کاربر به دوستان و سپس محصولات در یک traversal ساده خلاصه می‌شود. از منظر .NET، می‌توانید با استفاده از Neo4jClient و فریم‌ورک‌هایی مانند HotChocolate (GraphQL for .NET) ساختار query و mutation بر گراف را به اپلیکیشن خود بیاورید:
var client = new BoltGraphClient("neo4j://localhost:7687", "neo4j", "password");
await client.ConnectAsync();

var query = client.Cypher
    .Match("(u:User {id: $userId})-[:FRIEND]->(f:User)-[:PURCHASED]->(p:Product)")
    .Where("NOT (u)-[:PURCHASED]->(p)")
    .WithParam("userId", userId)
    .ReturnDistinct(p => p.As<Product>())
    .Limit(10);

var recommendedProducts = (await query.ResultsAsync).ToList();
پس هرجا که داده‌هایتان یک شبکه است و پرس‌وجوها حول کشف ارتباطات ژرف بین موجودیت‌ها می‌چرخد—مثلاً شبکه‌های اجتماعی، گراف دانش، تحلیل توصیه‌گر—گراف دیتابیس را امتحان کنید؛ شاید دیگر نگویید «دیتابیس‌مان کند شده!» 🚀 @DeveloperAdvocate 🥑

💡کدبو (Code Smell): پراکندگی مسئولیت (Shotgun Surgery) پراکندگی مسئولیت زمانی رخ می‌دهد که یک تغییر کوچک در نیازمندی‌ها مستلزم انجام تغییرات در چندین کلاس یا ماژول باشد. این اتفاق عمدتاً در کدهای پیش می‌آید که رعایت کامل اصل تک‌مسئولیتی (Single Responsibility Principle) نشده است. 🔍 مثال: فرض کنید یک سیستم فروش آنلاین داریم که تعیین تخفیف فروش به روش‌های مختلفی در چندین نقطه از سیستم پراکنده شده است.
public class Order {
    public decimal GetTotalPrice() {
        decimal discount = 0;

        if (isBlackFriday) {
            discount = 0.2m;
        }
        return CalculatePrice(discount);
    }
}

public class ShoppingCart {
    public decimal GetTotalPrice() {
        decimal discount = 0;

        if (isBlackFriday) {
            discount = 0.2m;
        }
        return CalculatePrice(discount);
    }
}
در اینجا، برای اعمال تخفیف جمع قیمت در روز بلک فرایدی، لازم است تغییرات در چندین کلاس اعمال شود. 🔧 Refactoring: اعمال الگوی استراتژی (Strategy Pattern) با استفاده از الگوی استراتژی، می‌توانیم مسئولیت تعیین تخفیف را به یک کلاس مجزا انتقال دهیم تا تغییرات به صورت ایزوله و مستقل باشد:
public interface IDiscountStrategy {
    decimal GetDiscount();
}

public class BlackFridayDiscount : IDiscountStrategy {
    public decimal GetDiscount() {
        return 0.2m;
    }
}

public class Order {
    private readonly IDiscountStrategy _discountStrategy;
    
    public Order(IDiscountStrategy discountStrategy) {
        _discountStrategy = discountStrategy;
    }

    public decimal GetTotalPrice() {
        return CalculatePrice(_discountStrategy.GetDiscount());
    }
}

public class ShoppingCart {
    private readonly IDiscountStrategy _discountStrategy;

    public ShoppingCart(IDiscountStrategy discountStrategy) {
        _discountStrategy = discountStrategy;
    }

    public decimal GetTotalPrice() {
        return CalculatePrice(_discountStrategy.GetDiscount());
    }
}
این تغییرات باعث می‌شوند که در زمان تغییر در نحوه محاسبه‌ی تخفیف، تنها نیاز به تغییر در کلاس استراتژی مربوطه باشد و پراکندگی مسئولیت حذف شود. با این رویکرد، انعطاف‌پذیری کد را افزایش داده و کارایی تیم در واکنش به تغییرات نیازمندی‌ها بهبود می‌یابد. 🌟 @DeveloperAdvocate 🥑

راه‌اندازی و مشاهده ردیابی توزیع‌شده با OpenTelemetry برای فراخوانی چند سرویس همانطور که می‌دانید، در معماری میکروسرویس، امکان مشاهده و ردیابی نحوه عملکرد درخواست‌ها از طریق تمام سرویس‌ها بسیار حیاتی است. OpenTelemetry به عنوان یکی از پرکاربردترین ابزارها در این زمینه به شما کمک می‌کند تا این نیاز را به صورت یکپارچه برطرف کنید. در اینجا نحوه راه‌اندازی OpenTelemetry برای یک سناریوی چندسرویسی را شرح می‌دهیم. ### ۱. نصب پکیج‌های ضروری ابتدا مطمئن شوید که پکیج‌های موردنیاز برای OpenTelemetry را به پروژه‌های C# خود اضافه کرده‌اید:
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Exporter.Console
### ۲. تنظیم OpenTelemetry پیکربندی سرویس‌ها برای استفاده از OpenTelemetry در Startup.cs یا Program.cs بسته به نسخه‌های جدید .NET به این شکل انجام می‌گیرد:
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetryTracing((tracingBuilder) => 
{
    tracingBuilder
        .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyCoolService"))
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddConsoleExporter(); // برای محیط‌های تولیدی بهتر است از Jaeger یا Zipkin استفاده کنید
});

var app = builder.Build();

// بقیه تنظیمات برنامه
### ۳. استفاده از Tracing در کد به منظور مشاهده کامل‌تر درخواست‌ها، می‌توانید به صورت کامل‌تر از ردیابی دستی استفاده کنید. به عنوان مثال:
using OpenTelemetry.Trace;

public class MyService
{
    private readonly Tracer _tracer;

    public MyService(TracerProvider tracerProvider)
    {
        _tracer = tracerProvider.GetTracer("MyServiceTracer");
    }

    public void ProcessRequest()
    {
        using (var scope = _tracer.StartActiveSpan("ProcessRequest", out var span))
        {
            try
            {
                // منطق کسب و کار
                span.SetAttribute("custom.attribute", "value");
            }
            catch (Exception ex)
            {
                span.RecordException(ex);
                throw;
            }
        }
    }
}
### ۴. مشاهده و تحلیل حالا اجرایی کنید و خروجی ردیابی را در کنسول یا استفاده از ابزارهای دیگری مانند Jaeger و Zipkin مشاهده کنید. این ابزارها ردیابی کامل‌تری ارائه می‌دهند که شامل مشاهده دقیق نحوه جریان درخواست در بین سرویس‌های مختلف است. OpenTelemetry یک ابزار قدرتمند برای مدیریت پیچیدگی‌های ردیابی توزیع‌شده است که به شما کمک می‌کند تا نواقص سیستم را راحت‌تر شناسایی و حل کنید. این قابلیت بهبود دهنده تجربه کاربری است و می‌تواند تأثیر مثبتی بر روی عملکرد سیستم شما داشته باشد. @DeveloperAdvocate 🥑

🌟 ساخت یک سرویس GraphQL با استفاده از Hot Chocolate در .NET در این پست، قصد داریم به طور مختصر نحوه‌ی ایجاد یک نقطه پایانی ساده GraphQL با استفاده از کتابخانه‌ی پرطرفدار Hot Chocolate در .NET را بررسی کنیم. Hot Chocolate با طراحی انعطاف‌پذیر و رابط کاربری قوی، ابزار مناسبی برای پیاده‌سازی GraphQL محسوب می‌شود. ### شروع کار با Hot Chocolate 1. نصب پکیج‌ها: پروژه‌ی جدیدی با استفاده از dotnet CLI ایجاد کنید و سپس پکیج HotChocolate.AspNetCore را به آن اضافه کنید:
   dotnet new web
   dotnet add package HotChocolate.AspNetCore
   
2. تعریف مدل‌ها و طرح‌های GraphQL: فرض کنید مدل ساده‌ای به نام Book داریم و می‌خواهیم از آن query‌ای تعریف کنیم:
   public class Book
   {
       public int Id { get; set; }
       public string Title { get; set; }
       public string Author { get; set; }
   }
   
حالا بیایید یک کلاس Query تعریف کنیم که لیستی از کتاب‌ها را برمی‌گرداند:
   public class Query
   {
       public IEnumerable<Book> GetBooks() => new List<Book>
       {
           new Book { Id = 1, Title = "1984", Author = "George Orwell" },
           new Book { Id = 2, Title = "To Kill a Mockingbird", Author = "Harper Lee" },
       };
   }
   
3. پیکربندی سرویس: حال زمان آن رسیده که این Query را در پایپ‌لاین درخواست‌های HTTP ثبت کنیم:
   public void ConfigureServices(IServiceCollection services)
   {
       services
           .AddGraphQLServer()
           .AddQueryType<Query>();
   }

   public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
   {
       if (env.IsDevelopment())
       {
           app.UseDeveloperExceptionPage();
       }

       app.UseRouting();

       app.UseEndpoints(endpoints =>
       {
           endpoints.MapGraphQL();
       });
   }
   
4. اجرای برنامه: برنامه را اجرا کرده و از رابط کاربری GraphQL Playground در مسیر /graphql برای تست نقطه پایانی استفاده کنید:
   {
     books {
       id
       title
       author
     }
   }
   
🌐 جهت استفاده‌ی عملی: این پیاده‌سازی ابتدایی به شما امکان می‌دهد تا به سرعت درک بهتری از کار با GraphQL در .NET بیابید. برای پروژه‌های بزرگ‌تر، توجه به ساختارهای پیچیده‌تری مانند fragmentها و directiveها بسیار اهمیت دارد. Hot Chocolate قابلیت‌های پیشرفته‌تری نیز ارائه می‌دهد که در آینده به آن‌ها می‌پردازیم. برنامه‌نویسی خوش! 🚀 @DeveloperAdvocate 🥑

در دنیای امروزی برنامه‌نویسی با C#، مدیریت منابع غیرمدیریت‌شده یکی از چالش‌های مهم است. در .NET 5 به بعد، ما می‌توانیم از IAsyncDisposable برای انجام این کار به صورت ناهمزمان و کارا استفاده کنیم. بیایید ببینیم چگونه می‌توان یک کلاس را که منابع غیرمدیریت‌شده را به‌طور ناهمزمان مدیریت می‌کند، پیاده‌سازی کرد. تصور کنید که کلاس ما مدیریتی است برای شبکه‌ای از سوکت‌ها که باید به صورت پاکسازی شوند. در این مثال، ما از IAsyncDisposable و مترهای ناهمزمان برای تخلیه منابع استفاده می‌کنیم:
using System;
using System.Net.Sockets;
using System.Threading.Tasks;

public class NetworkResourceManager : IAsyncDisposable
{
    private readonly Socket _socket;
    private bool _disposed = false;

    public NetworkResourceManager(string host, int port)
    {
        _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _socket.Connect(host, port);
    }

    public async ValueTask SendDataAsync(byte[] data)
    {
        if (_disposed)
            throw new ObjectDisposedException(nameof(NetworkResourceManager));

        await _socket.SendAsync(data, SocketFlags.None);
    }

    public async ValueTask ReceiveDataAsync(byte[] buffer)
    {
        if (_disposed)
            throw new ObjectDisposedException(nameof(NetworkResourceManager));

        await _socket.ReceiveAsync(buffer, SocketFlags.None);
    }

    public async ValueTask DisposeAsync()
    {
        if (!_disposed)
        {
            // فرض کنید تعداد فلش های پاکسازی
            await _socket.ShutdownAsync(SocketShutdown.Both);
            _socket.Close();
            _socket.Dispose();

            // تنظیم علامت
            _disposed = true;
        }
    }
}
نکات کلیدی: - با پیاده‌سازی IAsyncDisposable، ما متد DisposeAsync را ایجاد کرده‌ایم که عمل پاکسازی حقیقتی را به صورت ناهمزمان انجام می‌دهد. - توجه داشته باشید که استفاده از متدهای ناهمزمان مثل SendAsync و ReceiveAsync کد ما را آماده برای عملیات‌های ورودی/خروجی شبکه می‌کند، و بهبود کارایی را از طریق آزادسازی منابع با استفاده از الگوهای ناهمزمان تضمین می‌کند. - مطمئن شوید که روش‌های عملیاتی همچون SendDataAsync همیشه وضعیت disposing را بررسی می‌کنند تا از استثنائات ناخواسته جلوگیری شود. این یک راهکار ایده‌آل برای سناریوهایی است که به افزایش کارایی در محیط‌های شبکه‌ای نیاز داریم. امیدوارم استفاده از این تکنیک‌ها به بهبود نرم‌افزارهای شما کمک کند. موفق باشید! @DeveloperAdvocate 🥑

کلین آرکیتکچر (Clean Architecture) یکی از الگوهای معماری نرم‌افزار است که تمرکز ویژه‌ای بر جداسازی و استقلال لایه‌های مختلف اپلیکیشن دارد. هدف از این الگو ایجاد سیستمی است که به راحتی قابل تست، نگهداری و توسعه باشد. در دنیای مدرن .NET، کلین آرکیتکچر با فراهم کردن فریم‌ورکی که به وضوح نقش‌ها و مسئولیت‌های هر بخش از سیستم را تعیین می‌کند، توانایی بالایی در مواجهه با تغییرات پیچیده و افزایش مقیاس دارد. ### اصول اساسی کلین آرکیتکچر: 1. جداسازی لایه‌ها: کد اپلیکیشن باید به وضوح به لایه‌های مختلفی مانند Presentation، Application، Domain و Infrastructure تقسیم شود. 2. استقلال از فریم‌ورک‌ها: هر لایه باید طوری طراحی شود که وابستگی مستقیمی به یک فریم‌ورک خاص نداشته باشد. 3. استقلال از UI: تغییرات در لایه رابط کاربری نباید بر سایر بخش‌ها تاثیری بگذارد. 4. استقلال از پایگاه داده: سیستم نباید وابسته به یک نوع خاص از پایگاه داده باشد. 5. قابلیت تست‌پذیری: باید امکان تست واحد برای هر بخش از اپلیکیشن به صورت مجزا وجود داشته باشد. ### پیاده‌سازی در .NET: در یک پروژه .NET، پیاده‌سازی Clean Architecture به این گونه انجام می‌شود که معمولا یک ساختار پروژه با چندین لایه ایجاد می‌گردد. مثال زیر یک مدل ساده برای انجام این پیاده‌سازی است: 1. Core.Domain: شامل قوانین تجاری و موجودیت‌ها. 2. Core.Application: حاوی operationهای اپلیکیشن و قراردادهای استفاده شده توسط این اپلیکیشن. 3. Infrastructure: دربردارنده پیاده‌سازی تعاملات با پایگاه داده و دیگر سرویس‌های خارجی. 4. WebAPI: نقطه ورود اپلیکیشن و ارائه داده‌ها به کاربران و سرویس‌ها. در زیر مثالی از تعریف یک انترفیس در لایه Application و یک پیاده‌سازی از آن در لایه Infrastructure آورده شده است:
// Core.Application
public interface IProductRepository
{
    Product GetProductById(int id);
    void AddProduct(Product product);
}

// Infrastructure
public class ProductRepository : IProductRepository
{
    private readonly DbContext _context;

    public ProductRepository(DbContext context)
    {
        _context = context;
    }

    public Product GetProductById(int id)
    {
        return _context.Products.Find(id);
    }

    public void AddProduct(Product product)
    {
        _context.Products.Add(product);
        _context.SaveChanges();
    }
}
این ساختار موجب می‌گردد تا وابستگی به جزییات و پیاده‌سازی‌ها به حداقل برسد و لایه‌های مختلف بتوانند به طور مجزا تکامل یابند، و باعث تسهیل در امر توسعه و نگهداری پروژه می‌شود. @DeveloperAdvocate 🥑

# شاردینگ پایگاه داده - رویکرد مبتنی بر هش در هنگام کار با مجموعه داده‌های بزرگ، شاردینگ پایگاه داده یکی از استراتژی‌های ضروری برای اطمینان از بهبود عملکرد و مقیاس‌پذیری است. یکی از رویکردهای محبوب که بویژه برای توزیع یکنواخت داده‌ها مؤثر است، شاردینگ مبتنی بر هش است. بیایید ببینیم این استراتژی چطور عمل می‌کند و چگونه می‌توانیم در دات‌نت آن را پیاده‌سازی کنیم. ## ایده اصلی شاردینگ مبتنی بر هش با اختصاص توکن‌های هش یکتا به هر رکورد و سپس استفاده از این توکن‌ها برای تصمیم‌گیری درباره شاردی که رکورد باید در آن قرار گیرد، عمل می‌کند. این روش تضمین می‌کند که توزیع داده‌ها به شکلی یکنواخت بین شاردها انجام می‌شود، و احتمال به وجود آمدن نقاط ضعف یا سربار در شاردهای خاص کاهش می‌یابد. ## پیاده‌سازی در دات‌نت فرض کنید یک پایگاه داده سفارشات داریم و می‌خواهیم آن را براساس شناسه کاربر شارد کنیم. ابتدا، باید یک تابع هش پیاده‌سازی کنیم که به طور یکنواخت شناسه‌های کاربری را در بین شاردها توزیع کند.
using System.Security.Cryptography;
using System.Text;

public class ShardHelper
{
    public static int GetShardId(string userId, int totalShards)
    {
        using (var sha256 = SHA256.Create())
        {
            byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(userId));
            int hashCode = BitConverter.ToInt32(hash, 0);
            return Math.Abs(hashCode % totalShards);
        }
    }
}
### نحوه استفاده فرض کنیم ما 5 شارد داریم. برای تعیین اینکه داده‌های مربوط به یک کاربر خاص در کدام شارد قرار گیرد، می‌توانیم از تابع GetShardId استفاده کنیم:
string userId = "user123";
int shardId = ShardHelper.GetShardId(userId, 5);
Console.WriteLine($"User data should be stored in shard: {shardId}");
## نکات کلیدی - یافتن تعادل: انتخاب تعداد مناسب شاردها (مثلًا مطابق با ظرفیت سخت‌افزاری) از اهمیت ویژه‌ای برخوردار است. - مراقبت از هزینه محاسباتی: تابع هش نباید بار محاسباتی بزرگی را روی سیستم تحمیل کند. - پیش‌بینی رشد: از پیش برای افزایش تعداد شاردها در آینده برنامه‌ریزی کنید. با شاردینگ مبتنی بر هش، می‌توانید یک سیستم مقیاس‌پذیر و مؤثر داشته باشید که به خوبی با مجموعه داده‌های بزرگ و رشدیابنده کنار می‌آید. @DeveloperAdvocate 🥑

مقایسه‌ی عمیق و تخصصی میان دو جریان OAuth 2.0: 'تبادل کد مجوز' و 'اطلاعات اعتبارنامه کلاینت' برای پروژه‌های سطح بالا و چالش‌برانگیز .NET درک صحیح از جریان‌های مختلف OAuth 2.0 می‌تواند کمک شایانی به پیاده‌سازی‌های امن و بهینه در نرم‌افزارهای امروزی کند. در اینجا به بررسی دقیق دو جریان پرکاربرد و تفاوت‌های کلیدی آنها خواهیم پرداخت. جریان "تبادل کد مجوز" (Authorization Code Flow): جریان تبادل کد مجوز به طور معمول برای برنامه‌های سمت سرور که کنترل امن بر روی اطلاعات کاربر مد نظر است، استفاده می‌شود. مراحل این جریان به شرح زیر است: 1. درخواست مجوز: کلاینت کاربر را به سرور احراز هویت هدایت می‌کند. 2. کد مجوز: پس از تأیید کاربر، سرور کدی موقت برای کلاینت ارسال می‌کند. 3. دریافت دسترسی: کلاینت کد مجوز را به همراه اطلاعات امنیتی خود به سرور API ارسال می‌کند تا توکن دسترسی را دریافت کند. مزایای آن شامل حفاظت بهتر از اعتبار‌نامه کاربر و منع مستقیم از افشای توکن‌ها در سمت کلاینت است. این جریان از پروتکل‌های بازگشتی OAuth توصیه شده و برای کاربردهایی با امنیت حساس مناسب‌تر است. جریان "اطلاعات اعتبارنامه کلاینت" (Client Credentials Flow): این جریان بدون دخالت کاربر، به کلاینت اجازه می‌دهد که یک توکن دریافت کند و برای سناریوهایی که دسترسی مرتبط با کاربر وجود ندارد، مناسب است. به‌خصوص در سرویس‌های میکروسرویس که می‌خواهند بدون دخالت کاربر داخلی با یکدیگر تعامل کنند. ساختار آن به این شکل است: 1. درخواست توکن: کلاینت اطلاعات امنیتی خودش را مستقیماً برای سرور API ارسال می‌کند. 2. صدور توکن: سرور API پس از تأیید اطلاعات، یک توکن دسترسی برای کلاینت صادر می‌کند. این جریان به دلیل عدم نیاز به تعامل کاربر و ساده‌سازی فرآیند احراز هویت، در خدمات سرورتوسرور (S2S) و امثال آنها جذابیت زیادی دارد. با این حال، نیازمند مدیریت بسیار دقیق و امن اطلاعات امنیتی در سمت کلاینت است. انتخاب بین این دو باید بر اساس نوع کاربرد و نیاز به امنیت داده و حفاظت از اطلاعات کاربر باشد. در اپلیکیشن‌هایی با نیاز به تعامل کاربر، "تبادل کد مجوز" گزینه بسیار قدرتمندتری است، در حالی‌که برای تعاملات بین سرویس‌ها بدون ارتباط مستقیم با کاربر، "اطلاعات اعتبارنامه کلاینت" می‌تواند مناسب‌تر باشد. توجه داشته باشید که در هر دو سناریو، امنیت سمت کلاینت و سمت سرور باید به‌دقت برقرار شود تا از هرگونه نقض امنیتی جلوگیری شود. پیاده‌سازی این جریان‌ها در .NET Core می‌تواند به سادگی با استفاده از کتابخانه‌هایی مانند IdentityServer انجام شود که به طور کامل از OAuth 2.0 پشتیبانی می‌کنند. در مثال زیر چگونگی تنظیم جریان "تبادل کد مجوز" آورده شده است:
services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
    options.Authority = "https://your-identityserver-issuer.com";
    options.ClientId = "your-client-id";
    options.ClientSecret = "your-client-secret";
    options.ResponseType = "code";
    options.SaveTokens = true;
    options.Scope.Add("api1.read");
});
با این تنظیم، ارتباط امن و مؤثری بین کلاینت و سرور خود داشته باشید و از توانایی‌های بی‌نظیر OAuth در برنامه‌های خود بهره‌مند شوید. @DeveloperAdvocate 🥑

🔥 در دنیای توسعه مدرن با #Csharp، استفاده از الگوی "Factory Pattern" در ساخت اشیاء مزایای قابل توجهی دارد. این الگو به کاهش وابستگی‌های سخت کد به کلاس‌های خاص کمک می‌کند و تغییرات آینده را ساده‌تر می‌سازد. برای توضیح بیشتر، نگاهی به مثال زیر بیندازید که نمونه‌ی ساده‌ای از پیاده‌سازی این الگو است:
interface ILogger
{
    void Log(string message);
}

class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine("Console Logger: " + message);
    }
}

class FileLogger : ILogger
{
    public void Log(string message)
    {
        // Log to a file (شبیه‌سازی شده)
        Console.WriteLine("File Logger: " + message);
    }
}

class LoggerFactory
{
    public static ILogger CreateLogger(string type)
    {
        return type switch
        {
            "Console" => new ConsoleLogger(),
            "File" => new FileLogger(),
            _ => throw new ArgumentException("Invalid logger type"),
        };
    }
}

// استفاده از Factory برای ساخت Logger
ILogger logger = LoggerFactory.CreateLogger("Console");
logger.Log("Starting application...");
با استفاده از این ساختار، شما می‌توانید انواع مختلفی از Loggerها را بدون تغییر مستقیم در کد اصلی خود ایجاد کنید. این انعطاف‌پذیری به شما اجازه می‌دهد تا در شرایط مختلف به سرعت انطباق پیدا کنید و تغییرات را به طور موثر مدیریت کنید. مهم است که در معماری نرم‌افزار خود به استفاده از چنین الگوهایی توجه کنید تا ساختاری پایدار و قابل نگهداری داشته باشید. 🚀 @DeveloperAdvocate 🥑

در دنیای توسعه نرم‌افزار، انتخاب ابزار مناسب برای جستجوی متن کامل یکی از تصمیمات مهم است که باید به دقت مد نظر قرار گیرد. دو گزینه محبوب و پرکاربرد در این حوزه، توانایی‌های جستجوی متن کامل PostgreSQL و موتور اختصاصی Elasticsearch هستند. PostgreSQL: PostgreSQL با ارائه امکانات جستجوی متن کامل، فرصتی را فراهم می‌کند تا بدون نیاز به موتورهای خارجی، قابلیت جستجوی بالغی به برنامه‌های خود اضافه کنید. قابلیت‌های آن شامل: - تطابق عبارات: استفاده از عملگرهای متنی مانند @@ برای جستجوی سریع و بهینه. - دسته‌بندی و اولویت‌بندی بهینه: امکان دسته‌بندی نتایج با استفاده از توابع رتبه‌بندی و دستورات سفارشی. - یکپارچگی با دیتابیس: اجازه می‌دهد که جستجو به صورت یکپارچه با داده‌های رابطه‌ای انجام شود. نمونه کد ساده برای جستجوی متن کامل در PostgreSQL:
SELECT * FROM documents WHERE content @@ to_tsquery('C# & .NET');
Elasticsearch: از سوی دیگر، Elasticsearch موتور جستجوی توزیع‌شده و مقیاس‌پذیری است که عمدتاً برای حل مشکلات جستجوی پیچیده‌تر طراحی شده است: - سرعت و مقیاس‌پذیری بالا: به دلیل معماری توزیع‌شده، توانایی پردازش حجم بالایی از داده‌ها و جستجوهای بلادرنگ را دارد. - ویژگی‌های پیشرفته تحلیل: فیلترهای غنی و پشتیبانی از جستجوهای تحلیلی. - پشتیبانی از انواع داده: مناسب برای ذخیره‌سازی و جستجو در داده‌های نیمه‌ساختیافته و لاگ‌ها. نمونه کدی برای جستجو در Elasticsearch:
GET /documents/_search
{
  "query": {
    "match": {
      "content": "C# .NET"
    }
  }
}
ملاحظات نهایی: انتخاب بین PostgreSQL و Elasticsearch بستگی به نیازهای پروژه شما دارد. اگر تنها نیاز به جستجوی ساده در سیستم داخلی دارید، PostgreSQL می‌تواند کافی باشد. اما برای جستجوی پیچیده و نیاز به مقیاس‌پذیری بالا، Elasticsearch انتخاب مناسبتری است. به خاطر داشته باشید که Elasticsearch نیازمند توجه ویژه به مباحثی مانند شاخص‌سازی، پیکربندی کلاسترها و مدیریت منابع است. @DeveloperAdvocate 🥑

Isolation Levels در سیستم‌های مدیریت پایگاه داده (DBMS)، به ما کنترل‌هایی بر روی نحوه دسترسی همزمان به داده‌ها ارائه می‌دهند تا از بروز مشکلاتی مانند Dirty Read، Non-Repeatable Read و Phantom Read جلوگیری کنیم. دو سطح از این موارد یعنی Repeatable Read و Serializable را با یک سناریوی عملی بررسی می‌کنیم: فرض کنید ما یک پایگاه داده بانکی داریم و دو تراکنش همزمان در حال اجرا هستند: 1. Repeatable Read: این سطح، قابلیت تکرار دقیق خوانش‌های قبلی در طول یک تراکنش را فراهم می‌آورد. به طور مثال، یک تراکنش می‌تواند یک حساب بانکی را چک کند و مطمئن باشد که تا پایان تراکنش، هیچ کس نمی‌تواند این رکورد را تغییر دهد یا حذف کند. در این حالت، Non-Repeatable Read و Dirty Read وجود نخواهد داشت، ولی ممکن است پاسخگویی به Phantom Read نباشیم. در این سناریو، اگر طی تراکنش A، تعداد کلمات موجود در یک کتگوری محصولات چک شود (مثلاً موجودی محصولات در یک فروشگاه)، تا پایان تراکنش A تعداد محصولات ثابت خواهد ماند، حتی اگر تراکنش B محصول جدیدی اضافه کند. 2. Serializable: این بالاترین سطح جداسازی است و اطمینان می‌دهد که تراکنش‌ها به گونه‌ای اجرا می‌شوند که گویی به صورت سریالی و نه همزمان اجرا شده‌اند. این سطح همزمانی را کاهش داده و تضمین می‌کند که هیچ کدام از مشکلات خواندن رخ نمی‌دهد، حتی Phantom Read. در مثال فوق، استفاده از Serializable تضمین می‌کند که هیچ محصول جدیدی توسط تراکنش B نمی‌تواند اضافه شود تا تراکنش A پایان یابد. برای درک عملی این سطوح، می‌توانید نگاه کنید به مثال زیر در یک پایگاه داده با EF Core در .NET:
using (var context = new MyDbContext())
{
    var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.RepeatableRead);

    try
    {
        var products = context.Products.ToList();
        
        // عملیات تراکنش
        transaction.Commit();
    }
    catch 
    {
        transaction.Rollback();
    }
}
توجه داشته باشید که استفاده از Serializable می‌تواند باعث کاهش کارآیی به دلیل قفل‌های بیشتر شود، مخصوصاً در سیستم‌هایی با ترافیک بالا. هوشمندانه انتخاب کنید تا تعادلی بین صحت داده‌ها و کارایی سیستم برقرار نمایید. @DeveloperAdvocate 🥑

🔹 استفاده از Materialized Viewها در PostgreSQL برای بهبود عملکرد امروزه یکی از چالش‌های رایج در زمینه پایگاه‌های داده، بهینه‌سازی پرس‌وجو‌ها برای دستیابی به عملکرد بهتر است. در PostgreSQL، Materialized View‌ها ابزاری قدرتمند برای ذخیره‌سازی نتایج پرس‌وجو‌های پیچیده‌ای هستند که به طور مکرر اجرا می‌شوند. برخلاف View‌های معمولی که به صورت دینامیک و در زمان اجرا تشکیل می‌شوند، Materialized View‌ها نتایج پرس‌وجو را در دیسک ذخیره می‌کنند و بنابراین به پرس‌وجوهای تکراری کمک شایانی می‌کنند. زمان استفاده از Materialized View: 1. زمانی که عملکرد پرس‌وجوها اهمیت فوق‌العاده‌ای دارد و کاهش زمان اجرای پرس‌وجوهای سنگین مزیت بزرگی محسوب می‌شود. 2. هنگامی که داده‌های زیربنایی تغییرات مکرر ندارند، یا قابلیت تحمل داده‌های کمی قدیمی وجود دارد. 3. وقتی که تجمع و خوشه‌بندی داده‌ها ضروری است و در زمان اجرا فشار زیادی به سیستم وارد می‌کند. نحوه استفاده از Materialized View: برای ایجاد یک Materialized View، از دستور زیر استفاده می‌کنیم:
CREATE MATERIALIZED VIEW my_materialized_view AS
SELECT column1, SUM(column2) 
FROM my_table
GROUP BY column1;
برای به‌روزرسانی Materialized View و تازه‌سازی داده‌های آن، از دستور REFRESH استفاده می‌شود:
REFRESH MATERIALIZED VIEW my_materialized_view;
⚠️ نکته مهم: هنگام استفاده از Materialized View به توجه داشته باشید که نیاز به مدیریت زمان‌بندی و تعداد رفرش‌ها برای به‌روزرسانی داده‌های ذخیره‌شده خواهید داشت. در نهایت، Materialized View می‌تواند اجرای پرس‌وجوها را به طرز چشمگیری بهبود بخشد؛ به شرطی که شرایط استفاده به درستی ارزیابی و مدیریت شوند. آیا شما هم از این قابلیت برای بهینه‌سازی سیستم‌های پایگاه داده خود استفاده کرده‌اید؟ نظرات خود را به اشتراک بگذارید. 🌟 @DeveloperAdvocate 🥑

پیکربندی دیپلوی کاناری با استفاده از سرویس مش‌هایی مانند Istio یا Linkerd می‌تواند رویکرد بسیار موثری برای انتشار نرم‌افزار باشد. این تکنیک به شما اجازه می‌دهد تغییرات را به صورت تدریجی و کنترل‌شده اعمال کنید. در ادامه نحوه پیکربندی دیپلوی کاناری با Istio را توضیح می‌دهیم: 1. تنظیم نسخه‌های سرویس: ابتدا باید نسخه‌های متفاوتی از سرویس‌های خود داشته باشید. مثلاً v1 و v2. 2. تعریف Gateway و VirtualService: ابتدا Gateway و VirtualService مورد نیاز را تعریف می‌کنیم. این کار به ما امکان پذیرفتن ترافیک خارجی و هدایت آن به سرویس‌های داخلی را می‌دهد.
   apiVersion: networking.istio.io/v1alpha3
   kind: Gateway
   metadata:
     name: my-gateway
   spec:
     selector:
       istio: ingressgateway
     servers:
     - port:
         number: 80
         name: http
         protocol: HTTP
       hosts:
       - "*.example.com"
   ---
   apiVersion: networking.istio.io/v1alpha3
   kind: VirtualService
   metadata:
     name: my-service
   spec:
     hosts:
     - "my-service.example.com"
     gateways:
     - my-gateway
     http:
     - route:
       - destination:
           host: my-service
           subset: v1
         weight: 90
       - destination:
           host: my-service
           subset: v2
         weight: 10
   
3. تعریف DestinationRule: انجام این مرحله به ما امکان کنترل دقیق نسخه‌های مختلف را می‌دهد.
   apiVersion: networking.istio.io/v1alpha3
   kind: DestinationRule
   metadata:
     name: my-service
   spec:
     host: my-service
     subsets:
     - name: v1
       labels:
         version: v1
     - name: v2
       labels:
         version: v2
   
با انجام این موارد، ترافیک به سمت my-service تقسیم خواهد شد: ۹۰ درصد ترافیک به v1 و ۱۰ درصد به v2 هدایت می‌شود. به مرور زمان و با افزایش اطمینان از عملکرد نسخه‌ی جدید، می‌توانید درصد ترافیک را برای v2 افزایش دهید تا هنگامی که به اطمینان کامل برسید و تمام ترافیک را به نسخه‌ی جدید هدایت کنید. این روند به شما امکان می‌دهد ریسک‌های مربوط به انتشار ویژگی‌های جدید را به حداقل برسانید. @DeveloperAdvocate 🥑

در دنیای معماری میکروسرویس‌ها، اطمینان از ارسال مطمئن پیام‌ها بین سرویس‌ها یک چالش مهم است. پترن Outbox به عنوان یک راهکار موثر برای مدیریت این چالش مطرح می‌شود. هدف این پترن، تضمین تحویل پیام‌ها حتی در صورت وقوع رخدادهای ناگوار مانند شکست متعهد (commit) شدن تراکنش‌ها است. ### اصول پترن Outbox پترن Outbox در تراکنش بانک اطلاعاتی شما جای می‌گیرد و پیام‌ها را در یک «جدول خارج از صف» ویژه ذخیره می‌کند که به عنوان یک خروجی موقت عمل می‌کند. فرآیند ارسال پیام به دو بخش تقسیم می‌شود: ذخیره‌سازی و انتشار. 1. ذخیره‌سازی: زمانی که تغییر حالت (state) سیستم رخ می‌دهد که نیاز به ارسال پیام دارد، پیام در جدول Outbox ذخیره می‌شود. این عملیات در همان تراکنش بانک اطلاعاتی اصلی انجام می‌شود، برای اینکه از سازگاری (consistency) داده‌ها اطمینان حاصل شود:
   using (var transaction = dbContext.Database.BeginTransaction())
   {
       // اعمال تغییرات در دامین
       dbContext.Update(entity);
       
       // ذخیره پیام در جدول Outbox
       var outboxMessage = new OutboxMessage
       {
           Id = Guid.NewGuid(),
           Data = SerializeMessage(eventMessage),
           CreatedAt = DateTime.UtcNow
       };
       dbContext.OutboxMessages.Add(outboxMessage);
       
       dbContext.SaveChanges();
       transaction.Commit();
   }
   
2. انتشار (Publishing): بخشی جداگانه به نام Outbox Processor مسئول خواندن پیام‌های ثبت شده در جدول Outbox و ارسال آنها به سیستم پیام‌رسان (مثل Kafka یا RabbitMQ) است. این پردازش به صورت متناوب یا بر اساس رویداد انجام می‌شود.
   public async Task ProcessOutboxMessagesAsync()
   {
       var messages = dbContext.OutboxMessages
           .Where(m => !m.IsProcessed)
           .ToList();

       foreach (var message in messages)
       {
           // ارسال پیام به سیستم پیام‌رسان
           await messageBroker.PublishAsync(DeserializeMessage(message.Data));
           
           // علامت‌گذاری پیام به عنوان ارسال شده
           message.IsProcessed = true;
           dbContext.SaveChanges();
       }
   }
   
### مزایا - تضمین سازگاری: پیام‌ها همراه با تراکنش اصلی ثبت می‌شوند، در نتیجه تضمین سازگاری داده‌ها فراهم می‌شود. - مقیاس‌پذیری: پردازش Outbox می‌تواند به صورت افقی مقیاس‌پذیر شود. - احیای آسان: در صورت بروز خطا، پیام‌های پردازش نشده می‌توانند بدون تاثیر بر سیستم اصلی دوباره پردازش شوند. پترن Outbox یک راهکار ساده و قدرتمند برای اطمینان از ارسال مطمئن پیام‌ها در محیط‌های میکروسرویس است که به معماری شما ثبات و انطباق بیشتری می‌بخشد. @DeveloperAdvocate 🥑