💡 تکنیک Clean Code: جایگزینی if های پیچیده با متدهای گویا
شرطهای if پیچیده، خوانایی کد را به شدت پایین میآورند. 😫
وقتی چند شرط مجزا با هم ترکیب میشوند، درک منطق در نگاه اول حتی سختتر هم میشود.
اما میتوانیم این مشکل را با یک بازآرایی (refactoring) ساده حل کنیم.
راه حل: ↙️
منطق شرط را به یک متد یا پراپرتی با اسم گویا منتقل کنید.
👍🏻حالا نام توصیفی متد، توضیح میدهد که شرط چیست.
به یاد داشته باشید: درک زبان طبیعی برای انسانها، همیشه راحتتر از درک کد است.
📖 سری آموزشی کتاب C# 12 in a Nutshell
تا حالا شده بخواید یه کالکشن بسازید که بتونه هر نوع دادهای رو تو خودش نگه داره، از int و bool گرفته تا string و کلاسهای خودتون؟
این کار به لطف پدر همه تایپها در داتنت، یعنی System.Object، ممکنه. اما این قابلیت، یه راز عملکردی مهم به اسم Boxing و Unboxing رو تو دل خودش داره.
در #C، هر نوع دادهای، چه Value Type (مثل int) و چه Reference Type (مثل string)، به صورت پنهان از کلاس System.Object ارثبری میکنه. این یعنی شما میتونید هر متغیری رو به یه متغیر از نوع object تبدیل کنید (Upcast).
مثال (یک Stack همهکاره):
سوال اینجاست: چطور یه int که Value Type هست، میتونه مثل یه object که Reference Type هست رفتار کنه؟ با جادوی Boxing و Unboxing.
• Boxing (بستهبندی):
وقتی شما یه Value Type (مثل int) رو داخل یه متغیر object میریزید، CLR یه "جعبه" روی هیپ (Heap) میسازه، کپی از مقدار شما رو داخل اون میذاره و رفرنس اون جعبه رو به شما میده.
• Unboxing (باز کردن بسته):
وقتی میخواید مقدار رو از اون جعبه در بیارید، عملیات برعکس یعنی Unboxing اتفاق میفته که نیاز به کست صریح داره.
Unboxing
باید به نوع دقیق و اصلی انجام بشه. اگه سعی کنید یه int باکس شده رو به long آنباکس کنید، با خطای InvalidCastException مواجه میشید.
نکته حیاتی: Boxing یه کپی از مقدار شما رو میسازه. این یعنی اگه بعداً متغیر اصلی رو تغییر بدید، مقدار داخل جعبه دستنخورده باقی میمونه!
دونستن مفهوم Boxing و Unboxing برای درک پرفورمنس در #C حیاتیه، چون این عملیاتها هزینه حافظه و پردازش دارن. به همین دلیله که جنریکها (Generics) اختراع شدن تا جلوی این اتفاق رو بگیرن (که بعداً بهش میرسیم).
👑 پدر همه تایپها: object و راز Boxing/Unboxing در #C
تا حالا شده بخواید یه کالکشن بسازید که بتونه هر نوع دادهای رو تو خودش نگه داره، از int و bool گرفته تا string و کلاسهای خودتون؟
این کار به لطف پدر همه تایپها در داتنت، یعنی System.Object، ممکنه. اما این قابلیت، یه راز عملکردی مهم به اسم Boxing و Unboxing رو تو دل خودش داره.
1️⃣ object: جد بزرگ همه!
در #C، هر نوع دادهای، چه Value Type (مثل int) و چه Reference Type (مثل string)، به صورت پنهان از کلاس System.Object ارثبری میکنه. این یعنی شما میتونید هر متغیری رو به یه متغیر از نوع object تبدیل کنید (Upcast).
مثال (یک Stack همهکاره):
public class Stack
{
int position;
object[] data = new object[10];
public void Push(object obj) => data[position++] = obj;
public object Pop() => data[--position];
}
// --- نحوه استفاده ---
var stack = new Stack();
stack.Push("hello");
stack.Push(123); // حتی int!
stack.Push(false); // حتی bool!
// موقع خروج، باید Downcast کنیم
int myNumber = (int)stack.Pop(); // 123
string myString = (string)stack.Pop(); // "hello"
2️⃣ جادوی پشت پرده: Boxing و Unboxing 🎁
سوال اینجاست: چطور یه int که Value Type هست، میتونه مثل یه object که Reference Type هست رفتار کنه؟ با جادوی Boxing و Unboxing.
• Boxing (بستهبندی):
وقتی شما یه Value Type (مثل int) رو داخل یه متغیر object میریزید، CLR یه "جعبه" روی هیپ (Heap) میسازه، کپی از مقدار شما رو داخل اون میذاره و رفرنس اون جعبه رو به شما میده.
• Unboxing (باز کردن بسته):
وقتی میخواید مقدار رو از اون جعبه در بیارید، عملیات برعکس یعنی Unboxing اتفاق میفته که نیاز به کست صریح داره.
int x = 9;
object obj = x; // Boxing: مقدار x در یک جعبه روی هیپ کپی میشود
int y = (int)obj; // Unboxing: مقدار از جعبه کپی شده و به y ریخته میشود
3️⃣ تلهها و نکات مهم ⚠️ تله InvalidCastException:
Unboxing
باید به نوع دقیق و اصلی انجام بشه. اگه سعی کنید یه int باکس شده رو به long آنباکس کنید، با خطای InvalidCastException مواجه میشید.
object obj = 9; // این یک int باکس شده است
// long l = (long)obj; // ❌ InvalidCastException!
تله کپی شدن مقدار:
نکته حیاتی: Boxing یه کپی از مقدار شما رو میسازه. این یعنی اگه بعداً متغیر اصلی رو تغییر بدید، مقدار داخل جعبه دستنخورده باقی میمونه!
int i = 3;
object boxed = i; // یک کپی از 3 در boxed قرار گرفت
i = 5; // تغییر i اصلی، تأثیری روی مقدار باکس شده ندارد
Console.WriteLine(boxed); // خروجی: 3
🤔 حرف حساب و تجربه شما
دونستن مفهوم Boxing و Unboxing برای درک پرفورمنس در #C حیاتیه، چون این عملیاتها هزینه حافظه و پردازش دارن. به همین دلیله که جنریکها (Generics) اختراع شدن تا جلوی این اتفاق رو بگیرن (که بعداً بهش میرسیم).
🔖 هشتگها:
#OOP #MemoryManagement #Boxing
پیادهسازی حذف نرم (Soft Delete) با EF Core 💾
حذف کردن یا نکردن، مسئله این است. 🤔
روش سنتی برای حذف اطلاعات در پایگاه داده، از طریق "حذف سخت" (hard delete) است. یک حذف سخت، یک رکورد را برای همیشه از جدول پایگاه داده پاک میکند. اگرچه این روش ساده به نظر میرسد، اما ریسک قابل توجهی دارد: وقتی دادهای حذف شد، برای همیشه از بین رفته است. 💥
به جای حذف فیزیکی یک رکورد، در "حذف نرم" (soft delete) آن را به عنوان حذف شده علامتگذاری میکنیم، معمولاً با قرار دادن یک فلگ مانند IsDeleted به مقدار true. رکورد در پایگاه داده باقی میماند، اما به طور موثر از کوئریهای عادی برنامه پنهان میشود. 🛡️
امروز، به جزئیات نحوه پیادهسازی حذف نرم با استفاده از EF Core خواهیم پرداخت. در مورد فیلترهای کوئری سراسری بحث خواهیم کرد، روشهای کارآمد برای مدیریت دادههای حذف نرم شده را بررسی میکنیم و مزایا و معایب آن را میسنجیم. 🚀
حذف نرم (Soft Delete) چیست؟ 🧐
حذف نرم یک استراتژی پایداری داده است که از حذف دائمی رکوردها از پایگاه داده شما جلوگیری میکند. به جای حذف داده از پایگاه داده، یک فلگ روی رکورد تنظیم میشود که آن را به عنوان "حذف شده" مشخص میکند.
این رویکرد به برنامه اجازه میدهد تا در کوئریهای عادی این رکوردها را نادیده بگیرد. با این حال، در صورت لزوم میتوانید این رکوردها را بازیابی کنید. حذف نرم همچنین زمانی کاربردی است که بخواهید قیود کلید خارجی (foreign key constraints) را حفظ کنید. حذف نرم یک عملیات "غیرمخرب" است، در مقابل حذف سخت که در آن دادهها به طور کامل حذف میشوند. 🔄
یک حذف سخت از دستور DELETE در SQL استفاده میکند:
DELETE FROM bookings.Reviews
WHERE Id = @BookingId;
از طرف دیگر، یک حذف نرم از دستور UPDATE استفاده میکند:
UPDATE bookings.Reviews
SET IsDeleted = 1, DeletedOnUtc = @UtcNow
WHERE Id = @BookingId;
دادهها هنوز در پایگاه داده وجود دارند و این عملیات قابل بازگشت است.
اما باید به خاطر داشته باشید که هنگام کوئری گرفتن از پایگاه داده، دادههای حذف نرم شده را فیلتر کنید:
SELECT *
FROM bookings.Reviews
WHERE IsDeleted = 0;
بیایید ببینیم چگونه میتوانیم حذف نرم را با EF Core پیادهسازی کنیم. 👨💻
حذف نرم با استفاده از رهگیرها (Interceptors) در EF Core ⚙️
رهگیرهای EF Core یک مکانیزم قدرتمند برای رهگیری و تغییر عملیات پایگاه داده فراهم میکنند. به عنوان مثال، میتوانید عملیات ذخیره تغییرات (saving changes) را برای پیادهسازی قابلیت حذف نرم رهگیری کنید.
بیایید یک اینترفیس نشانگر ISoftDeletable برای نمایش موجودیتهای قابل حذف نرم ایجاد کنیم:
public interface ISoftDeletable
{
bool IsDeleted { get; set; }
DateTime? DeletedOnUtc { get; set; }
}
موجودیتهایی که باید از حذف نرم پشتیبانی کنند، این اینترفیس را پیادهسازی خواهند کرد. شما باید مایگریشن مربوطه را برای ایجاد این ستونها در پایگاه داده اعمال کنید.
مولفه بعدی که نیاز داریم یک SaveChangesInterceptor است که به ما اجازه میدهد به متد SavingChangesAsync (یا SavingChanges) متصل شویم. ما میتوانیم به ChangeTracker دسترسی پیدا کنیم و به دنبال ورودیهایی بگردیم که ISoftDeletable را پیادهسازی کرده و برای حذف شدن علامتگذاری شدهاند. این موضوع را میتوانیم با بررسی اینکه وضعیت موجودیت (entity state) برابر با EntityState.Deleted است، تشخیص دهیم.
وقتی موجودیتهایی را که برای حذف علامتگذاری شدهاند پیدا کردیم، در یک حلقه آنها را پیمایش کرده و وضعیتشان را به EntityState.Modified تغییر میدهیم. همچنین باید مقادیر مربوط به پراپرتیهای IsDeleted و DeletedOnUtc را تنظیم کنید. این کار باعث میشود EF به جای عملیات DELETE، یک عملیات UPDATE ایجاد کند.
این رویکرد تضمین میکند که تمام عملیات حذف در سراسر برنامه از سیاست حذف نرم پیروی میکنند.
شما باید SoftDeleteInterceptor را با تزریق وابستگی (dependency injection) ثبت کرده و آن را با ApplicationDbContext پیکربندی کنید.
برای اطمینان از اینکه رکوردهای حذف نرم شده به طور خودکار از کوئریها حذف میشوند، میتوانیم از فیلترهای کوئری سراسری (global query filters) در EF Core استفاده کنیم. ما میتوانیم فیلترهای کوئری را با استفاده از متد OnModelCreating روی موجودیتها اعمال کنیم تا رکوردهای علامتگذاری شده به عنوان حذف شده، به طور خودکار مستثنی شوند. این ویژگی نوشتن کوئریها را به شدت ساده میکند.
در اینجا نحوه پیکربندی فیلتر کوئری حذف نرم آمده است:
یک محدودیت این است که شما نمیتوانید بیش از یک فیلتر کوئری برای هر موجودیت پیکربندی کنید.
با این حال، گاهی اوقات لازم است که رکوردهای حذف نرم شده را به صراحت در کوئریها لحاظ کنید. شما میتوانید این کار را با استفاده از متد IgnoreQueryFilters انجام دهید.
برای بهبود عملکرد کوئری، به ویژه در جداولی که تعداد زیادی رکورد حذف نرم شده دارند، میتوانید از ایندکسهای فیلتر شده (filtered indexes) استفاده کنید. یک ایندکس فیلتر شده فقط شامل رکوردهایی است که معیارهای مشخص شده را برآورده میکنند. این کار اندازه ایندکس را کاهش داده و زمان اجرای کوئری را برای عملیاتی که رکوردهای فیلتر شده را مستثنی میکنند، بهبود میبخشد. اکثر پایگاه دادههای محبوب از ایندکسهای فیلتر شده پشتیبانی میکنند.
در اینجا نحوه پیکربندی یک ایندکس فیلتر شده با EF Core آمده است:
public sealed class SoftDeleteInterceptor : SaveChangesInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
if (eventData.Context is null)
{
return base.SavingChangesAsync(
eventData, result, cancellationToken);
}
IEnumerable<EntityEntry<ISoftDeletable>> entries =
eventData
.Context
.ChangeTracker
.Entries<ISoftDeletable>()
.Where(e => e.State == EntityState.Deleted);
foreach (EntityEntry<ISoftDeletable> softDeletable in entries)
{
softDeletable.State = EntityState.Modified;
softDeletable.Entity.IsDeleted = true;
softDeletable.Entity.DeletedOnUtc = DateTime.UtcNow;
}
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
}
این رویکرد تضمین میکند که تمام عملیات حذف در سراسر برنامه از سیاست حذف نرم پیروی میکنند.
شما باید SoftDeleteInterceptor را با تزریق وابستگی (dependency injection) ثبت کرده و آن را با ApplicationDbContext پیکربندی کنید.
services.AddSingleton<SoftDeleteInterceptor>();
services.AddDbContext<ApplicationDbContext>(
(sp, options) => options
.UseSqlServer(connectionString)
.AddInterceptors(
sp.GetRequiredService<SoftDeleteInterceptor>()));
فیلتر کردن خودکار دادههای حذف نرم شده 🔍
برای اطمینان از اینکه رکوردهای حذف نرم شده به طور خودکار از کوئریها حذف میشوند، میتوانیم از فیلترهای کوئری سراسری (global query filters) در EF Core استفاده کنیم. ما میتوانیم فیلترهای کوئری را با استفاده از متد OnModelCreating روی موجودیتها اعمال کنیم تا رکوردهای علامتگذاری شده به عنوان حذف شده، به طور خودکار مستثنی شوند. این ویژگی نوشتن کوئریها را به شدت ساده میکند.
در اینجا نحوه پیکربندی فیلتر کوئری حذف نرم آمده است:
public sealed class ApplicationDbContext(
DbContextOptions<UsersDbContext> options) : DbContext(options)
{
public DbSet<Review> Reviews { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Review>().HasQueryFilter(r => !r.IsDeleted);
}
}
یک محدودیت این است که شما نمیتوانید بیش از یک فیلتر کوئری برای هر موجودیت پیکربندی کنید.
با این حال، گاهی اوقات لازم است که رکوردهای حذف نرم شده را به صراحت در کوئریها لحاظ کنید. شما میتوانید این کار را با استفاده از متد IgnoreQueryFilters انجام دهید.
dbContex.Reviews
.IgnoreQueryFilters()
.Where(r => r.ApartmentId == apartmentId)
.ToList();
کوئریهای سریعتر با استفاده از ایندکس فیلتر شده (Filtered Index) 🚀
برای بهبود عملکرد کوئری، به ویژه در جداولی که تعداد زیادی رکورد حذف نرم شده دارند، میتوانید از ایندکسهای فیلتر شده (filtered indexes) استفاده کنید. یک ایندکس فیلتر شده فقط شامل رکوردهایی است که معیارهای مشخص شده را برآورده میکنند. این کار اندازه ایندکس را کاهش داده و زمان اجرای کوئری را برای عملیاتی که رکوردهای فیلتر شده را مستثنی میکنند، بهبود میبخشد. اکثر پایگاه دادههای محبوب از ایندکسهای فیلتر شده پشتیبانی میکنند.
در اینجا نحوه پیکربندی یک ایندکس فیلتر شده با EF Core آمده است:
public sealed class ApplicationDbContext(
DbContextOptions<UsersDbContext> options) : DbContext(options)
{
public DbSet<Review> Reviews { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Review>().HasQueryFilter(r => !r.IsDeleted);
modelBuilder.Entity<Review>()
.HasIndex(r => r.IsDeleted)
.HasFilter("IsDeleted = 0");
}
}
متد HasFilter فیلتر SQL را برای رکوردهایی که در ایندکس قرار خواهند گرفت، میپذیرد.
شما همچنین میتوانید یک ایندکس فیلتر شده را با استفاده از SQL ایجاد کنید:
شما میتوانید از طریق مستندات، اطلاعات بیشتری در مورد ایندکسهای فیلتر شده کسب کنید.
ارزشش را دارد که فکر کنید آیا اصلاً به حذف نرم رکوردها نیاز دارید یا خیر.
در سیستمهای سازمانی (enterprise)، شما معمولاً به "حذف" داده فکر نمیکنید. مفاهیم تجاری وجود دارند که شامل حذف داده نمیشوند. چند مثال عبارتند از: لغو یک سفارش، بازپرداخت یک پرداخت، یا باطل کردن یک فاکتور. این عملیاتهای "مخرب" سیستم را به حالت قبلی بازمیگردانند. اما از دیدگاه تجاری، شما واقعاً در حال حذف داده نیستید. 💼
حذف نرم در صورتی مفید است که خطر حذف تصادفی وجود داشته باشد. این قابلیت به شما امکان میدهد رکوردهای حذف نرم شده را به راحتی بازیابی کنید.
در هر صورت، در نظر بگیرید که آیا حذف نرم از دیدگاه تجاری منطقی است یا خیر.
حذف نرم یک شبکه ایمنی ارزشمند برای بازیابی اطلاعات ارائه میدهد و میتواند ردیابی دادههای تاریخی را بهبود بخشد.
با این حال، ارزیابی اینکه آیا این روش واقعاً با نیازمندیهای خاص برنامه شما همخوانی دارد، بسیار مهم است. عواملی مانند اهمیت بازیابی دادههای حذف شده، نیازهای ممیزی (auditing) و مقررات صنعت خود را در نظر بگیرید. ایجاد یک ایندکس فیلتر شده میتواند عملکرد کوئری را در جداول دارای رکوردهای حذف نرم شده بهبود بخشد.
اگر تصمیم گرفتید که حذف نرم برای شما مناسب است، EF Core ابزارهای لازم را برای یک پیادهسازی روان و ساده فراهم میکند.
شما همچنین میتوانید یک ایندکس فیلتر شده را با استفاده از SQL ایجاد کنید:
CREATE INDEX IX_Reviews_IsDeleted
ON bookings.Reviews (IsDeleted)
WHERE IsDeleted = 0;
شما میتوانید از طریق مستندات، اطلاعات بیشتری در مورد ایندکسهای فیلتر شده کسب کنید.
آیا واقعاً به حذف نرم نیاز دارید؟ 🤔
ارزشش را دارد که فکر کنید آیا اصلاً به حذف نرم رکوردها نیاز دارید یا خیر.
در سیستمهای سازمانی (enterprise)، شما معمولاً به "حذف" داده فکر نمیکنید. مفاهیم تجاری وجود دارند که شامل حذف داده نمیشوند. چند مثال عبارتند از: لغو یک سفارش، بازپرداخت یک پرداخت، یا باطل کردن یک فاکتور. این عملیاتهای "مخرب" سیستم را به حالت قبلی بازمیگردانند. اما از دیدگاه تجاری، شما واقعاً در حال حذف داده نیستید. 💼
حذف نرم در صورتی مفید است که خطر حذف تصادفی وجود داشته باشد. این قابلیت به شما امکان میدهد رکوردهای حذف نرم شده را به راحتی بازیابی کنید.
در هر صورت، در نظر بگیرید که آیا حذف نرم از دیدگاه تجاری منطقی است یا خیر.
نکات کلیدی (Takeaway) 📌
حذف نرم یک شبکه ایمنی ارزشمند برای بازیابی اطلاعات ارائه میدهد و میتواند ردیابی دادههای تاریخی را بهبود بخشد.
با این حال، ارزیابی اینکه آیا این روش واقعاً با نیازمندیهای خاص برنامه شما همخوانی دارد، بسیار مهم است. عواملی مانند اهمیت بازیابی دادههای حذف شده، نیازهای ممیزی (auditing) و مقررات صنعت خود را در نظر بگیرید. ایجاد یک ایندکس فیلتر شده میتواند عملکرد کوئری را در جداول دارای رکوردهای حذف نرم شده بهبود بخشد.
اگر تصمیم گرفتید که حذف نرم برای شما مناسب است، EF Core ابزارهای لازم را برای یک پیادهسازی روان و ساده فراهم میکند.
🔖 هشتگها:
#EntityFrameworkCore #EFCore #SoftDelete #Database #DataPersistence #SQL
📖 سری آموزشی کتاب C# 12 in a Nutshell
چطور #C جلوی خطاهای مربوط به نوع داده رو میگیره؟ این زبان از یه سیستم امنیتی دو لایه استفاده میکنه: بررسی استاتیک در زمان کامپایل و بررسی در زمان اجرا.
امروز میخوایم با این دو لایه امنیتی و ابزارهای بازرسیشون آشنا بشیم.
این اولین خط دفاعی شماست. کامپایلر #C قبل از اینکه حتی برنامه رو اجرا کنید، کد شما رو بررسی میکنه تا مطمئن بشه که شما نوعهای داده رو به درستی استفاده کردید. این کار جلوی یه عالمه باگ رو در نطفه خفه میکنه.
بعضی وقتا کامپایلر نمیتونه از همه چیز مطمئن باشه (مثلاً موقع Downcasting). اینجا CLR (محیط اجرای داتنت) وارد عمل میشه.
هر آبجکتی که روی هیپ ساخته میشه، یه "توکن نوع" با خودش داره. CLR در زمان اجرا، از این توکن برای چک کردن صحت عملیات کستینگ استفاده میکنه.
برای اینکه خودمون اطلاعات یه نوع رو بدست بیاریم، دو تا ابزار اصلی داریم:
🔹️ GetType() (برای زمان اجرا):
این یه متده که روی یک نمونه (instance) از آبجکت صدا زده میشه و نوع دقیق اون آبجکت رو در زمان اجرا به ما میده.
🔹️ typeof (برای زمان کامپایل):
این یه اپراتوره که اسم یه نوع (Type) رو میگیره و آبجکت System.Type مربوط به اون رو در زمان کامپایل مشخص میکنه.
این سیستم بررسی نوع دو مرحلهای، یکی از دلایل اصلی قدرت و امنیت زبان #C هست.
🛡 بازرسی هویت در #C :
Type Checking در زمان کامپایل و اجرا
چطور #C جلوی خطاهای مربوط به نوع داده رو میگیره؟ این زبان از یه سیستم امنیتی دو لایه استفاده میکنه: بررسی استاتیک در زمان کامپایل و بررسی در زمان اجرا.
امروز میخوایم با این دو لایه امنیتی و ابزارهای بازرسیشون آشنا بشیم.
1️⃣ بررسی استاتیک (Static Type Checking): نگهبان زمان کامپایل 👮♂️
این اولین خط دفاعی شماست. کامپایلر #C قبل از اینکه حتی برنامه رو اجرا کنید، کد شما رو بررسی میکنه تا مطمئن بشه که شما نوعهای داده رو به درستی استفاده کردید. این کار جلوی یه عالمه باگ رو در نطفه خفه میکنه.
// کامپایلر اینجا جلوی شما رو میگیره و اجازه نمیده برنامه ساخته بشه
// چون نمیتونید یه رشته رو تو یه متغیر int بریزید.
int x = "5"; // ❌ خطای زمان کامپایل!
2️⃣ بررسی در زمان اجرا (Runtime Type Checking): نگهبان CLR 🏃♂️
بعضی وقتا کامپایلر نمیتونه از همه چیز مطمئن باشه (مثلاً موقع Downcasting). اینجا CLR (محیط اجرای داتنت) وارد عمل میشه.
هر آبجکتی که روی هیپ ساخته میشه، یه "توکن نوع" با خودش داره. CLR در زمان اجرا، از این توکن برای چک کردن صحت عملیات کستینگ استفاده میکنه.
object y = "5";
// در زمان اجرا، CLR میبینه که آبجکت داخل y یک string است، نه int
// و یک خطای InvalidCastException پرتاب میکنه.
int z = (int)y; // 💥 خطای زمان اجرا!
3️⃣ ابزارهای بازرسی: GetType در برابر typeof 🔎
برای اینکه خودمون اطلاعات یه نوع رو بدست بیاریم، دو تا ابزار اصلی داریم:
🔹️ GetType() (برای زمان اجرا):
این یه متده که روی یک نمونه (instance) از آبجکت صدا زده میشه و نوع دقیق اون آبجکت رو در زمان اجرا به ما میده.
🔹️ typeof (برای زمان کامپایل):
این یه اپراتوره که اسم یه نوع (Type) رو میگیره و آبجکت System.Type مربوط به اون رو در زمان کامپایل مشخص میکنه.
public class Point { public int X, Y; }
// ...
Point p = new Point();
// GetType روی نمونه کار میکنه
Console.WriteLine(p.GetType().Name); // خروجی: Point
// typeof روی خودِ نوع کار میکنه
Console.WriteLine(typeof(Point).Name); // خروجی: Point
// میتونیم نتایج رو با هم مقایسه کنیم
Console.WriteLine(p.GetType() == typeof(Point)); // خروجی: Trueاین سیستم بررسی نوع دو مرحلهای، یکی از دلایل اصلی قدرت و امنیت زبان #C هست.
🔖 هشتگها:
#DotNet #OOP #BestPractices #TypeSystem
📖 سری آموزشی کتاب C# 12 in a Nutshell
تا حالا شده یه آبجکت از کلاس خودتون رو Console.WriteLine کنید و با یه خروجی بیمعنی مثل MyProject.Panda مواجه بشید؟ 😑
راه حل این مشکل، بازنویسی (override) کردن یکی از مهمترین متدهای کلاس object یعنی ToStringهست.
ToString()
یه متد virtual در کلاس System.Object هست. وظیفهش اینه که یه نمایش متنی و خوانا از آبجکت شما برگردونه. این متد به خصوص موقع دیباگ کردن و لاگ انداختن فوقالعاده به درد میخوره، چون به شما اجازه میده وضعیت داخلی آبجکت رو به راحتی ببینید.
کافیه تو کلاس خودتون، متد ToString رو override کنید و هر رشتهای که دوست دارید و وضعیت آبجکت رو بهتر توصیف میکنه، برگردونید.
اگه
یه نکته خیلی مهم در مورد پرفورمنس: وقتی ToString رو مستقیماً روی یه Value Type (مثل int) صدا میزنید، هیچ عمل Boxing اتفاق نمیفته. Boxing (که هزینه پرفورمنس داره) فقط زمانی رخ میده که شما اول اون Value Type رو به object کست کنید.
عادت کردن به override کردن ToString در تمام کلاسهاتون، یکی از بهترین کارهاییه که میتونید برای خودتون (و همتیمیهاتون در آینده) انجام بدید.
🖨 چاپ زیبا با ()ToString: معرفی کردن آبجکتهای شما به دنیا
تا حالا شده یه آبجکت از کلاس خودتون رو Console.WriteLine کنید و با یه خروجی بیمعنی مثل MyProject.Panda مواجه بشید؟ 😑
راه حل این مشکل، بازنویسی (override) کردن یکی از مهمترین متدهای کلاس object یعنی ToStringهست.
1️⃣ ToString() چیست و چرا مهمه؟
ToString()
یه متد virtual در کلاس System.Object هست. وظیفهش اینه که یه نمایش متنی و خوانا از آبجکت شما برگردونه. این متد به خصوص موقع دیباگ کردن و لاگ انداختن فوقالعاده به درد میخوره، چون به شما اجازه میده وضعیت داخلی آبجکت رو به راحتی ببینید.
2️⃣ چطور ToString رو بازنویسی (Override) کنیم؟
کافیه تو کلاس خودتون، متد ToString رو override کنید و هر رشتهای که دوست دارید و وضعیت آبجکت رو بهتر توصیف میکنه، برگردونید.
public class Panda
{
public string Name;
// بازنویسی متد ToString
public override string ToString() => Name;
}
// --- نحوه استفاده ---
Panda p = new Panda { Name = "Petey" };
// Console.WriteLine به صورت خودکار ToString() رو روی آبجکتها صدا میزنه
Console.WriteLine(p);
// خروجی:
// Petey
اگه
ToString رو بازنویسی نکنید، خروجی پیشفرض، اسم کامل تایپ خواهد بود.3️⃣ نکته فنی: ToString و تلهی Boxing ⚠️
یه نکته خیلی مهم در مورد پرفورمنس: وقتی ToString رو مستقیماً روی یه Value Type (مثل int) صدا میزنید، هیچ عمل Boxing اتفاق نمیفته. Boxing (که هزینه پرفورمنس داره) فقط زمانی رخ میده که شما اول اون Value Type رو به object کست کنید.
int x = 1;
// ✅ بدون Boxing: بهینه و سریع
string s1 = x.ToString();
// ❌ با Boxing: هزینه پرفورمنس داره
object box = x;
string s2 = box.ToString();
🤔 حرف حساب و تجربه شما
عادت کردن به override کردن ToString در تمام کلاسهاتون، یکی از بهترین کارهاییه که میتونید برای خودتون (و همتیمیهاتون در آینده) انجام بدید.
🔖 هشتگها:
#DotNet #OOP #ToString
Forwarded from thisisnabi.dev [Farsi]
به نظر من، بهینه سازی اینجوریه که هرچقدر شما جلوتر میرید باید زمان بیشتری صرف تغییرات کمتر کنید.
به زبان ساده میشه گفت همون قانون ۸۰/۲۰ یا پارتو اینجا صدق میکنه؛ یعنی معمولاً ۸۰ درصد نتیجه رو توی ۲۰ درصد تلاش اولیه میشه گرفت. اما وقتی جلوتر میری، برای همون چند درصد باقیمونده باید کلی زمان و انرژی بیشتری بذاری تا تغییر کوچیکی اتفاق بیفته.
به زبان ساده میشه گفت همون قانون ۸۰/۲۰ یا پارتو اینجا صدق میکنه؛ یعنی معمولاً ۸۰ درصد نتیجه رو توی ۲۰ درصد تلاش اولیه میشه گرفت. اما وقتی جلوتر میری، برای همون چند درصد باقیمونده باید کلی زمان و انرژی بیشتری بذاری تا تغییر کوچیکی اتفاق بیفته.
بررسیهای سلامت (Health Checks) در ASP.NET Core برای مانیتورینگ اپلیکیشنهای شما 🩺
همه ما میخواهیم اپلیکیشنهای قوی و قابل اعتماد بسازیم که بتوانند به طور نامحدود مقیاسپذیر باشند و هر تعداد درخواستی را مدیریت کنند.
اما با افزایش پیچیدگی سیستمهای توزیعشده و معماریهای میکروسرویس، مانیتور کردن سلامت اپلیکیشنهای ما به طور فزایندهای دشوارتر میشود.
حیاتی است که شما سیستمی برای دریافت بازخورد سریع از سلامت اپلیکیشن خود داشته باشید.
اینجاست که health checks وارد میشوند.
بررسیهای سلامت راهی برای مانیتور و تأیید سلامت کامپوننتهای مختلف یک اپلیکیشن فراهم میکنند، از جمله:
🔹 دیتابیسها
🔹 APIها
🔹 کشها
🔹 سرویسهای خارجی
در این مقاله به شما نشان خواهم داد:
🔹 Health checks چه هستند
🔹 افزودن یک health check سفارشی
🔹 استفاده از کتابخانههای health check موجود
🔹 سفارشیسازی فرمت پاسخ health checks
بیایید ببینیم چگونه health checks را در ASP.NET Core پیادهسازی کنیم.
Health Checks چه هستند؟ 🤔
یک مکانیزم پیشگیرانه برای مانیتورینگ و تأیید سلامت و در دسترس بودن یک اپلیکیشن در ASP.NET Core هستند.
در اینجا پیکربندی اولیه آمده است که سرویسهای health check را ثبت کرده و HealthCheckMiddleware را برای پاسخگویی در URL مشخص شده اضافه میکند.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();
var app = builder.Build();
app.MapHealthChecks("/health");
app.Run();
health check
یک مقدار HealthStatus را برمیگرداند که سلامت سرویس را نشان میدهد.
سه مقدار متمایز HealthStatus وجود دارد:
🟢 HealthStatus.Healthy
🟡 HealthStatus.Degraded
🔴 HealthStatus.Unhealthy
شما میتوانید از HealthStatus برای نشان دادن حالتهای مختلف اپلیکیشن خود استفاده کنید.
برای مثال، اگر اپلیکیشن کندتر از حد انتظار عمل میکند، میتوانید HealthStatus.Degraded را برگردانید.
افزودن Health Checks سفارشی 👨🔧
شما میتوانید health checks سفارشی را با پیادهسازی اینترفیس IHealthCheck ایجاد کنید.
برای مثال، میتوانید یک چک برای بررسی در دسترس بودن دیتابیس SQL خود پیادهسازی کنید.
مهم است که از یک کوئری استفاده کنید که بتواند به سرعت در دیتابیس کامل شود، مانند SELECT 1.
در اینجا یک مثال از پیادهسازی health check سفارشی در کلاس SqlHealthCheck آمده است:
public class SqlHealthCheck : IHealthCheck
{
private readonly string _connectionString;
public SqlHealthCheck(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("Database");
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
using var sqlConnection = new SqlConnection(_connectionString);
await sqlConnection.OpenAsync(cancellationToken);
using var command = sqlConnection.CreateCommand();
command.CommandText = "SELECT 1";
await command.ExecuteScalarAsync(cancellationToken);
return HealthCheckResult.Healthy();
}
catch(Exception ex)
{
return HealthCheckResult.Unhealthy(
context.Registration.FailureStatus,
exception: ex);
}
}
}
پس از پیادهسازی health check سفارشی، باید آن را ثبت کنید.
فراخوانی قبلی به AddHealthChecks اکنون به این شکل میشود:
builder.Services.AddHealthChecks()
.AddCheck<SqlHealthCheck>("custom-sql", HealthStatus.Unhealthy);
ما به آن یک نام سفارشی میدهیم و مشخص میکنیم که کدام وضعیت به عنوان نتیجه شکست در
HealthCheckContext.Registration.FailureStatus
استفاده شود.
اما یک لحظه بایستید و فکر کنید.
آیا میخواهید برای هر سرویس خارجی که دارید، یک health check سفارشی را خودتان پیادهسازی کنید؟
البته که نه! یک راه حل بهتر وجود دارد.
استفاده از کتابخانههای Health Check موجود 📚
قبل از اینکه برای همه چیز یک health check سفارشی پیادهسازی کنید، ابتدا باید ببینید آیا از قبل کتابخانه موجودی وجود دارد یا نه.
در ریپازیتوری AspNetCore.Diagnostics.HealthChecks میتوانید مجموعه وسیعی از پکیجهای health check برای سرویسها و کتابخانههای پرکاربرد را پیدا کنید.
در اینجا فقط چند مثال آمده است:
SQL Server -
AspNetCore.HealthChecks.SqlServer
Postgres -
AspNetCore.HealthChecks.Npgsql
Redis -
AspNetCore.HealthChecks.Redis
RabbitMQ -
AspNetCore.HealthChecks.RabbitMQ
در اینجا نحوه افزودن health checks برای PostgreSQL و RabbitMQ آمده است:
builder.Services.AddHealthChecks()
.AddCheck<SqlHealthCheck>("custom-sql", HealthStatus.Unhealthy);
.AddNpgSql(pgConnectionString)
.AddRabbitMQ(rabbitConnectionString)
فرمتدهی پاسخ Health Checks 🎨
بهطور پیشفرض، endpointی که وضعیت health check شما را برمیگرداند، یک مقدار رشتهای را که نماینده یک HealthStatus است، برمیگرداند.
این عملی نیست اگر چندین health check پیکربندی کرده باشید، زیرا میخواهید وضعیت سلامت را به صورت جداگانه برای هر سرویس مشاهده کنید.
بدتر از آن، اگر یکی از سرویسها در حال شکست باشد، کل پاسخ Unhealthy برمیگرداند و شما نمیدانید چه چیزی باعث مشکل شده است.
شما میتوانید این مشکل را با ارائه یک ResponseWriter حل کنید، و یک نمونه موجود در کتابخانه AspNetCore.HealthChecks.UI.Client وجود دارد.
پکیج NuGet را نصب کنید:
Install-Package AspNetCore.HealthChecks.UI.Client
و باید فراخوانی به MapHealthChecks را کمی بهروز کنید تا از ResponseWriter این کتابخانه استفاده کند:
app.MapHealthChecks(
"/health",
new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
پس از این تغییرات، پاسخ از endpoint health check به این شکل خواهد بود:
{
"status": "Unhealthy",
"totalDuration": "00:00:00.3285211",
"entries": {
"npgsql": { "status": "Healthy", ... },
"rabbitmq": { "status": "Healthy", ... },
"custom-sql": { "status": "Unhealthy", ... }
}
}نکات پایانی 📝
مانیتورینگ اپلیکیشن برای ردیابی در دسترس بودن، استفاده از منابع و تغییرات عملکرد در اپلیکیشن شما مهم است.
مانیتور کردن سلامت اپلیکیشنهای ASP.NET Core شما با ارائه health checks برای سرویسهایتان آسان است.
شما میتوانید تصمیم بگیرید health checks سفارشی را پیادهسازی کنید، اما ابتدا در نظر بگیرید که آیا راهحلهای موجود وجود دارد یا نه.
📖 سری آموزشی کتاب C# 12 in a Nutshell
در #C، ما دو نوع اصلی برای ساختن تایپهای خودمون داریم: class و struct. اکثر ما به صورت پیشفرض از class استفاده میکنیم، اما دونستن اینکه struct چیه و کی باید ازش استفاده کنیم، یه مهارت کلیدیه که کد شما رو بهینهتر و خواناتر میکنه.
دو تا تفاوت بنیادی وجود داره که همه چیز از اونها نشأت میگیره:
این مهمترین تفاوته. struct یک Value Type هست؛ یعنی وقتی اون رو به یه متغیر دیگه یا یه متد پاس میدید، کل آبجکت کپی میشه. class یک Reference Type هست؛ یعنی فقط رفرنس (آدرس حافظه) اون کپی میشه.
استراکت ها از وراثت پشتیبانی نمیکنن. شما نمیتونید یه struct بسازید که از یه struct یا class دیگه ارثبری کنه (البته همهشون به صورت پنهان از System.ValueType ارث میبرن). به همین دلیل، اعضای struct نمیتونن virtual یا abstract باشن.
قانون کلی اینه: وقتی تایپ شما بیشتر شبیه به یک "مقدار" ساده هست تا یک "موجودیت" پیچیده با هویت خاص، struct انتخاب خوبیه.
از این چکلیست استفاده کنید:
✅ برای تایپهای کوچک و داده-محور: مثل Point (نقطه)، Color (رنگ)، یا یک زوج مرتب KeyValuePair.
✅ وقتی تغییرناپذیری (Immutability) مهمه: چون structها کپی میشن، تغییر یک نمونه روی بقیه تأثیر نمیذاره و این باعث امنیت بیشتر کد میشه.
✅ وقتی پرفورمنس خیلی مهمه: ساختن structها (مخصوصاً در آرایههای بزرگ) هزینه حافظه کمتری داره چون سربار آبجکتهای روی هیپ رو ندارن و باعث کاهش فشار روی Garbage Collector میشن.
برای اینکه structهای خودتون رو امنتر و واقعاً تغییرناپذیر کنید، از 8 #C به بعد میتونید از کلمه کلیدی readonly قبل از تعریف struct استفاده کنید. این به کامپایلر میگه که تمام فیلدهای این struct باید readonly باشن.
این کار هم "نیت" شما رو برای ساختن یک تایپ تغییرناپذیر نشون میده و هم به کامپایلر اجازه بهینهسازیهای بیشتری رو میده.
استراکت ها ابزارهای قدرتمندی برای بهینهسازی و نوشتن کدهای خوانا هستن، به شرطی که درست و به جا ازشون استفاده بشه.
🧱 راهنمای کامل struct در #C: کی و چرا از آن استفاده کنیم؟
در #C، ما دو نوع اصلی برای ساختن تایپهای خودمون داریم: class و struct. اکثر ما به صورت پیشفرض از class استفاده میکنیم، اما دونستن اینکه struct چیه و کی باید ازش استفاده کنیم، یه مهارت کلیدیه که کد شما رو بهینهتر و خواناتر میکنه.
1️⃣ struct در برابر class: دوئل اصلی
دو تا تفاوت بنیادی وجود داره که همه چیز از اونها نشأت میگیره:
Value Type در برابر Reference Type: 📜
این مهمترین تفاوته. struct یک Value Type هست؛ یعنی وقتی اون رو به یه متغیر دیگه یا یه متد پاس میدید، کل آبجکت کپی میشه. class یک Reference Type هست؛ یعنی فقط رفرنس (آدرس حافظه) اون کپی میشه.
عدم پشتیبانی از وراثت: 🚫
استراکت ها از وراثت پشتیبانی نمیکنن. شما نمیتونید یه struct بسازید که از یه struct یا class دیگه ارثبری کنه (البته همهشون به صورت پنهان از System.ValueType ارث میبرن). به همین دلیل، اعضای struct نمیتونن virtual یا abstract باشن.
2️⃣ چه زمانی باید از struct استفاده کنیم؟
قانون کلی اینه: وقتی تایپ شما بیشتر شبیه به یک "مقدار" ساده هست تا یک "موجودیت" پیچیده با هویت خاص، struct انتخاب خوبیه.
از این چکلیست استفاده کنید:
✅ برای تایپهای کوچک و داده-محور: مثل Point (نقطه)، Color (رنگ)، یا یک زوج مرتب KeyValuePair.
✅ وقتی تغییرناپذیری (Immutability) مهمه: چون structها کپی میشن، تغییر یک نمونه روی بقیه تأثیر نمیذاره و این باعث امنیت بیشتر کد میشه.
✅ وقتی پرفورمنس خیلی مهمه: ساختن structها (مخصوصاً در آرایههای بزرگ) هزینه حافظه کمتری داره چون سربار آبجکتهای روی هیپ رو ندارن و باعث کاهش فشار روی Garbage Collector میشن.
3️⃣ readonly struct: بهترین شیوه مدرن 🛡
برای اینکه structهای خودتون رو امنتر و واقعاً تغییرناپذیر کنید، از 8 #C به بعد میتونید از کلمه کلیدی readonly قبل از تعریف struct استفاده کنید. این به کامپایلر میگه که تمام فیلدهای این struct باید readonly باشن.
این کار هم "نیت" شما رو برای ساختن یک تایپ تغییرناپذیر نشون میده و هم به کامپایلر اجازه بهینهسازیهای بیشتری رو میده.
public readonly struct Point
{
// تمام فیلدها باید readonly باشن
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
// متدهایی که وضعیت رو تغییر نمیدن رو هم میتونید readonly مشخص کنید
public readonly double DistanceFromOrigin()
{
return Math.Sqrt(X * X + Y * Y);
}
}
🤔 حرف حساب و تجربه شما
استراکت ها ابزارهای قدرتمندی برای بهینهسازی و نوشتن کدهای خوانا هستن، به شرطی که درست و به جا ازشون استفاده بشه.
🔖 هشتگها:
#OOP #Struct #Performance
📖 سری آموزشی کتاب C# 12 in a Nutshell
تو پست قبلی، با اصول اولیه و کاربردی structها آشنا شدیم. امروز وقتشه که کلاه غواصی رو سرمون کنیم و به دو تا از عمیقترین و تخصصیترین مباحث مربوط به structها شیرجه بزنیم: رفتار عجیب سازندهها و قابلیت ref struct.
این یکی از گیجکنندهترین بخشهای کار با structهاست. یک struct همیشه یک سازنده پیشفرض بدون پارامتر داره که تمام فیلدها رو صفر میکنه (همون default).
حالا اگه شما خودتون یه سازنده بدون پارامتر بنویسید (که از 10 #C به بعد ممکنه)، اون سازنده پیشفرض حذف نمیشه و هنوز از راههای دیگهای مثل ساختن آرایه، قابل دسترسه!
این کد رو ببینید تا کامل متوجه بشید:
توصیه حرفهای: بهترین کار اینه که
این یه قابلیت خیلی خاص و پیشرفته برای بهینهسازیهای سطح پایینه. یه ref struct، نوعی از struct هست که کامپایلر تضمین میکنه فقط و فقط روی Stack زندگی کنه و هرگز به Heap منتقل نشه.
چرا این خوبه؟ چون به ما اجازه میده با حافظه Stack به صورت خیلی بهینه کار کنیم و از فشار روی Garbage Collector کم کنیم، مثل کاری که <Span<T انجام میده.
محدودیتهای ref struct:
چون ref struct هرگز نباید روی هیپ قرار بگیره، محدودیتهای زیر رو داره:
🚫 نمیتونه عضو یک class باشه.
🚫 نمیتونه عنصر یک آرایه باشه.
🚫 نمیتونه Boxed بشه (به object تبدیل بشه).
🚫 نمیتونه اینترفیس پیادهسازی کنه.
🚫 نمیتونه در متدهای async استفاده بشه.
🤔 حرف حساب و تجربه شما
این دو مفهوم، نهایت عمق و قدرت structها در #C رو نشون میدن.
🚀 شیرجه عمیق در structها: سازندههای گیجکننده و ref struct
تو پست قبلی، با اصول اولیه و کاربردی structها آشنا شدیم. امروز وقتشه که کلاه غواصی رو سرمون کنیم و به دو تا از عمیقترین و تخصصیترین مباحث مربوط به structها شیرجه بزنیم: رفتار عجیب سازندهها و قابلیت ref struct.
1️⃣ تلهی سازندهها: دوگانگی new() و default 🤯
این یکی از گیجکنندهترین بخشهای کار با structهاست. یک struct همیشه یک سازنده پیشفرض بدون پارامتر داره که تمام فیلدها رو صفر میکنه (همون default).
حالا اگه شما خودتون یه سازنده بدون پارامتر بنویسید (که از 10 #C به بعد ممکنه)، اون سازنده پیشفرض حذف نمیشه و هنوز از راههای دیگهای مثل ساختن آرایه، قابل دسترسه!
این کد رو ببینید تا کامل متوجه بشید:
struct Point
{
int x = 1;
int y;
// سازنده سفارشی بدون پارامتر
public Point() => y = 1;
}
// --- نتایج عجیب ---
// سازنده صریح و سفارشی ما صدا زده میشه
Point p1 = new Point();
Console.WriteLine($"p1: ({p1.x}, {p1.y})");
// خروجی: p1: (1, 1)
// سازنده پیشفرضِ صفرکننده صدا زده میشه
Point p2 = default;
Console.WriteLine($"p2: ({p2.x}, {p2.y})");
// خروجی: p2: (0, 0)
// آرایهها هم از سازنده پیشفرض و صفرکننده استفاده میکنن
Point[] points = new Point[1];
Console.WriteLine($"points[0]: ({points[0].x}, {points[0].y})");
// خروجی: points[0]: (0, 0)
توصیه حرفهای: بهترین کار اینه که
structهاتون رو جوری طراحی کنید که حالت پیشفرض و صفر شدهشون، یک حالت معتبر و قابل استفاده باشه.2️⃣ ref struct: زندگی فقط روی Stack! ⚡️
این یه قابلیت خیلی خاص و پیشرفته برای بهینهسازیهای سطح پایینه. یه ref struct، نوعی از struct هست که کامپایلر تضمین میکنه فقط و فقط روی Stack زندگی کنه و هرگز به Heap منتقل نشه.
چرا این خوبه؟ چون به ما اجازه میده با حافظه Stack به صورت خیلی بهینه کار کنیم و از فشار روی Garbage Collector کم کنیم، مثل کاری که <Span<T انجام میده.
محدودیتهای ref struct:
چون ref struct هرگز نباید روی هیپ قرار بگیره، محدودیتهای زیر رو داره:
🚫 نمیتونه عضو یک class باشه.
🚫 نمیتونه عنصر یک آرایه باشه.
🚫 نمیتونه Boxed بشه (به object تبدیل بشه).
🚫 نمیتونه اینترفیس پیادهسازی کنه.
🚫 نمیتونه در متدهای async استفاده بشه.
🤔 حرف حساب و تجربه شما
این دو مفهوم، نهایت عمق و قدرت structها در #C رو نشون میدن.
🔖 هشتگها:
#AdvancedCSharp #Struct #Performance #MemoryManagement
مقدمهای بر Distributed Tracing با OpenTelemetry در NET. 📡
اگر در حال ساخت یا نگهداری اپلیکیشنهای توزیعشده NET. هستید، درک نحوه رفتار آنها کلید تضمین قابلیت اطمینان و عملکرد است.
سیستمهای توزیعشده انعطافپذیری ارائه میدهند اما پیچیدگی را نیز به همراه دارند و عیبیابی را به یک سردرد تبدیل میکنند. 🤯 درک چگونگی جریان درخواستها در سیستم شما برای دیباگ کردن و بهینهسازی عملکرد، حیاتی است.
OpenTelemetry
یک فریمورک observability متنباز است که این امر را ممکن میسازد. ✨
در این مقاله، ما به عمق این موضوع میپردازیم که OpenTelemetry چیست، چگونه از آن در پروژههای NET. خود استفاده کنیم و چه بینشهای قدرتمندی را فراهم میکند.
معرفی OpenTelemetry
OpenTelemetry (OTel)
یک استاندارد متنباز و بیطرف نسبت به فروشندگان (vendor-neutral) برای ابزار دقیق (instrumenting) اپلیکیشنها به منظور تولید دادههای تلهمتری است. OpenTelemetry شامل APIها، SDKها، ابزارها و یکپارچهسازیهایی برای ایجاد و مدیریت این دادههای تلهمتری (traces, metrics, and logs) است.
دادههای تلهمتری شامل:
Traces (ردیابیها) 📈:
جریان درخواستها را در سیستمهای توزیعشده نشان میدهند و زمانبندیها و روابط بین سرویسها را نمایش میدهند.
Metrics (معیارها) 📊:
اندازهگیریهای عددی از رفتار سیستم در طول زمان (مانند تعداد درخواستها، نرخ خطا، استفاده از حافظه).
Logs (لاگها) 📝:
رکوردهای متنی از رویدادها با اطلاعات زمینهای غنی. لاگهای ساختاریافته.
OpenTelemetry
یک راه یکپارچه برای جمعآوری این دادهها فراهم میکند، که درک رفتار و سلامت اپلیکیشنهای توزیعشده پیچیده را آسانتر میکند.
ما میتوانیم دادههای تلهمتری که جمعآوری میکنیم را به سرویسی که قادر به پردازش آن است و یک اینترفیس برای تحلیل آن به ما ارائه میدهد، صادر (export) کنیم.
ما قصد داریم OpenTelemetry را طوری پیکربندی کنیم که traceها را مستقیماً به Jaeger صادر کند.
OpenTelemetry
کتابخانهها و SDKهایی برای افزودن کد (instrumentation) به اپلیکیشنهای NET. شما فراهم میکند. این instrumentationها به طور خودکار traces، metrics و logs مورد علاقه ما را ضبط میکنند.
ما قصد داریم پکیجهای NuGet زیر را نصب کنیم: 📦
هنگامی که این پکیجهای NuGet را نصب کردیم، زمان پیکربندی برخی سرویسها فرا میرسد.
🌐 AddAspNetCoreInstrumentation -
این ابزار دقیق (instrumentation) ASP.NET Core را فعال میکند.
📤 AddHttpClientInstrumentation -
این ابزار دقیق HttpClient را برای درخواستهای خروجی فعال میکند.
💾 AddEntityFrameworkCoreInstrumentation -
این ابزار دقیق EF Core را فعال میکند.
🔥 AddRedisInstrumentation -
این ابزار دقیق Redis را فعال میکند.
🐘 AddNpgsql -
این ابزار دقیق PostgreSQL را فعال میکند.
با پیکربندی تمام این ابزارهای دقیق، اپلیکیشن ما شروع به جمعآوری بسیاری از ردیابیهای (traces) ارزشمند در زمان اجرا خواهد کرد.
ما همچنین باید یک متغیر محیطی را برای exporter اضافه شده با AddOtlpExporter پیکربندی کنیم تا به درستی کار کند. ما میتوانیم OTEL_EXPORTER_OTLP_ENDPOINT را از طریق تنظیمات اپلیکیشن تنظیم کنیم. آدرس مشخص شده در اینجا به یک نمونه محلی Jaeger اشاره خواهد کرد.
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
Jaeger
یک پلتفرم متنباز و توزیعشده برای ردیابی است. Jaeger جریان درخواستها و دادهها را حین عبور از یک سیستم توزیعشده، ترسیم میکند.
در اینجا نحوه اجرای Jaeger داخل یک کانتینر Docker آمده است:
ما از ایمیج jaegertracing/all-in-one:latest استفاده میکنیم و پورت 4317 را برای پذیرش دادههای تلهمتری باز میکنیم. رابط کاربری Jaeger روی پورت 16686 در دسترس خواهد بود.
پس از نصب کتابخانههای OpenTelemetry و پیکربندی tracing در اپلیکیشنهایمان، میتوانیم چند درخواست برای تولید دادههای تلهمتری ارسال کنیم. سپس میتوانیم به Jaeger دسترسی پیدا کرده و شروع به تحلیل distributed traceهای خود کنیم.
ثبت نام یک کاربر جدید 👤
انتشار یک پیام با MassTransit 📨
بررسی اطلاعات اضافی trace 💾
traceهای توزیعشده پیچیده 🕸
یک راه یکپارچه برای جمعآوری این دادهها فراهم میکند، که درک رفتار و سلامت اپلیکیشنهای توزیعشده پیچیده را آسانتر میکند.
ما میتوانیم دادههای تلهمتری که جمعآوری میکنیم را به سرویسی که قادر به پردازش آن است و یک اینترفیس برای تحلیل آن به ما ارائه میدهد، صادر (export) کنیم.
ما قصد داریم OpenTelemetry را طوری پیکربندی کنیم که traceها را مستقیماً به Jaeger صادر کند.
افزودن OpenTelemetry به اپلیکیشنهای NET.🔧
OpenTelemetry
کتابخانهها و SDKهایی برای افزودن کد (instrumentation) به اپلیکیشنهای NET. شما فراهم میکند. این instrumentationها به طور خودکار traces، metrics و logs مورد علاقه ما را ضبط میکنند.
ما قصد داریم پکیجهای NuGet زیر را نصب کنیم: 📦
# Automatic tracing, metrics
Install-Package OpenTelemetry.Extensions.Hosting
# Telemetry data exporter
Install-Package OpenTelemetry.Exporter.OpenTelemetryProtocol
# Instrumentation packages
Install-Package OpenTelemetry.Instrumentation.Http
Install-Package OpenTelemetry.Instrumentation.AspNetCore
هنگامی که این پکیجهای NuGet را نصب کردیم، زمان پیکربندی برخی سرویسها فرا میرسد.
services
.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService(serviceName))
.WithTracing(tracing =>
{
tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddRedisInstrumentation()
.AddNpgsql();
tracing.AddOtlpExporter();
});
🌐 AddAspNetCoreInstrumentation -
این ابزار دقیق (instrumentation) ASP.NET Core را فعال میکند.
📤 AddHttpClientInstrumentation -
این ابزار دقیق HttpClient را برای درخواستهای خروجی فعال میکند.
💾 AddEntityFrameworkCoreInstrumentation -
این ابزار دقیق EF Core را فعال میکند.
🔥 AddRedisInstrumentation -
این ابزار دقیق Redis را فعال میکند.
🐘 AddNpgsql -
این ابزار دقیق PostgreSQL را فعال میکند.
با پیکربندی تمام این ابزارهای دقیق، اپلیکیشن ما شروع به جمعآوری بسیاری از ردیابیهای (traces) ارزشمند در زمان اجرا خواهد کرد.
ما همچنین باید یک متغیر محیطی را برای exporter اضافه شده با AddOtlpExporter پیکربندی کنیم تا به درستی کار کند. ما میتوانیم OTEL_EXPORTER_OTLP_ENDPOINT را از طریق تنظیمات اپلیکیشن تنظیم کنیم. آدرس مشخص شده در اینجا به یک نمونه محلی Jaeger اشاره خواهد کرد.
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
اجرای Jaeger به صورت محلی 🐳
Jaeger
یک پلتفرم متنباز و توزیعشده برای ردیابی است. Jaeger جریان درخواستها و دادهها را حین عبور از یک سیستم توزیعشده، ترسیم میکند.
در اینجا نحوه اجرای Jaeger داخل یک کانتینر Docker آمده است:
docker run -d -p 4317:4317 -p 16686:16686 jaegertracing/all-in-one:latest
ما از ایمیج jaegertracing/all-in-one:latest استفاده میکنیم و پورت 4317 را برای پذیرش دادههای تلهمتری باز میکنیم. رابط کاربری Jaeger روی پورت 16686 در دسترس خواهد بود.
Distributed Tracing (ردیابی توزیعشده)
پس از نصب کتابخانههای OpenTelemetry و پیکربندی tracing در اپلیکیشنهایمان، میتوانیم چند درخواست برای تولید دادههای تلهمتری ارسال کنیم. سپس میتوانیم به Jaeger دسترسی پیدا کرده و شروع به تحلیل distributed traceهای خود کنیم.
ثبت نام یک کاربر جدید 👤
انتشار یک پیام با MassTransit 📨
بررسی اطلاعات اضافی trace 💾
traceهای توزیعشده پیچیده 🕸
ثبت نام یک کاربر جدید 👤
در اینجا مثالی از ثبت نام یک کاربر جدید در سیستم آمده است. ما در حال دسترسی به سرویس API gateway (Evently.Gateway) هستیم، که درخواست را به سرویس Evently.Api پروکسی میکند. و میتوانید ببینید که سرویس Evently.Api چند درخواست HTTP را قبل از پایدار کردن یک رکورد جدید در دیتابیس، انجام میدهد.
خلاصه 📝
درک اپلیکیشنهای مدرن، به خصوص توزیعشده، میتواند واقعاً گیجکننده باشد. OpenTelemetry مانند داشتن دید اشعه ایکس 👁 به سیستم شماست.
در حالی که افزودن OpenTelemetry نیاز به مقداری کار اولیه دارد، آن را یک سرمایهگذاری در نظر بگیرید. این سرمایهگذاری زمانی که مشکلات بروز میکنند، به شدت نتیجه میدهد. به جای حدس و گمانهای آشفته، شما دادههای دقیقی برای تمرکز سریع بر روی مشکلات دارید.
🔖 هشتگها:
#OpenTelemetry #Distributed_Tracing
#Performance #MemoryManagement