Developer Advocate
Ir al canal en Telegram
1 035
Suscriptores
Sin datos24 horas
Sin datos7 días
-130 días
Archivo de publicaciones
1 035
سه راهکار عملی برای منتورینگ مؤثر دولوپرهای جونیور:
۱. بازطراحی کد (Refactoring) بهصورت Pair Programming
یک جلسه pair programming حول بازآرایی کد با جونیور ترتیب دهید. بدون آنکه مستقیماً کد را بنویسید، با پرسشهای هدفمند او را به شناسایی smellها و refactorها هدایت کنید. مثال، کدی با توابع تو در تو و شرطهای پیچیده را به یک ساختار خواناتر و تابعیتر تبدیل کنید:
// قبل از refactor
public bool IsUserActive(User user)
{
if (user != null)
{
if (user.LastLogin != null)
{
if ((DateTime.Now - user.LastLogin.Value).Days < 30)
{
return true;
}
}
}
return false;
}
// بعد از refactor
public bool IsUserActive(User user) =>
user?.LastLogin is DateTime lastLogin &&
(DateTime.Now - lastLogin).Days < 30;
فرایند دگرگونی را توضیح دهید و از جونیور بخواهید گامهای refactor را خودش انجام دهد.
۲. معرفی الگوهای معماری در عمل
در دیزاین ریویوها، جایگزینی راهحلهای سرراست جونیور با الگوهای معماری استاندارد (مثلاً Dependency Injection یا Repository Pattern) را با ذکر دلایل، مزایا و نقاط ضعف مقایسه کنید. سپس implementation کوچکی به او بسپارید تا خودش آن الگو را تجربه کند.
۳. بازخورد متمرکز بر رشد و تفکر انتقادی
بازخوردها را از سطح خطاهای جزئی بالاتر ببرید؛ روی افق تفکر و تصمیمات طراحی تمرکز کنید. بهجای جمله "این متد خیلی طولانی است"، سؤال محوری مطرح کنید: "چه هزینههایی دارد اگر این متد تستپذیر نباشد یا قابلیت نگهداری نداشته باشد؟" یا درخواست کنید flow دیپندنسی یا خطایابی هر بخش را به شما توضیح دهد.
🔹 هدف منتورینگ ارایه پاسخ یا راحل سریع نیست؛ بلکه ساختن ذهنیتی عمیق و growth mindset است.
@DeveloperAdvocate 🥑1 035
وقتی بحث Core Web Vitals میشود، معمولاً تشخیص و بهبود LCP (Largest Contentful Paint) یکی از گلوگاههای تجربه کاربری در اپلیکیشنهای Next.js است. LCP معیاری کلیدی است که زمان بارگذاری "بزرگترین محتوای قابل مشاهده" در viewport را اندازه میگیرد؛ معمولاً این محتوا یک تصویر، ویدیو یا بلوک متنی بزرگ است.
### تشخیص LCP در Next.js
1. استفاده از ابزارها
- Chrome DevTools: تب Performance > تجربه بارگذاری را ضبط و ریزبینانه LCP را بررسی کنید.
- Google Lighthouse و WebPageTest.org نیز LCP را با جزئیات دقیق گزارش میکنند.
- برای مشاهده realtime، میتوانید از مرورگر (Performance Insights) یا پلاگین Web Vitals Extension استفاده کنید.
2. تحلیل رندر SSR/CSR:
- در SSR/SSG، بارگذاری SSR میتواند LCP را تحت تأثیر قرار دهد؛ در حالی که در CSR، حجم و ساختار باندل و lazy loading نقش کلیدی دارد.
- با DevTools به دنبال المانی بگردید که به عنوان LCP انتخاب شده و زمان و علت تأخیر را ارزیابی کنید (مثلاً،
<img /> با سایز بزرگ).
### رفع مشکلات معمول LCP در Next.js
1. بهینهسازی تصاویر:
- از کامپوننت <Image> Next.js با priority={true} برای critical images استفاده کنید.
- تصاویر را با سایز و فرمت بهینه (WebP/AVIF) و لود تنبل (Lazy Loading) برای موارد غیر حیاتی تنظیم کنید.
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // این تصویر برای LCP حیاتی است
/>
2. کاهش TTFB (Time To First Byte):
- از getServerSideProps صرفاً در مواقع ضروری استفاده کنید؛ برای محتواهای ثابت ترجیحاً SSG (getStaticProps) را انتخاب کنید.
- بهینهسازی کامپوننتهای Server Component و APIها را با caching و prefetching انجام دهید.
3. لود سریع فونت و استایل:
- برای جلوگیری از CLS و تسریع LCP، از قابلیتهای next/font و font-display: swap بهره ببرید.
- استایلهای Critical را inline کنید یا از CSS Modules/Styled JSX با SSR استفاده کنید.
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'], display: 'swap' })
4. کاهش جاوااسکریپت و اسکریپتهای باندل:
- از dynamic import و lazy loading برای اسکریپتهای غیر حیاتی استفاده کنید.
- سرویسهای third-party و analytics را با strategy="lazyOnload" بارگذاری کنید:
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="lazyOnload"
/>
5. Activation سریع المان LCP:
- اگر LCP شما یک تصویر زیر fold است، تلاش کنید آن را تا حد ممکن نزدیک به top قرار دهید یا ساختار DOM را ساده کنید.
---
💡 تست و مانیتورینگ LCP را با ابزارهای CI و پروفایلینگ مستمر (مانند next analytics یا Real User Monitoring) اتومات کنید تا افت شاخصها را قبل از Production ببینید. کوچکترین تغییرات در SSR/CSR و asset delivery میتواند تأثیرات بزرگی روی LCP و تجربه کاربر داشته باشد.
@DeveloperAdvocate 🥑1 035
🔎 فرآیند پستمورتِم بیطرفانه برای حوادث Production
در تیم DevOps بالغ، پستمورتِم باید ابزاری برای یادگیری جمعی باشد، نه مقصر جلوهدادن. برای رسیدن به این هدف، فرآیند زیر را توصیه میکنم:
۱. جمعآوری دادهها (Gathering Data)
تمام لاگها، نمودارهای مانیتورینگ، alertها، و تایملاین وقایع را پیش از جلسه آماده کنید. اهداف: تصویر جامع، حذف قضاوت شتابزده.
۲. توصیف بیطرفانه حادثه (Objective Incident Narrative)
یک timeline بینام بنویسید: چه زمانی چه رخ داد؟ چه عملی باعث چه نتیجهای شد؟ نه "فلانی اشتباه کرد" بلکه "کد X در ساعت Y اجرا و ...".
۳. تحلیل علت ریشهای (Root Cause Analysis)
از تکنیکهایی مثل "۵ Why’s" و "Cause Mapping" استفاده کنید تا به جای مقصر، زنجیره علتها را واکاوی کنید.
مثال ساده:
// Why #1: چرا سرویس down شد؟ → Connection Pool Exhaustion
// Why #2: چرا Pool Exhaust شد؟ → درخواستهای غیرمنتظره زیاد
// Why #3: چرا درخواستها زیاد؟ → فیلتر جدید فعال شد و ترافیک افزایش یافت
// Why #4: چرا فیلتر درست تست نشده بود؟ → فرآیند CI ما آن سناریو را پوشش نداد...
۴. جمعبندی دانش و یادگیریها (Key Learnings)
چه درسی گرفتیم؟ کد، تست، alert، یا فرآیند کجا میتواند مقاومتر شود؟
۵. تعیین Actionable Items
هر راهحلی باید مالک (Owner)، زمانبندی و معیار موفقیت داشته باشد؛ مثل:
- بهبود تستهای Load برای endpoint جدید → مسئول: Sara تا پنجشنبه
- اضافهکردن alert روی استفاده Pool → مسئول: DevOps Team
۶. انتشار و شفافیت
گزارش پستمورتِم را درونی broadcast کنید. برای پروژههای باز، حتی انتشار عمومی تجربه و یادگیری ارزشمند است.
۷. پیگیری اجرا (Follow-Up)
بعد از x هفته، اجرا شدن اقدامها و اثربخشیشان را بررسی کنید.
⏩ کلید طلایی:
سوال، انتقاد و یادگیری باید روی سیستم و فرآیندها متمرکز باشد، نه افراد. اگر «چرا این اتفاق افتاد» پاسخش فقط «فلانی اشتباه کرد» باشد، یعنی هنوز ریشه را پیدا نکردید.
✍️ این رویکرد، فرهنگ اعتماد و بهبود دائمی را تزریق میکند؛ پایهای برای resiliency در عملیات Production.
@DeveloperAdvocate 🥑1 035
🌐 معماری سیستمها - توضیح عمیق CAP Theorem با مثال عملی CP
فرض کنید در حال طراحی یک سیستم بانکداری آنلاین هستید. طبق نظریه CAP، در صورت پارتیشن شبکه، فقط دو ویژگی از سه تا را میتوانید حفظ کنید: Consistency (یکپارچگی داده)، Availability (دردسترسبودن سرویس)، و Partition Tolerance (تحمل جداسازی شبکه).
در بانکداری، Consistency حیاتی است؛ نمیخواهیم کسی یک مبلغ را دو بار خرج کند یا موجودی منفی شود. فرض کنیم بین دیتابیس مرکزی و شعبه ارتباط قطع میشود (Partition). شما تصمیم میگیرید CP باشید: یعنی حفظ Consistency و تحمل Partition را به قیمت کاهش Availability ترجیح دهید.
در این حالت، تا زمانی که پارتیشن برطرف نشود، تراکنشهایی که نیازمند اطلاعات لحظهای از هر دو دیتابیس هستند، fail میشوند یا در صف انتظار میمانند. کاربر ممکن است پیام خطا بگیرد، اما هیچگاه داده ناسازگار ایجاد نمیشود.
نمونه شِمای شبهکد C# در سطح سرویس:
public async Task<bool> TransferAsync(string fromAccount, string toAccount, decimal amount)
{
using var tx = await dbContext.Database.BeginTransactionAsync();
var from = await dbContext.Accounts
.SingleAsync(a => a.Id == fromAccount, cancellationToken: ct);
var to = await dbContext.Accounts
.SingleAsync(a => a.Id == toAccount, cancellationToken: ct);
if (from.Balance < amount)
throw new InvalidOperationException("Insufficient funds.");
from.Balance -= amount;
to.Balance += amount;
await dbContext.SaveChangesAsync(ct);
await tx.CommitAsync(ct);
return true;
}
اگر دیتابیس مرکزی در دسترس نباشد، عملیات BeginTransactionAsync() یا SaveChangesAsync() با خطا مواجه میشود و انتقال انجام نمیشود (سیستم قابلدسترس نیست اما داده هرگز ناسازگار نخواهد شد).
⚡ نکته: در سیستمهایی مثل پیامرسان یا DNS، معماری AP (یعنی Available + Partition Tolerant) محبوبتر است؛ چون Consistency لحظهای اهمیت کمتری دارد و همیشه پاسخدهی مهمتر است. اما در حوزههایی که دقت مهم است (مثل مالی)، CP ارجح است—even اگر به معنی عدم دسترسی لحظهای باشد.
#معماری #CAP #Consistency #بانکداری #BestPractices
@DeveloperAdvocate 🥑1 035
🔍 مدیریت حافظه در #DotNet — عمیقتر به GC و IDisposable
یکی از زیرساختهای کلیدی .NET، مدیریت خودکار حافظه از طریق GC است، اما هنوز دانستن نحوه همکاری صحیح با این سیستم، خصوصاً زمانی که unmanaged resourceها (مثل فایل، شبکه یا هندلهای Win32) در بازی هستند، حیاتی است.
اصل ماجرا: GC فقط حافظه managed را مدیریت میکند و دربارهی آزادسازی منابع unmanaged اطلاعی ندارد. این دقیقاً همانجاست که
IDisposable وارد میشود. وقتی کلاسی داری که unmanaged resource را نگه میدارد، باید IDisposable را پیادهسازی و الگوی Disposable pattern را رعایت کنی.
الگوی پیشنهادی مایکروسافت:
public class NativeResourceWrapper : IDisposable
{
private IntPtr _resource;
private bool _disposed;
public NativeResourceWrapper()
{
_resource = AcquireUnmanagedResource();
}
~NativeResourceWrapper()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
// آزادسازی resourceهای managed اینجا
}
// آزادسازی resourceهای unmanaged اینجا
if (_resource != IntPtr.Zero)
{
ReleaseUnmanagedResource(_resource);
_resource = IntPtr.Zero;
}
_disposed = true;
}
}
نکتههای حیاتی:
- همیشه GC.SuppressFinalize(this) را در Dispose() صدا بزن تا جلوی اضافهکاری فاینالایزر گرفته شود.
- اگر کلاس تو فقط resourceهای managed دارد و هیچگونه unmanaged resource به ارث نبرده، از فاینالایزر صرفنظر کن.
- هرگز Dispose چندباره صدا نزن. با flag مثل _disposed مانع شو.
در نهایت، استفاده از using (یا await using برای asyncها) رو فراموش نکن:
using (var resource = new NativeResourceWrapper())
{
// استفاده امن از resource
}
یا:
await using var resource = new AsyncNativeResourceWrapper();
با این الگو، صد درصد مطمئن خواهی بود که منابع ارزشمند سیستم هیچگاه لیک نمیشوند و GC نهایت کارایی را خواهد داشت. 🧹🕵️♂️
@DeveloperAdvocate 🥑1 035
ایمپوتنسی در APIها—یک شاهکلید برای اعتبار و امنیت تراکنشهای حساس (مانند پرداختها) — به سادگی با شناسه درخواست پیادهسازی میشود: هر درخواست تکراری با همین شناسه همیشه یک نتیجه مشابه برمیگرداند، حتی اگر چند بار فراخوانی شود.
در ASP.NET Core، یک استراتژی حرفهای، طراحی یک فیلتر میانی (Middleware) است که درخواستهای با X-Idempotency-Key را رهگیری و مدیریت میکند. سادگی معماری در گروی این است که ذخیرهسازی کلید و پاسخ، اتمیک و thread-safe باشد؛ مثلا Redis گزینهای عالی است.
نمونهی پیادهسازی با Redis:
public class IdempotencyMiddleware
{
private readonly RequestDelegate _next;
private readonly IDatabase _redis;
public IdempotencyMiddleware(RequestDelegate next, IConnectionMultiplexer redis)
{
_next = next;
_redis = redis.GetDatabase();
}
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Method != HttpMethods.Post)
{
await _next(context);
return;
}
var key = context.Request.Headers["X-Idempotency-Key"].FirstOrDefault();
if (string.IsNullOrEmpty(key))
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
await context.Response.WriteAsync("Missing Idempotency Key.");
return;
}
var redisKey = $"idem:{key}";
var cached = await _redis.StringGetAsync(redisKey);
if (cached.HasValue)
{
// بازگشت پاسخ کَششده
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(cached);
return;
}
// گرفتن پاسخ واقعی
var originalBody = context.Response.Body;
using var memStream = new MemoryStream();
context.Response.Body = memStream;
await _next(context);
memStream.Position = 0;
var response = await new StreamReader(memStream).ReadToEndAsync();
await _redis.StringSetAsync(redisKey, response, TimeSpan.FromHours(1)); // TTL امن
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
context.Response.Body = originalBody;
}
}
نکات حرفهای:
- به کمک decorator یا middleware، قابلیت ایمپوتنتسی را یکپارچه و قابلتست کنید.
- کلید را به صورت ترکیبی از کاربر و endpoint بسازید: مثلا userId:endpoint:idempotencyKey.
- پیادهسازی باید نسبت به race condition مقاوم باشد (در Redis میتوانید SETNX و expire را اتمیک انجام دهید).
- در تراکنشهای بانکی، حتما پاسخ و وضعیت پردازش را درون یک دیتابیس ACID ذخیره کنید؛ Redis برای stateless بودن کفایت میکند، اما همیشه کافی نیست.
ایمپوتنسی عمق معماری API را بالا میبرد؛ با رویکرد بالا، سرویس شما حرفهای و قابل پیشبینی خواهد شد.
@DeveloperAdvocate 🥑1 035
🎯 پردازش بدون تخصیص (Zero-Allocation) روی رشتهها با Span<T>
در داتنت، با استفاده از
Span<T> میتوانید پردازش کارآمد و سریع روی دادههای رشتهای انجام دهید؛ بدون اینکه آبجکتهای جدید رشته یا آرایه تخصیص دهید. نمونهای عمیق از حذف تمام فاصلههای ابتدای و انتهای یک رشته (Trim)، بدون هیچ تخصیص اضافی:
public static ReadOnlySpan<char> TrimSpaces(ReadOnlySpan<char> input)
{
int start = 0;
int end = input.Length - 1;
// پیدا کردن اولین کاراکتر غیر فاصله از ابتدا
while (start <= end && char.IsWhiteSpace(input[start]))
start++;
// پیدا کردن اولین کاراکتر غیر فاصله از انتها
while (end >= start && char.IsWhiteSpace(input[end]))
end--;
return input.Slice(start, end - start + 1);
}
این تابع نه تنها هیچ رشته جدیدی نمیسازد، بلکه امکان استفاده مستقیم از دادههای رشته روی استک را میدهد:
string raw = " سلام دنیا! ";
ReadOnlySpan<char> trimmed = TrimSpaces(raw);
Console.WriteLine(trimmed.ToString()); // خروجی: "سلام دنیا!"
نکته! Span<T> مختص دادههای وراثتناپذیر و مبتنی بر حافظه کوتاهمدت (stack-only) است؛ برای عبور Zero-Allocation بین async/await یا ذخیره طولانیتر از Memory<T> استفاده کنید. این رویکرد برای پردازش رشتههای عظیم، پارس سریع و Web API های high-performance ایدهآل است.
@DeveloperAdvocate 🥑1 035
وباسمبلی (WebAssembly یا WASM) یک فرمت باینری کامپکت و سریع برای اجرای کد نزدیک به native در مرورگر است و به زبان ساده، امکان اجرای codeهای کامپایلشده (مثلاً C#, Rust, C/C++) را مستقیماً روی Web Platform میدهد، بدون وابستگی به JavaScript.
اما پتانسیل وباسمبلی برای داتنت عمیقتر از صرفاً اجرای کد در مرورگر است:
با پروژههایی مثل Blazor WebAssembly و ASP.NET Core WASM، میتوان اپلیکیشنهای SPA کاملاً C#-محور نوشت که کدهای UI و لاجیک شما به WASM ترجمه شده و در sandbox مرورگر اجرا میشود. این معماری علاوه بر cross-platform بودن، مزایایی مثل type safety، ابزارهای پیشرفته ویژوال استودیو و اشتراک لایههای مدل/بیزینس بین کلاینت و سرور را برای شما فراهم میکند.
مثال: روش RCL (Razor Class Library) و اشتراک دیتامدل بین بلازور WASM و سرور:
// در پروژه Shared
public record WeatherForecast(DateTime Date, int TemperatureC, string? Summary);
// API Controller (سرور)
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public IEnumerable<WeatherForecast> Get() =>
Enumerable.Range(1, 5).Select(index =>
new WeatherForecast(
DateTime.Now.AddDays(index),
Random.Shared.Next(-20, 55),
"Sample forecast..."
));
}
// در کلاینت Blazor WASM
var forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
در این مدل، کامپوننتهای UI، مدلها و اعتبارسنجی همه در C# و به شکلی اشتراکی قابل توسعه هستند و frontend به معنای واقعی multi-platform و maintainable میشود.
برای سناریوهای FFI (صدا کردن کتابخانههای native از WASM)، WASI یا technologyهایی مثل dotnet/runtime.wasm فضای اکوسیستم را بیشتر هم گسترش دادهاند.
در پروژههای با نیاز به performance بالا (مثل بازیها، گرافیک و فریمورکهای تعاملی) یا میکروسرویسهای edge، WASM با داتنت یک انتخاب آیندهنگر است که تا سالهای آینده مسیر web engineering را دگرگون خواهد کرد.
@DeveloperAdvocate 🥑1 035
یک اصل کلیدی در توسعه سیستمهای مدرن پذیرش logging ساختیافته (Structured Logging) است؛ رویکردی که فقط به متن خام پیام اکتفا نمیکند، بلکه دادهها را بهصورت جفت کلید-مقدار و با ساختار منظم ثبت میکند. چرا این مهم است؟ در سامانههایی با معماری توزیعشده (خصوصاً میکروسرویسها) جستوجو، پایش و آنالیز رخدادها بدون وجود ساختار در لاگها عملاً غیرممکن یا بسیار پرهزینه خواهد بود.
فرض کنید خطای زیر را با logging سنتی ثبت میکنید:
_logger.LogError("User with id 42 not found.");
در شکل ساختیافته، همان رخداد را اینگونه ثبت میکنیم:
_logger.LogError("User not found {@UserId}", userId);
خروجی ساختیافته (بهویژه توسط Loggerهای مدرن مانند Serilog با sinkهای JSON) بهشکل زیر خواهد بود:
{
"Timestamp": "2024-06-09T18:32:00.123Z",
"Level": "Error",
"MessageTemplate": "User not found {@UserId}",
"RenderedMessage": "User not found 42",
"UserId": 42
}
این تحلیلپذیری را بهشدت افزایش میدهد. موتورهای جستوجو مانند Elasticsearch بهراحتی میتوانند بر اساس مقادیر کلیدها فیلتر، آمارگیری و ترندگیری کنند (مثلاً پیدا کردن تعداد خطاهای مربوط به UserId خاص). از طرف دیگر، جداسازی Concerns و نگهداری متادیتا (مانند CorrelationId, HTTPStatusCode و ...)، رهگیری تراکنشهای توزیعشده را ساده میکند:
_logger.LogInformation("Process order {@OrderId} by user {@UserId}", order.Id, user.Id);
نکته حرفهای: ترجیحاً تمام Contextهای اصلی (مانند شناسه درخواست، محیط اجرا، نسخه نرمافزار) را با scopes به لاگ تزریق کنید:
using (_logger.BeginScope(new Dictionary<string, object>
{
["CorrelationId"] = correlationId,
["Environment"] = env
}))
{
// Log structured here
}
در نهایت، ساختیافتهنویسی در کنار Observalibility اجازه میدهد پایش هوشمند و پاسخ سریع به رخدادها و Anomalyها داشته باشید، و Logging را به یک ابزار استراتژیک در توسعه و عملیات تبدیل میکند. این تحول فقط یک انتخاب فنی نیست—یک الزام معماری برای نرمافزارهای آیندهنگر است.
@DeveloperAdvocate 🥑1 035
🎯 آپدیت خوشبینانه (Optimistic Update) با TanStack Query (React Query) در فرانتاند
یکی از ویژگیهای جذاب TanStack Query، پشتیبانی درخشان از آپدیتهای خوشبینانه (optimistic) است؛ یعنی رفتار UI را فوراً مطابق با انتظارات کاربر آپدیت میکنیم (قبل از تأیید سرور)، سپس خطاها را هندل میکنیم، و چابکی UX را بهبود میبخشیم.
★ سناریو: فرض کنیم لیستی از تسکها داریم و میخواهیم موقع ویرایش عنوان یک تسک، فوراً UI را بروز کنیم.
import { useMutation, useQueryClient } from '@tanstack/react-query';
function useUpdateTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, newTitle }) =>
fetch(`/api/tasks/${id}`, {
method: 'PATCH',
body: JSON.stringify({ title: newTitle }),
headers: { 'Content-Type': 'application/json' }
}).then(res => res.json()),
// 🔸 مرحله اول: optimistic update UI
onMutate: async ({ id, newTitle }) => {
await queryClient.cancelQueries(['tasks']);
const prevTasks = queryClient.getQueryData(['tasks']);
queryClient.setQueryData(['tasks'], old =>
old.map(task =>
task.id === id ? { ...task, title: newTitle } : task
)
);
return { prevTasks };
},
// 🔸 مرحله دوم: خطا رخ دهد، rollback کن
onError: (err, variables, context) => {
queryClient.setQueryData(['tasks'], context.prevTasks);
},
// 🔸 مرحله سوم: دیتا را دوباره اعتبارسنجی کن
onSettled: () => {
queryClient.invalidateQueries(['tasks']);
}
});
}
🔍 نکات معماری:
- مقدار prevTasks را حتماً ذخیره کنید تا rollback دقیق و بدون مشکل انجام شود.
- data mutation باید idempotent و قابل جبران باشد تا sync سرور-کلاینت حفظ شود.
- برای ساختارهای پیچیدهتر (یا multiple sources)، هر کوئری مرتبط را cancel و setQueryData کنید.
👾 این سبک خوشبینانه، تجربه کاربری real-time بدون پیچیدگی مدیریت state دستی فراهم میسازد—و نقطه تلاقی Clean Frontend Architecture و قاعده CQRS است.
@DeveloperAdvocate 🥑1 035
✨ آشنایی با قدرت Collection Literals اختصاصی در .NET 8 با CollectionBuilder ✨
در .NET 8، با ویژگی جدید
[CollectionBuilder]، حالا میتوانید برای کلاسهای کلکشن خود، سینتکس Collection Literal (همانند { a, b, c }) را تعریف کنید. این یعنی میتوانید کلکشنهای Immutable، struct-based یا حتی custom خود را با نوشتاری تمیز و قابلفهم مقداردهی کنید.
فرض کنید یک نوع کلکشن سفارشی داریم:
public readonly struct MySet<T>
{
private readonly HashSet<T> _set;
public MySet(IEnumerable<T> items) => _set = new(items);
public bool Contains(T item) => _set.Contains(item);
}
حالا، کافیست یک Builder معرفی کنیم:
public static class MySetBuilder
{
public static MySet<T> Create<T>(ReadOnlySpan<T> items) => new MySet<T>(items.ToArray());
}
و یک attribute بالا سر کلکشن بگذاریم:
using System.Runtime.CompilerServices;
[CollectionBuilder(typeof(MySetBuilder), "Create")]
public readonly struct MySet<T>
{
// ... (همان کد قبلی)
}
حالا میتوانید از این syntax استفاده کنید:
MySet<int> numbers = [1, 2, 3, 5, 8];
if (numbers.Contains(5))
Console.WriteLine("در مجموعه وجود دارد!");
🔹 نکات:
- متد Create باید static بوده و یک پارامتر از نوع ReadOnlySpan<T> یا IEnumerable<T> بگیرد.
- تمام قدرت Collection Literalها (مثل استفاده در مقداردهی اولیه یا آرگومان) به دستتان خواهد بود.
- محدودیتی برای استفاده در کلاسهای struct-based، Immutable یا انواع خاص دیگر نیست!
این قابلیت برای طراحی APIهای تمیز، DSLها و کلکشنهای امن (مانند Immutableها) واقعاً جذاب است. لذت ببرید از سینتکس مدرن! 🚀
@DeveloperAdvocate 🥑1 035
مفهوم Pod در Kubernetes در دنیای دیپلوی اپلیکیشنهای .NET بسیار حیاتیست. هر Pod، معمولاً یک پروسه منفرد از اپلیکیشن شما را اجرا میکند—مثلاً یک instance از aspnetcore web API. این Pod شامل یک یا چند کانتینر است که منابع سیستمی مانند shared volume و network را به اشتراک میگذارند. به یاد داشته باشید: Podها در سطح smallest deployable unit در K8s تعریف میشوند و خودشان مستقیماً قابل ارتقا (scalable) نیستند; برای مقیاسپذیری افقی باید Deployment یا ReplicaSet تعریف کنید.
نمونه yaml برای اجرای یک اپلیکیشن .NET 8 ساده:
apiVersion: v1
kind: Pod
metadata:
name: dotnet-webapi
spec:
containers:
- name: webapi
image: mcr.microsoft.com/dotnet/aspnet:8.0
ports:
- containerPort: 80
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
volumeMounts:
- name: logs
mountPath: /app/logs
volumes:
- name: logs
emptyDir: {}
هر بار که K8s این Pod را اجرا میکند، یک پلاگ کپسولهشده و ایزوله از وباپلیکیشن .NET دارید. نکته کلیدی: اگر Pod به هر دلیلی حذف شود، state آن (بهغیر از volumeهای persist شده) از بین میرود. پس لاگینگ، کش یا state مهم را هرگز روی فایلسیستم داخلی کانتینر ذخیره نکنید؛ یا آنها را به external service متصل کنید.
در معماری حرفهای، بهتر است state یا لاگها را به ابزارهایی چون Azure Blob Storage، Redis، یا ELK ارسال کنید و فقط از volumeهای ephemeral برای موارد موقتی (مثل بافر لاگهای کوتاهمدت) بهره بگیرید.
برای اجرای web API .NET:
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
جمعبندی: K8s Pod یعنی حداقل تار و پود اجرای اپلیکیشن شما در کلاستر؛ مراقب کپسولهسازی، ephemeral بودن state و مدیریت منابع اشتراکی باشید تا بتوانید از معماری native cloud به شکل بهینه سود ببرید.
@DeveloperAdvocate 🥑1 035
Virtual DOM دقیقا چیست و چرا قلب تپندهی عملکرد React محسوب میشود؟
وقتی ساختار UI را با React توصیف میکنید، در واقع با یک آبجکت جاواسکریپتی (Virtual DOM) در حافظه سروکار دارید که نمایی انتزاعی از DOM واقعی مرورگر است. این مدل انتزاع به React اجازه میدهد تا کل وضعیت نمایشی را هر بار از نو رندر نماید—بدون نگرانی دربارهی هزینهی بالای عملیات روی DOM.
اما راز بهبود عملکرد در تفاوتگیری هوشمندانه (Diffing) نهفته است. React بعد از هر تغییر state، یک نسخهی جدید از Virtual DOM میسازد و آن را با نسخهی قبلی مقایسه میکند تا فقط تغییرات واقعا لازم را شناسایی کند. سپس حداقل مجموعه از دستورات اصلاحی (Patch) را روی DOM واقعی اِعمال میکند. این فرآیند batched و async است؛ یعنی حتی اگر state چندین بار تغییر کند، React تلاش میکند فقط یکبار، با حداقل دسترسی به DOM، همه چیز را بروزرسانی کند—و این نقطهی عطف عملکردی است.
این معماری را میتوان با دنیای #CSharp و طراحی Immutable Data Structures مقایسه کرد. مثلا تصور کنید برای هر تغییر وضعیت، معادلی از یک آرایهی جدید تولید میکنید اما فقط المانهایی را که واقعا تغییر کردهاند، در اختیار سیستم اصلی قرار میدهید. این شبهکد را ببینید:
var previousState = new[] { "A", "B", "C" };
var nextState = new[] { "A", "X", "C" };
// Diff logic
for (int i = 0; i < previousState.Length; i++)
{
if (previousState[i] != nextState[i])
{
Console.WriteLine($"Element at index {i} changed from {previousState[i]} to {nextState[i]}");
// Apply minimal update
}
}
این مثال ساده گویای فلسفهی Virtual DOM است: به جای رندر دوبارهی کل UI، فقط همان قسمتی که باید تغییر کند، واقعا تغییر میکند؛ نتیجه؟ کارایی فوقالعاده، مصرف منابع بهینه و تجربه کاربری روانتر.
🔹 پس اگر خواستید معماری کلاینتی با UIهای پیچیده و تعاملی حتی در سیستمهای مبتنی بر Blazor یا MAUI طراحی کنید، ایده مفهومی Virtual DOM و الگوریتم Diffing را در ذهن داشته باشید—اینجا هم میتواند الهامبخش باشد!
@DeveloperAdvocate 🥑1 035
🔹 GitOps Workflow با ArgoCD برای دپلوی داتنت روی Kubernetes
در فضای مدرن DevOps، GitOps یکی از رویکردهای تحولگراست که Source of Truth را به گیت میسپارد و چرخه CI/CD را Automation-first میسازد. اجازه دهید یک سناریوی جدی برای دیپلوی اپلیکیشن ASP.NET Core روی K8s با GitOps و ArgoCD را لایهبهلایه بررسی کنیم:
۱️⃣ ریپازیتوریها:
- یک ریپوی source کد (.NET, Dockerfile, CI Pipeline)
- یک ریپوی config (YAMLهای K8s، Helm یا Kustomize)؛ این دقیقا همان ریپویی است که ArgoCD آنرا sync میکند.
۲️⃣ پایپلاین CI:
- با commit روی Master در ریپوی سورس:
- Docker image بیلد و روی رجیستری (مثلاً ACR) پابلیش میشود.
- تگ ایمیج (مثلاً با
git commit sha) تولید میشود.
- یک PR یا commit در ریپوی config، ایمیج جدید را به YAMLهای K8s تزریق میکند:
# deployment.yaml
spec:
containers:
- name: my-dotnet-app
image: myregistry.azurecr.io/my-dotnet-app:1.3.4-$(git rev-parse --short HEAD)
۳️⃣ پایپلاین CD - ArgoCD:
- ArgoCD یک App تعریف میکند که این ریپوی config را مانیتور میکند.
- هر تغییر در YAML (مثلا تگ ایمیج) توسط ArgoCD تشخیص و روی K8s sync میشود (auto-sync یا manual).
- drift detection کاملا اتوماتیک: اگر کسی دستی Deployment را ویرایش کند، ArgoCD آنرا برمیگرداند!
۴️⃣ Secret Management:
- توصیه: از SealedSecrets یا HashiCorp Vault برای secrets استفاده کنید تا تمام تعریفها تحت version control باقی بمانند.
۵️⃣ Notes برای مهندسان داتنت:
- Health checks (readinessProbe, livenessProbe) را در K8s manifests لحاظ کنید تا rolloutها zero-downtime شوند.
- Config های اضافی اپلیکیشن (مثل appsettings.Production.json) را با Kubernetes ConfigMap مدیریت و mount کنید.
- Logging, telemetry و distributed tracing (OpenTelemetry) را به صورت cloud-native در دپلویمنت لحاظ کنید.
۶️⃣ نمونه تعریف ArgoCD Application:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-dotnet-app
spec:
project: default
source:
repoURL: 'https://github.com/myorg/dotnet-k8s-config'
targetRevision: main
path: charts/my-dotnet-app # یا manifests/
destination:
server: 'https://kubernetes.default.svc'
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
🔹 نکته مهم: هر چیز Production روی K8s را فقط از طریق Git تغییر دهید؛ نه kubectl apply دستی! شما نه فقط دپلویمنت، بلکه RBAC، Ingress و policies را هم گیتاپس کنید.
این رویکرد، پایداری، auditability و سرعت خیرهکنندهای برای rollout ها به ارمغان میآورد—آن هم دقیقا با معرفی ساختار Infrastructure-as-Code برای کل چرخه توسعه و عملیات. 🚀
@DeveloperAdvocate 🥑1 035
💡 عملکرد بالای I/O در .NET با System.IO.Pipelines
در سناریوهای شبکهای با ترافیک بالا—مانند سرورهای HTTP، پراکسیها یا هر سرویسی که نیازمند پردازش همزمان حجم زیادی از داده است—حتی بهینهسازیهای جزئی در بخش I/O میتوانند تفاوتهای بنیادی ایجاد کنند. کلاسیک APIهای مانند Stream یا NetworkStream، به رغم راحتی کار، هزینههای مدیریت buffer، کپی دادههای تکراری و مشکلات backpressure را دارند. اینجاست که System.IO.Pipelines وارد میدان میشود.
Pipelines مفهومی مدرن و با الهام از runtimes سطح پایینتر (مثل Kestrel) است که با تفکیک بارگذاری/نوشتن داده (Writer) از خواندن/پردازش داده (Reader)، به شما اجازه میدهد کاملاً Async، Minimal Allocation و بدون پردازش اضافی کار کنید.
نمونهی فوقساده برای خواندن و پردازش HTTP Request:
using System.IO.Pipelines;
using System.Net.Sockets;
async Task ProcessRequestsAsync(Socket socket)
{
var networkStream = new NetworkStream(socket, ownsSocket: false);
var pipe = new Pipe();
Task writing = FillPipeAsync(networkStream, pipe.Writer);
Task reading = ReadPipeAsync(pipe.Reader);
await Task.WhenAll(reading, writing);
}
async Task FillPipeAsync(NetworkStream stream, PipeWriter writer)
{
while (true)
{
Memory<byte> memory = writer.GetMemory(4096);
int bytesRead = await stream.ReadAsync(memory);
if (bytesRead == 0) break;
writer.Advance(bytesRead);
FlushResult result = await writer.FlushAsync();
if (result.IsCompleted) break;
}
await writer.CompleteAsync();
}
async Task ReadPipeAsync(PipeReader reader)
{
while (true)
{
ReadResult result = await reader.ReadAsync();
ReadOnlySequence<byte> buffer = result.Buffer;
// پردازش دادهی دریافتی (مثلاً پارس کردن HTTP Request)
ProcessBuffer(buffer);
reader.AdvanceTo(buffer.End);
if (result.IsCompleted) break;
}
await reader.CompleteAsync();
}
مزایای ملموس Pipelines:
- صفر شدن کپی دادههای غیرضروری (Zero-Copy)
- مدل backpressure خودکار و هماهنگ بین Producer/Consumer
- تخصیص حداقلی heap و کنترل مستقیم روی lifecycle داده
🎯 در سرویسهای Performance-Critical، Pipelines انتخاب کسانیست که زیرساختشان را با وسواس برای مقیاسپذیری و latency پایین مینویسند.
منابع تکمیلی:
- مستند رسمی: Pipelines documentation
- کد Kestrel: منبع الهام معماریهای مدرن I/O در ASP.NET Core
#dotnet #HighPerformance #IO #Pipelines
@DeveloperAdvocate 🥑1 035
🎯 تکنیک پیشرفته Git: از Interactive Rebase تا Cherry-pick برای کنترل تاریخچه
در پروژههای enterprise، تمیز نگهداشتن تاریخچه git اهمیت استراتژیک دارد، مخصوصاً هنگام merge چندین feature branch و هماهنگی تیمهای بزرگ. دو فرمان زیر، سطحی فراتر از commit/merge سنتی را به ارمغان میآورند:
🚀 Interactive Rebase (
git rebase -i)
این ابزار، محیطی برای بازنویسی تاریخچه branch به شما میدهد — ایدهآل برای squash کردن commitهای تودرتو، reorder کردن آنها یا حتی اصلاح پیغام commit پیش از merge به main. مثال رایج:
git rebase -i HEAD~5
یک editor باز میشود؛ میتوانید همانجا commitها را با دستوراتی مثل squash یا reword مدیریت کنید.
🔎 چه زمانی؟
- قبل از PR/merge به main، برای تمیز کردن commitها و نوشتن پیامهای مناسب.
- حذف commitهای اضافی یا اشتباه، حتی commitهای "WIP" که تهدیدی برای maintainability هستند.
---
🔄 Cherry-pick (git cherry-pick <commit>)
وقتی فقط یک (یا چند) تغییر خاص از یک branch باید به branch دیگر اعمال شود (مثلاً hotfix مهم از develop به release)، cherry-pick راه نجات شماست:
git cherry-pick 3e5f2a1
یک commit خاص را به branch فعلی میآورد، بجای merge کامل. به این ترتیب، کنترل دقیق روی تاریخچه و جلوگیری از وارد شدن تغییرات ناخواسته دارید.
---
🛡️ Best Practices
- بعد از هر interactive rebase، حتماً با git log --oneline بازبینی کنید؛ تاریخچه شما مظبوطتر از همیشه باید باشد.
- در پروژههای حساس به audit trail، interactive rebase و force push فقط روی feature branchها مجاز است، هرگز روی branchهای اصلی!
- همیشه قبل از cherry-pick به وجود dependency در commitها دقت کنید تا تاریخچه دچار inconsistency نشود.
این ابزارها را در کنار هم بهکار ببرید تا git history در سازمانتان تبدیل به یک داستان شفاف، audit-friendly و توسعهپذیر شود.
@DeveloperAdvocate 🥑1 035
❓ چرا ایندکسها در پایگاههای داده حیاتیاند؟
اغلب تیمها، زمانی ارزش واقعی ایندکسها را درک میکنند که یک کوئری سنگین آنها را زمینگیر کرده و DBA وارد میدان میشود. بیایید با یک مثال ملموس نگاه کنیم:
فرض کنید جدولی بزرگ به نام Users داریم با میلیونها رکورد:
SELECT * FROM Users WHERE Email = 'alice@example.com'
در صورت نبود ایندکس روی ستون Email، دیتابیس مجبور است کل جدول را اسکن (Full Table Scan) کند تا رکورد مورد نظر را پیدا کند — این یعنی O(n)!
🎩 اما اگر ایندکس مناسب داشته باشیم:
CREATE INDEX IX_Users_Email ON Users(Email);
حال همین کوئری میتواند با پیچیدگی O(log n) یا حتی سریعتر، از ایندکس B-Tree کمک بگیرد و بهسرعت نتیجه را بیابد.
حتی در EF Core هم همین داستان برقرار است:
var user = dbContext.Users.SingleOrDefault(u => u.Email == "alice@example.com");
بدون ایندکس، این خط کد میتواند کابوس Prod شما شود!
⚡️ نکته معماری: هر ایندکس هزینهای هم دارد (افزایش حجم و کاهش سرعت INSERT/UPDATE). پس فقط روی ستونهایی ایندکس بسازید که واقعاً فیلتر یا جستوجوی پرتکراری دارند.
⛏️ ابزارهای مانند SQL Server’s Query Analyzer و Postgres’s EXPLAIN کمک میکنند دقیقا Bottleneckها را شناسایی و ایندکس را هدفمند اضافه کنید.
آیا تجربهای در بهینهسازی کوئریها با ایندکس داشتهاید؟ تجربیات خود را کامنت کنید!
@DeveloperAdvocate 🥑1 035
🔍 چالش N+1 Query در EF Core: شناسایی و حل با ابزارهای لاگگیری
مشکل N+1 Query یکی از مهمترین دلایل افت عملکرد در پروژههای EF Core است؛ وقتی برای هر رکورد اصلی، یک کوئری جدا جهت لود کردن دادههای مرتبط اجرا میشود. برای مثال یک لیست از Orders با لود شدن Customer هرکدام:
var orders = context.Orders.ToList();
foreach (var order in orders)
{
var customerName = order.Customer.Name;
}
برای هر order یک Query جهت Customer اجرا خواهد شد: 👎
✅ شناسایی با لاگگیری EF Core
ابتدا لاگهای EF Core را فعال کنید تا کوئریهای ارسالشده به دیتابیس را ببینید:
var options = new DbContextOptionsBuilder<AppDbContext>()
.LogTo(Console.WriteLine, LogLevel.Information)
.Options;
using var context = new AppDbContext(options);
// اجرای کد مشکلدار اینجا
در Console خروجیای مشابه زیر میبینید:
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT ... FROM Orders Executed DbCommand (1ms) [Parameters=[@p0='1'], ...] SELECT ... FROM Customers WHERE Id = @p0 Executed DbCommand (1ms) [Parameters=[@p0='2'], ...] SELECT ... FROM Customers WHERE Id = @p0 ...تعداد زیاد کوئریهای مشابه = تایید وجود N+1! 💡 حل صحیح: Eager Loading با Include استفاده از
.Include() برای حذف N+1:
var orders = context.Orders
.Include(o => o.Customer)
.ToList();
foreach (var order in orders)
{
var customerName = order.Customer.Name; // بدون کوئری اضافه
}
در لاگ فقط دو query خواهید دید: یکی برای Orders و یکی Join با Customers 👌
🚩 نکات حرفهای:
- اگر رابطهی چندسطحی دارید، از .ThenInclude() استفاده کنید.
- برای روابط پیچیده یا زمانی که فقط به برخی فیلدها نیاز دارید:
از Projection با .Select() استفاده کنید تا دادههای اضافی ارسال نشوند.
var orderSummaries = context.Orders
.Select(o => new
{
o.Id,
CustomerName = o.Customer.Name
})
.ToList();
🔓 نتیجه:
فعالسازی لاگ EF Core و تحلیل خروجی، بهترین راه برای یافتن و رفع N+1 در کد است. با Propel کردن کوئری با Include یا Projection، عملکرد اپ خود را چندبرابر بهبود دهید!
@DeveloperAdvocate 🥑1 035
✴️ Service Discovery در معماری مایکروسرویس: چرا حیاتی است؟
در معماری مایکروسرویس، سرویسها اغلب به صورت پویا scale میشوند، Crash میکنند یا حتی به سرورهای مختلف منتقل میشوند. اگر سرویسها، آدرس یکدیگر را hardcode کنند، نهایتاً با یک spaghetti از dependencyها مواجه خواهید شد که نگهداری آن بسیار مشکل خواهد بود.
🔷 Service Discovery دقیقا چه کار میکند؟
Service Discovery ابزاری است که endpointهای هر سرویس را به طور داینامیک پیدا و ردیابی میکند. این مکانیزم معمولاً دو مدل دارد:
- Client-side discovery: کلاینت قبل از ارسال درخواست، از Service Registry مکان سرویس مقصد را میپرسد (مثلاً Netflix Eureka، Consul).
- Server-side discovery: Load balancer مرکزی درخواست را دریافت و به آدرس معتبر هدایت میکند (مثلاً Kubernetes Service یا AWS ELB).
مزیت اصلی: سرویسها به جای آدرسدهی مستقیم، فقط نام logical سرویس مقصد را میشناسند؛ زیرساخت، بقیه مسیر را handle میکند.
🔥 نمونهای از رجیستر کردن سرویس در Consul با C#:
using Consul;
var registration = new AgentServiceRegistration()
{
ID = "orderservice-1",
Name = "OrderService",
Address = "10.0.0.21",
Port = 5000,
Check = new AgentServiceCheck
{
HTTP = "http://10.0.0.21:5000/health",
Interval = TimeSpan.FromSeconds(30)
}
};
// Register to Consul
using(var client = new ConsulClient())
{
await client.Agent.ServiceRegister(registration);
}
👈 نتیجه: با Service Discovery، سیستم شما Resilient و Evolvable میشود، و به آسانی میتوانید قابلیتهایی مثل load balancing، health check، و auto-scaling را به اکوسیستم اضافه کنید—بدون نیاز به تغییر در منطق سرویسها.
📌 تجربه شما با Service Discovery چگونه بوده؟ کدام Tool را ترجیح میدهید و چرا؟
@DeveloperAdvocate 🥑1 035
تعامل پیشرفته با Dependency Injection در ASP.NET Core: معرفی ServiceProviderOptions
اغلب با Patternهای ساده DI آشنا هستیم، اما سناریوهایی وجود دارد که کنترل دقیقتر روی رفتار ServiceProvider نیاز است؛ به خصوص زمانی که Performance یا رفتار Singletonها اهمیت کلیدی دارند.
💡 یکی از گزینههای مهم، ServiceProviderOptions است که اجازه میدهد رفتار Runtime ServiceProvider را تغییر دهید. دو Property مهم آن:
– ValidateScopes: اگر true باشد و Scoped serviceها به صورت Singleton تزریق شوند، Exception میگیرند (حائز اهمیت در اپلیکیشنهای Enterprise-grade).
– ValidateOnBuild: با فعال بودن، بهمحض Build کردن ServiceProvider، صحت Registrationها را ارزیابی میکند و خطاهای Potential را زودهنگام شناسایی میکند.
در پروژههای بزرگ، توصیه میشود این Featureها را فعال کنید تا از خطاهای حیاتی در زمان اجرا جلوگیری شود:
var services = new ServiceCollection();
// تعریف Serviceها...
var provider = services.BuildServiceProvider(new ServiceProviderOptions
{
ValidateScopes = true,
ValidateOnBuild = true
});
نکته کلیدی:
فعالسازی این گزینهها میتواند کمک شایانی در کشف زودهنگام Misconfigurationهای DI داشته باشد و Health کلی مشروعیت IoC را تضمین کند ⚙️
#Dotnet #DependencyInjection #BestPractices
@DeveloperAdvocate 🥑