C# Geeks (.NET) – Telegram
4️⃣ رویدادهای مهم اپلیکیشن را لاگ کنید 📌

به طور کلی، من سعی می‌کنم رویدادهای مهم را در اپلیکیشن خود لاگ کنم. این شامل اطلاعات درخواست فعلی، خطاها، شکست‌ها، مقادیر غیرمنتظره، نقاط انشعاب و غیره است.
اگر از الگوی CQRS با MediatR استفاده می‌کنید، می‌توانید به راحتی لاگینگ را برای تمام درخواست‌های اپلیکیشن اضافه کنید.
در RequestLoggingPipelineBehavior من پراپرتی Error را به LogContext پوش می‌کنم.
internal sealed class RequestLoggingPipelineBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : class
where TResponse : Result
{
// ...
public async Task<TResponse> Handle(...)
{
// ...
TResponse result = await next();
if (result.IsSuccess) { /* ... */ }
else
{
using (LogContext.PushProperty("Error", result.Error, true))
{
_logger.LogError(
"Completed request {RequestName} with error", requestName);
}
}
return result;
}
}


5️⃣ از Seq برای توسعه محلی استفاده کنید 🔍

Seq
یک سرور جستجو، تحلیل و هشدار self-hosted است که برای داده‌های لاگ ساختاریافته ساخته شده است. استفاده از آن برای توسعه محلی رایگان است. این ابزار قابلیت‌های جستجو و فیلترینگ پیشرفته‌ای را روی داده‌های لاگ ساختاریافته ارائه می‌دهد.

می‌توانید یک نمونه Seq را در یک کانتینر Docker بالا بیاورید:
version: '3.4'
services:
seq:
image: datalust/seq:latest
container_name: seq
environment:
- ACCEPT_EULA=Y
ports:
- 5341:5341
- 8081:80
`


خلاصه

لاگ‌های ساختاریافته از یک ساختار یکسان پیروی می‌کنند و چون قابل خواندن توسط ماشین هستند، می‌توانید آن‌ها را برای اطلاعات خاص جستجو کنید. آن‌ها زمینه و جزئیات بیشتری در مورد خطاهای اپلیکیشن ارائه می‌دهند و شناسایی و رفع مشکلات را آسان‌تر می‌کنند.
شما می‌توانید از LogContext قدرتمند Serilog برای غنی‌سازی لاگ‌های خود با یک CorrelationId استفاده کنید.

🔖 هشتگ‌ها:
#CSharp #DotNet #Logging #StructuredLogging #Observability #Serilog
📖 سری آموزشی کتاب C# 12 in a Nutshell

🪄 خداحافظی با رشته‌های جادویی: قدرت nameof در #C

تا حالا شده اسم یه پراپرتی رو تو یه رشته بنویسید (مثلاً برای Exceptionها یا INotifyPropertyChanged) و بعداً که اسم اون پراپرتی رو عوض می‌کنید، یادتون بره اون رشته رو هم آپدیت کنید و کل برنامه به باگ بخوره؟

به این رشته‌ها میگن "رشته‌های جادویی" (Magic Strings) و یکی از منابع اصلی باگ‌های پنهان هستن. #C برای حل این مشکل، یه اپراتور خیلی ساده و قدرتمند داره.

1️⃣ راه حل: اپراتور nameof

اپراتور nameof اسم هرچیزی (متغیر، متد، کلاس، پراپرتی و...) رو در زمان کامپایل به یه رشته تبدیل می‌کنه.

مزیت اصلیش چیه؟ چک شدن در زمان کامپایل!
اگه شما اسم اون متغیر یا پراپرتی رو عوض کنید (Refactor)، ویژوال استودیو به صورت خودکار تمام nameofهای مربوط به اون رو هم آپدیت می‌کنه و دیگه هیچوقت کد شما به خاطر یه رشته قدیمی، به باگ نمی‌خوره.

2️⃣ نحوه استفاده
برای متغیرهای محلی:
int userCount = 123;
string variableName = nameof(userCount); // مقدار: "userCount"

برای اعضای یک تایپ:
// برای اعضای استاتیک و غیراستاتیک کار می‌کنه
string lengthPropName = nameof(StringBuilder.Length); // مقدار: "Length"
// برای گرفتن اسم کامل
string fullName = nameof(StringBuilder) + "." + nameof(StringBuilder.Length);
// "StringBuilder.Length"


🔖 هشتگ‌ها:
#CleanCode #Refactoring
📖 سری آموزشی کتاب C# 12 in a Nutshell

🏛 ستون‌های OOP در #C: وراثت، چندریختی و کستینگ

آماده‌اید که به قلب برنامه‌نویسی شیءگرا (OOP) سفر کنیم؟ امروز می‌خوایم سه تا از مهم‌ترین و به هم پیوسته‌ترین مفاهیم #C رو کالبدشکافی کنیم: وراثت، چندریختی و کستینگ.

تسلط بر این سه مفهوم، پایه و اساس ساختن سیستم‌های انعطاف‌پذیر و قدرتمنده.

1️⃣ وراثت (Inheritance): "یک نوع از..." 👨‍👩‍👧
وراثت به یک کلاس اجازه میده که ویژگی‌ها و رفتارهای یک کلاس دیگه رو به ارث ببره. به کلاس اصلی میگیم کلاس پایه (Base Class) و به کلاس جدید میگیم کلاس مشتق‌شده (Derived Class). این یعنی "کلاس مشتق‌شده، یک نوع از کلاس پایه است".

// این کلاس پایه ماست
public class Asset
{
public string Name;
}
// این دو کلاس، از Asset ارث‌بری می‌کنند
public class Stock : Asset
{
public long SharesOwned;
}

public class House : Asset
{
public decimal Mortgage;
}

حالا Stock و House هم پراپرتی Name رو دارن و هم پراپرتی‌های مخصوص خودشون.

🎭 جادوی چندریختی (Polymorphism) در
C# : Upcasting و Downcasting


2️⃣ چندریختی (Polymorphism): یک متغیر، چند چهره

چندریختی یعنی شما می‌تونید یه متغیر از نوع کلاس پایه داشته باشید، ولی در زمان اجرا، به اون یک نمونه از هر کدوم از کلاس‌های فرزندش رو اختصاص بدید. این قابلیت به شما اجازه میده کدهای خیلی انعطاف‌پذیری بنویسید.

مثال: این متد یک Asset به عنوان ورودی می‌گیره، ولی شما می‌تونید هم Stock و هم House رو بهش پاس بدید!
// کلاس‌های پست قبلی
public class Asset { public string Name; }
public class Stock : Asset { public long SharesOwned; }
public class House : Asset { public decimal Mortgage; }
// این متد به لطف چندریختی، با هر نوع Asset کار می‌کنه
public static void Display(Asset asset)
{
Console.WriteLine(asset.Name);
}

// --- نحوه استفاده ---
Stock msft = new Stock { Name = "MSFT" };
House mansion = new House { Name = "Mansion" };

Display(msft); // خروجی: MSFT
Display(mansion); // خروجی: Mansion


3️⃣ کستینگ (Casting): تغییر زاویه دید به آبجکت 🔄

کستینگ یعنی تغییر نوع یک رفرنس برای دسترسی به اعضای مختلف. دو نوع اصلی داریم:

Upcasting (تبدیل فرزند به پدر) ⬆️

این کار همیشه امنه و به صورت ضمنی (implicit) انجام میشه. شما یک نمونه از کلاس فرزند رو در یک متغیر از نوع پدر قرار میدید. بعد از این کار، شما به آبجکت از یک زاویه دید "محدودتر" نگاه می‌کنید و فقط به اعضایی که در کلاس پدر تعریف شدن، دسترسی دارید.
Stock msft = new Stock { Name = "MSFT", SharesOwned = 1000 };
Asset a = msft; // Upcast (ضمنی و همیشه امن)
Console.WriteLine(a.Name); // درسته، چون Name در Asset هست
// Console.WriteLine(a.SharesOwned); // خطای زمان کامپایل! a از نوع Asset است و SharesOwned را نمی‌شناسد.


Downcasting (تبدیل پدر به فرزند) ⬇️

این کار می‌تونه خطرناک باشه و باید به صورت صریح (explicit) با (type) انجام بشه. شما به کامپایلر میگید: "من مطمئنم این آبجکت که از نوع پدره، در واقع یک نمونه از نوع فرزند هست". اگه اشتباه کرده باشید، در زمان اجرا با خطای InvalidCastException مواجه میشید.
Stock msft = new Stock();
Asset a = msft; // Upcast
// Downcast موفقیت‌آمیز
Stock s = (Stock)a;
Console.WriteLine(s.SharesOwned); // <بدون خطا>
// --- مثال خطا ---
House h = new House();
Asset a2 = h; // Upcast
// Downcast ناموفق! a2 یک Stock نیست.
// Stock s2 = (Stock)a2; // در زمان اجرا خطای InvalidCastException می‌دهد


🤔 حرف حساب و تجربه شما

درک درست Upcasting و Downcasting، کلید استفاده امن و قدرتمند از چندریختی در کدهای شماست. (در آینده با روش‌های امن‌تر Downcasting مثل as و is آشنا میشیم).

بهترین مثالی که از قدرت چندریختی تو پروژه‌هاتون دیدید چی بوده؟ آیا تا حالا به خطای InvalidCastException به خاطر یه Downcast اشتباه برخورد کردید؟

🔖 هشتگ‌ها:
#OOP #Polymorphism #Casting
معماری برش عمودی (Vertical Slice): ساختاربندی برش‌های عمودی 🔪


از سازماندهی پروژه خود در لایه‌های مختلف خسته شده‌اید؟ 😫

معماری برش عمودی (VSA) یک جایگزین قانع‌کننده برای معماری‌های لایه‌ای سنتی است. VSA فیلم‌نامه را در مورد نحوه ساختاردهی کد ما برعکس می‌کند.

به جای لایه‌های افقی (Presentation, Application, Domain)، VSA کد را بر اساس ویژگی (feature) سازماندهی می‌کند. هر ویژگی همه چیز مورد نیاز خود را، از endpointهای API گرفته تا دسترسی به داده، در بر می‌گیرد.

در این مقاله، ما بررسی خواهیم کرد که چگونه می‌توانید برش‌های عمودی را در VSA ساختاربندی کنید.

درک برش‌های عمودی 🧩

در هسته خود، یک برش عمودی یک واحد مستقل از عملکرد را نشان می‌دهد. این یک برش از طریق کل پشته اپلیکیشن است. این برش، تمام کدها و کامپوننت‌های لازم برای تحقق یک ویژگی خاص را کپسوله می‌کند.

در معماری‌های لایه‌ای سنتی، کد به صورت افقی در لایه‌های مختلف سازماندهی می‌شود. پیاده‌سازی یک ویژگی می‌تواند در چندین لایه پراکنده شود. تغییر یک ویژگی نیازمند اصلاح کد در چندین لایه است.

VSA
با گروه‌بندی تمام کدها برای یک ویژگی در یک برش واحد، به این مشکل رسیدگی می‌کند.

📌این تغییر دیدگاه چندین مزیت به همراه دارد:


انسجام بهبود یافته: کدهای مربوط به یک ویژگی خاص در کنار هم قرار می‌گیرند، که درک، اصلاح و تست آن را آسان‌تر می‌کند.

پیچیدگی کاهش یافته: VSA با اجتناب از نیاز به پیمایش چندین لایه، مدل ذهنی اپلیکیشن شما را ساده می‌کند.

تمرکز بر منطق بیزینس: ساختار به طور طبیعی بر روی مورد استفاده بیزینس تأکید می‌کند تا جزئیات پیاده‌سازی فنی.

نگهداری آسان‌تر: تغییرات در یک ویژگی، در داخل برش آن محلی‌سازی می‌شوند و ریسک عوارض جانبی ناخواسته را کاهش می‌دهند.

پیاده‌سازی معماری برش عمودی 👨‍💻

در اینجا یک مثال از برش عمودی که ویژگی CreateProduct را نشان می‌دهد، آمده است. ما از یک کلاس استاتیک برای نمایش ویژگی و گروه‌بندی انواع مرتبط استفاده می‌کنیم.
public static class CreateProduct
{
public record Request(string Name, decimal Price);
public record Response(int Id, string Name, decimal Price);

public class Endpoint : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder app)

app.MapPost("products", Handler).WithTags("Products");
}

public static IResult Handler(Request request, AppDbContext context)
{
var product = new Product
{
Name = request.Name,
Price = request.Price
};

context.Products.Add(product);
context.SaveChanges();

return Results.Ok(
new Response(product.Id, product.Name, product.Price));
}
}
}


کد برای کل ویژگی CreateProduct به طور فشرده در یک فایل واحد گروه‌بندی شده است. این کار مکان‌یابی، درک و اصلاح همه چیز مربوط به این عملکرد را بسیار آسان می‌کند. ما نیازی به پیمایش چندین لایه (مانند کنترلرها، سرویس‌ها، ریپازیتوری‌ها و غیره) نداریم.

معرفی اعتبارسنجی (Validation) در برش‌های عمودی 🛡

برش‌های عمودی معمولاً نیاز به حل برخی دغدغه‌های مشترک (cross-cutting concerns) دارند که یکی از آن‌ها اعتبارسنجی است. ما می‌توانیم به راحتی اعتبارسنجی را با کتابخانه FluentValidation پیاده‌سازی کنیم.
public static class CreateProduct
{
public record Request(string Name, decimal Price);
public record Response(int Id, string Name, decimal Price);
public class Validator : AbstractValidator<Request> { /* ... */ }

public class Endpoint : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder app)
{
app.MapPost("products", Handler).WithTags("Products");
}

public static async Task<IResult> Handler(
Request request,
IValidator<Request> validator,
AppDbContext context)
{
var validationResult = await validator.ValidateAsync(request);
if (!validationResult.IsValid)
{
return Results.BadRequest(validationResult.Errors);
}
// ... (ایجاد محصول و بازگرداندن پاسخ)
}
}
}
مدیریت ویژگی‌های پیچیده و منطق مشترک 🧠

VSA
در مدیریت ویژگی‌های مستقل برتری دارد. با این حال، اپلیکیشن‌های دنیای واقعی اغلب شامل تعاملات پیچیده و منطق مشترک هستند.
در اینجا چند استراتژی وجود دارد که می‌توانید در نظر بگیرید:

🔹️ تجزیه (Decomposition): ویژگی‌های پیچیده را به برش‌های عمودی کوچکتر و قابل مدیریت‌تر تقسیم کنید.

🔹️ بازآرایی (Refactoring): وقتی یک برش عمودی نگهداری‌اش دشوار می‌شود، از تکنیک‌های بازآرایی مانند Extract method و Extract class استفاده کنید.

🔹️ استخراج منطق مشترک: منطق مشترکی که در چندین ویژگی استفاده می‌شود را شناسایی کنید. یک کلاس جداگانه (یا متد توسعه) برای ارجاع به آن از برش‌های عمودی خود ایجاد کنید.

🔹️ انتقال منطق به پایین: برش‌های عمودی را با استفاده از کد رویه‌ای، مانند یک Transaction Script بنویسید. سپس، می‌توانید بخش‌هایی از منطق بیزینس را که به طور طبیعی به انتیتی‌های دامین تعلق دارند، شناسایی کنید.

خلاصه 📝

معماری برش عمودی بیش از یک راه برای ساختاردهی کد شماست. با تمرکز بر ویژگی‌ها، VSA به شما اجازه می‌دهد اپلیکیشن‌های منسجم و قابل نگهداری ایجاد کنید. برش‌های عمودی مستقل هستند و تست واحد و یکپارچه‌سازی را ساده‌تر می‌کنند.

تغییرات محلی‌سازی می‌شوند، ریسک رگرسیون‌ها را کاهش داده و امکان تکرارهای سریع‌تر را فراهم می‌کنند.
معماری برش عمودی را در پروژه بعدی خود در نظر بگیرید. این یک تغییر ذهنی بزرگ از معماری تمیز است. با این حال، هر دو جایگاه خود را دارند و حتی ایده‌های مشابهی را به اشتراک می‌گذارند.

🔖 هشتگ‌ها:
#SoftwareArchitecture #VerticalSliceArchitecture #CQRS
📌5 books that made me a better software engineer:

1️⃣ Clean Architecture, Robert Martin

2️⃣ Domain-Driven Design, Eric Evans

3️⃣ Building Microservices, Sam Newman

4️⃣ Designing Data-Intensive Applications, Martin Kleppmann

5️⃣ Patterns of Enterprise Application Architecture, Martin Fowler

Which book made you a better software engineer?🤔
📖 سری آموزشی کتاب C# 12 in a Nutshell

🛡 کستینگ امن در #C: راهنمای کامل اپراتورهای as و is

تو پست قبلی دیدیم که Downcasting (تبدیل پدر به فرزند) با (Stock)a می‌تونه خطرناک باشه و اگه اشتباه کنیم، با خطای InvalidCastException برنامه کرش می‌کنه.

اما #C دو تا ابزار خیلی بهتر و امن‌تر برای این کار داره که جلوی این کرش‌ها رو می‌گیره: اپراتورهای as و is.

1️⃣ اپراتور as: کستینگ بدون استثنا (Exception)

اپراتور as سعی می‌کنه عمل downcast رو انجام بده.

اگه موفق بشه، نتیجه رو برمی‌گردونه.

اگه شکست بخوره، به جای پرتاب Exception، خیلی آروم و بی‌صدا مقدار null رو برمی‌گردونه.

این اپراتور برای مواقعی عالیه که شما می‌خواید نتیجه رو برای null چک کنید.
Asset a = new House(); // a یک House است، نه Stock
Stock s = a as Stock; // خطا رخ نمی‌دهد، s برابر با null می‌شود

if (s != null)
{
// این کد اجرا نخواهد شد
Console.WriteLine(s.SharesOwned);
}


💡نکته مهم: as فقط زمانی خوبه که شما قصد دارید نتیجه رو برای null چک کنید. اگه چک نکنید و روی نتیجه‌ش کاری انجام بدید، ممکنه با خطای مبهم NullReferenceException مواجه بشید.

2️⃣ اپراتور is: سوال پرسیدن قبل از عمل

اپراتور is فقط یه سوال true/false می‌پرسه: "آیا این آبجکت از این نوع هست (یا قابل تبدیل به این نوعه)؟" این روش کلاسیک برای جلوگیری از خطای کستینگه.
// روش قدیمی ولی امن
if (a is Stock)
{
Stock s = (Stock)a; // حالا که مطمئنیم، با خیال راحت کست می‌کنیم
// ...
}


3️⃣ جادوی مدرن: is با Pattern Variable

این روش مدرن و پیشنهادی امروزه. شما می‌تونید همزمان با چک کردن نوع، اگه شرط درست بود، نتیجه کست رو مستقیماً داخل یه متغیر جدید بریزید! این کار کد رو فوق‌العاده تمیز و کوتاه می‌کنه.
Asset a = new Stock { SharesOwned = 500 };
if (a is Stock s)
{
// دیگه نیازی به کست جداگانه نیست!
// s همینجا از نوع Stock و قابل استفاده است
Console.WriteLine(s.SharesOwned); // 500
}

ترفند حرفه‌ای: شما حتی می‌تونید از این متغیر جدید، در ادامه‌ی همون شرط استفاده کنید!
if (a is Stock s && s.SharesOwned > 100)
{
Console.WriteLine("This is a significant stock holding!");
}

🤔 حرف حساب و تجربه شما

استفاده از as و به خصوص is با pattern variable، کد شما رو در کار با سلسله‌مراتب وراثت، خیلی امن‌تر و خواناتر می‌کنه.

🔖 هشتگ‌ها:
#Casting #is #as
معماری فریادزن (Screaming Architecture) 📢


اگر به ساختار پوشه‌های سیستم خود نگاهی بیندازید، آیا می‌توانید بگویید که سیستم در مورد چیست؟ و یک سوال جالب‌تر. آیا یک توسعه‌دهنده جدید در تیم شما می‌تواند به راحتی بر اساس ساختار پوشه‌ها بفهمد که سیستم چه کاری انجام می‌دهد؟

معماری شما باید بیانگر مشکلاتی باشد که حل می‌کند. سازماندهی سیستم شما حول موارد استفاده (use cases)، به ساختاری منجر می‌شود که با دامنه کسب‌وکار (business domain) هماهنگ است. این رویکرد معماری فریادزن نامیده می‌شود.

معماری فریادزن اصطلاحی است که توسط رابرت مارتین (عمو باب) ابداع شده است. او استدلال می‌کند که ساختار یک سیستم نرم‌افزاری باید بیانگر این باشد که سیستم در مورد چیست. او تشابهی بین نگاه کردن به نقشه یک ساختمان ترسیم می‌کند، که در آن شما می‌توانید هدف ساختمان را بر اساس نقشه تشخیص دهید. 🗺

در این مقاله، می‌خواهم چند مثال عملی نشان دهم و در مورد مزایای معماری فریادزن بحث کنم.

یک رویکرد مبتنی بر مورد استفاده (Use Case Driven) 🎯

یک مورد استفاده، یک تعامل یا وظیفه خاص را نشان می‌دهد که کاربر می‌خواهد در سیستم شما به آن دست یابد. این، منطق بیزینس مورد نیاز برای انجام آن وظیفه را کپسوله می‌کند. یک مورد استفاده، توصیف سطح بالایی از هدف کاربر است. برای مثال، "رزرو یک آپارتمان" یا "خرید یک بلیت". این رویکرد بر روی چه چیزی رفتار سیستم تمرکز دارد، نه چگونه.

وقتی به ساختار پوشه‌ها و فایل‌های سورس کد سیستم خود نگاه می‌کنید:

آیا آن‌ها فریاد می‌زنند: سیستم رزرو آپارتمان یا سیستم بلیت‌فروشی؟

یا فریاد می‌زنند ASP.NET Core؟

در اینجا یک مثال از ساختار پوشه‌ای که حول دغدغه‌های فنی سازماندهی شده، آمده است: 👎

📁 Api/
| 📁 Controllers
|
📁 Entities
| 📁 Repositories
|
📁 Services
| #️⃣ ApartmentService.cs
|
#️⃣ BookingService.cs
| 📁 Models

شما متوجه خواهید شد که انسجام (cohesion) با این ساختار پوشه پایین است.

معماری فریادزن چگونه کمک می‌کند؟
یک رویکرد مبتنی بر مورد استفاده، موارد استفاده سیستم را به عنوان مفهوم سطح بالا قرار می‌دهد. در داخل یک پوشه مورد استفاده، ممکن است مفاهیم فنی مورد نیاز برای پیاده‌سازی آن را پیدا کنیم. معماری برش عمودی نیز از دیدگاه مشابهی به این موضوع نزدیک می‌شود. 👍

📁 Api/
|
📁 Apartments
| 📁 ReserveApartment
|
📁 Bookings
| 📁 CancelBooking
|
📁 Payments
| 📁 Reviews
مزایای معماری فریادزن

• انسجام بهبود یافته چون موارد استفاده مرتبط به هم نزدیک هستند.

• اتصال بالا (High coupling) برای یک مورد استفاده واحد و موارد استفاده مرتبط با آن.

• اتصال سست (Low coupling) بین موارد استفاده نامرتبط.

• ناوبری آسان‌تر در سراسر سولوشن.

Bounded Contextها و برش‌های عمودی 🧩

ما تکنیک‌های زیادی برای کشف ماژول‌های سطح بالا در سیستم خود داریم. برای مثال، می‌توانیم از event storming برای کاوش موارد استفاده سیستم استفاده کنیم.

ایده کلی در اینجا، فکر کردن در مورد انسجام حول عملکردهاست. Bounded contextها، برش‌های عمودی، و معماری فریادزن مفاهیم مکمل یکدیگر هستند.

در اینجا یک مثال از معماری فریادزن برای این سیستم آمده است. بیایید بگوییم ماژول Ticketing به صورت داخلی از معماری تمیز استفاده می‌کند. اما ما هنوز هم می‌توانیم سیستم را حول پوشه‌های ویژگی و موارد استفاده سازماندهی کنیم.

📁 Modules/
|
📁 Ticketing
| 📁 Application
|
📁 Carts
| 📁 AddItemToCart
|
📁 ClearCart
| 📁 Orders
|
📁 SubmitOrder
| 📁 Domain
|
📁 Customers
| 📁 Orders
|
📁 infrastructure
| 📁 Authentication
|
📁 Customers

نکات پایانی 📝

معماری فریادزن فقط یک عبارت جذاب نیست، بلکه رویکردی است که می‌تواند عمیقاً بر نحوه ساخت نرم‌افزار شما تأثیر بگذارد. با سازماندهی سیستم خود حول موارد استفاده، شما پایگاه کد خود را با دامنه اصلی کسب‌وکار هماهنگ می‌کنید.

به یاد داشته باشید، هدف ایجاد سیستمی است که هدف خود را از طریق ساختارش مخابره کند. یک رویکرد مبتنی بر مورد استفاده را در پیش بگیرید، دامنه‌های پیچیده را به bounded contextها تجزیه کنید. سیستمی بسازید که واقعاً در مورد مشکلاتی که حل می‌کند "فریاد" بزند.

🔖 هشتگ‌ها:
#SoftwareArchitecture #ScreamingArchitecture #DomainDrivenDesign #VerticalSliceArchitecture
خروجی کد زیر چیست؟🤔
int a = 5, b = 10;
Console.WriteLine( a++ + ++b );
Anonymous Quiz
10%
15
65%
16
26%
17
0%
18
کجا نباید از داداشمون RabbitMQ استفاده کنیم؟🤔


1️⃣ وقتى نياز به real-time response داريم.
چرا؟ چون RabbitMQ صف هست و ارسال/دريافت پيام ممكنه با تاخير انجام شود.
پيشنهادم WebSocket, gRPC يا Redis Pub/Sub است.

2️⃣ وقتى به message replay يا history نياز داريم.
چرا؟ چون RabbitMQ پيام‌ها رو بعد از مصرف حذف می‌كند. البته ميشه كانفيگ كرد كه نگه داره.

3️⃣ زمانى كه پيام‌ها حجمشون خيلى زياد است.
چرا؟ چون در حجم بالا RabbitMQ دچار افت performance می‌شود. Kafka رو پيشنهاد میكنم براى اینجا.

4️⃣ وقتى ترتيب دقيق پردازش پيام‌ها خيلى مهم است. چرا؟ چون RabbitMQ تضمين دقيقى براى ترتيب پيام‌ها ندارد اينجا هم پیشنهادم Kafka است

بقيه‌اش رو شما بگید.👇🏻
🌱 توسعه‌دهنده جونیور از EF Core مستقیم در کنترلر استفاده می‌کنه.

🌳 توسعه‌دهنده میدلول از الگوی Controller-Service-Repository استفاده می‌کنه.

🌲 توسعه‌دهنده سینیور از معماری تمیز (Clean Architecture) استفاده می‌کنه.

🤯 معمار دوباره برمی‌گرده به استفاده از EF Core در کنترلر!

چرا این اتفاق میفته؟ 🤔
• چون پروژه‌های واقعی از راه حل‌های ساده سود می‌برن.

• فقط زمانی لایه اضافه کن که واقعاً بهش نیاز داری.

• یه اپلیکیشن کوچیک می‌تونه با EF Core در کنترلرها یا Minimal API endpoints زنده بمونه.

• یه پروژه متوسط ممکنه از یه لایه اپلیکیشن (مثل سرویس‌ها) سود ببره.

• یه سیستم بزرگ ممکنه به برش‌های عمودی (Vertical Slices) و معماری تمیز یا حتی DDD نیاز داشته باشه.

📌درس اصلی اینه: از اول کار، بیش از حد مهندسی (over-engineer) نکن.

قدم به قدم بساز.
اگه به پیچیدگی بیشتری نیاز پیدا کردی، اضافه‌ش کن.
اگه نه، ساده نگهش دار و برو جلو.

شما همیشه می‌تونید بعداً کد رو بازآرایی (refactor) یا گسترش بدید.
روی تحویل ارزش تمرکز کن، نه روی ساختن لایه‌ها. 🎯

💡: خودِ EF Core از قبل الگوهای Repository و Unit Of Work رو پیاده‌سازی کرده، پس در اکثر مواقع شما نیازی به ساختن کلاس‌های ریپازیتوری خودتون ندارید.
📖 سری آموزشی کتاب C# 12 in a Nutshell

🏛 راهنمای کامل وراثت پیشرفته در #C: از virtual تا sealed


آماده‌اید برای یک شیرجه عمیق به قلب تپنده برنامه‌نویسی شیءگرا؟ امروز می‌خوایم تمام ابزارهای #C برای کنترل دقیق وراثت و چندریختی رو کالبدشکافی کنیم.

1️⃣ تعریف قرارداد برای فرزندان: virtual و abstract


برای اینکه به کلاس‌های فرزند اجازه بدیم رفتار کلاس پدر رو تخصصی‌سازی کنن، از این دو کلمه کلیدی استفاده می‌کنیم:

virtual (قرارداد اختیاری): ✍️

با virtual، کلاس پایه یک پیاده‌سازی پیش‌فرض ارائه میده ولی به فرزندان اجازه override کردن (بازنویسی) اون رفتار رو میده.
public class Asset
{
public string Name;
public virtual decimal Liability => 0;
}
public class House : Asset
{
public decimal Mortgage;
public override decimal Liability => Mortgage;
}


abstract (قرارداد اجباری): 🏗

با abstract، کلاس پایه هیچ پیاده‌سازی‌ای ارائه نمیده و فرزندان رو مجبور به override کردن و پیاده‌سازی اون عضو می‌کنه. کلاسی که عضو abstract داره، خودش هم باید abstract باشه و نمیشه ازش نمونه (new) ساخت.
public abstract class Asset
{
public abstract decimal NetValue { get; }
}
public class Stock : Asset
{
public long SharesOwned;
public decimal CurrentPrice;
public override decimal NetValue => CurrentPrice * SharesOwned;
}


2️⃣ مخفی کردن عضو با new (Hiding)

گاهی وقتا یه عضو در کلاس فرزند، هم‌اسم عضوی در کلاس پدر میشه. به این کار میگن مخفی کردن (Hiding). کلمه کلیدی new فقط برای اینه که به کامپایلر بگیم "من می‌دونم دارم چیکار می‌کنم، این کارم عمدیه!" و جلوی Warning رو بگیریم.

3️⃣ دوئل بزرگ: override در برابر new ⚔️

این مهم‌ترین بخش ماجراست. تفاوت این دو، در رفتار چندریختی (Polymorphic) مشخص میشه:

• override:
رفتار واقعی چندریختی رو پیاده می‌کنه. مهم نیست متغیر شما از چه نوعی باشه (پدر یا فرزند)، همیشه متدِ نوعِ واقعی آبجکت صدا زده میشه.

• new:
چندریختی رو می‌شکنه! متدی که صدا زده میشه، بستگی به نوع متغیر در زمان کامپایل داره، نه نوع واقعی آبجکت.

این مثال رو ببینید تا کامل متوجه بشید:
public class BaseClass 
{
public virtual void Foo() { Console.WriteLine("BaseClass.Foo"); }
}
public class Overrider : BaseClass
{
public override void Foo() { Console.WriteLine("Overrider.Foo"); }
}

public class Hider : BaseClass
{
public new void Foo() { Console.WriteLine("Hider.Foo"); }
}

// --- نتایج ---
Overrider over = new Overrider();
BaseClass b1 = over;
over.Foo(); // خروجی: Overrider.Foo
b1.Foo(); // خروجی: Overrider.Foo <- (چون override شده، رفتار واقعی حفظ میشه)

Hider h = new Hider();
BaseClass b2 = h;
h.Foo(); // خروجی: Hider.Foo
b2.Foo(); // خروجی: BaseClass.Foo <- (چون new شده، متد کلاس پایه اجرا میشه!)


4️⃣ قفل کردن وراثت با sealed 🔒

حالا اگه بخواید جلوی override شدن بیشتر رو در زنجیره وراثت بگیرید، از sealed استفاده می‌کنید.

• sealed روی متد:
می‌تونید یه متد override شده رو sealed کنید تا کلاس‌های فرزند بعدی دیگه نتونن اون رو تغییر بدن.

• sealed روی کلاس:
یا می‌تونید کل یک کلاس رو sealed کنید تا دیگه هیچ کلاسی نتونه ازش ارث‌بری کنه.

🤔 حرف حساب

تسلط بر این کلمات کلیدی، شما رو از یه کاربر ساده وراثت، به یک معمار واقعی کلاس‌ها تبدیل می‌کنه.

🔖 هشتگ‌ها:
#OOP #Inheritance #Override #Sealed #Polymorphism
Outbox Pattern✨️
پیاده‌سازی الگوی Outbox 📬


در سیستم‌های توزیع‌شده، ما اغلب با چالش همگام‌سازی دیتابیس و سیستم‌های خارجی مواجه هستیم. تصور کنید سفارشی را در دیتابیس ذخیره کرده و سپس پیامی را به یک message broker منتشر می‌کنید. اگر هر یک از این عملیات‌ها شکست بخورد، سیستم شما در وضعیتی ناسازگار (inconsistent) قرار می‌گیرد.

الگوی Outbox این مشکل را با در نظر گرفتن انتشار پیام به عنوان بخشی از تراکنش دیتابیس شما حل می‌کند. به جای انتشار مستقیم پیام‌ها، ما آن‌ها را در یک جدول Outbox در دیتابیس خود ذخیره می‌کنیم و از عملیات اتمیک اطمینان حاصل می‌کنیم. سپس یک فرآیند جداگانه این پیام‌ها را به طور قابل اعتماد منتشر می‌کند.

در این مقاله، ما به پیاده‌سازی این الگو در NET.، از راه‌اندازی تا مقیاس‌پذیری، خواهیم پرداخت.

چرا به الگوی Outbox نیاز داریم؟ 🤔

الگوی transactional Outbox یک مشکل رایج را در سیستم‌های توزیع‌شده حل می‌کند. این مشکل زمانی رخ می‌دهد که شما باید دو کار را همزمان انجام دهید: ذخیره داده و ارتباط با یک کامپوننت خارجی.

برای مثال، یک میکروسرویس را تصور کنید که باید:

🔹️ یک سفارش جدید را در دیتابیس خود ذخیره کند.

🔹️ در مورد این سفارش جدید به سیستم‌های دیگر اطلاع دهد.

🔹️ اگر یکی از این مراحل شکست بخورد، سیستم شما می‌تواند در وضعیتی ناسازگار قرار گیرد.
// کد با مشکل سازگاری بالقوه
public async Task<OrderDto> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
// ...
await unitOfWork.CommitAsync(cancellationToken);

// تراکنش دیتابیس در این نقطه کامل شده است
// 💥 اگر برنامه اینجا کرش کند یا event bus در دسترس نباشد چه؟
await eventBus.Send(new OrderCreatedIntegrationEvent(order.Id));

return new OrderDto { Id = order.Id, Total = order.Total };
}


الگوی transactional Outbox به حل این مشکل کمک می‌کند. 📈 ما هم سفارش و هم پیام Outbox را در یک تراکنش دیتابیس واحد ذخیره می‌کنیم. این یک عملیات "همه یا هیچ" است.

سپس یک پردازشگر Outbox جداگانه، ارسال واقعی پیام را مدیریت می‌کند. ⚠️ نکته مهمی که باید در اینجا متوجه شوید این است که الگوی Outbox به ما تحویل حداقل-یک‌باره (at-least-once delivery) را می‌دهد. این یعنی ما باید مصرف‌کنندگان پیام خود را idempotent بسازیم.

پیاده‌سازی الگوی Outbox 💾

ابتدا، جدول Outbox خود را ایجاد می‌کنیم:
CREATE TABLE outbox_messages (
id UUID PRIMARY KEY,
type VARCHAR(255) NOT NULL,
content JSONB NOT NULL,
occurred_on_utc TIMESTAMP WITH TIME ZONE NOT NULL,
processed_on_utc TIMESTAMP WITH TIME ZONE NULL,
error TEXT NULL
);

و کلاس #C برای نمایش ورودی Outbox:
public sealed class OutboxMessage
{
public Guid Id { get; init; }
public string Type { get; init; }
public string Content { get; init; }
public DateTime OccurredOnUtc { get; init; }
public DateTime? ProcessedOnUtc { get; init; }
public string? Error { get; init; }
}


✨️یک رویکرد زیبا برای پیاده‌سازی این، استفاده از domain events برای نمایش نوتیفیکیشن‌ها است. قبل از تکمیل تراکنش، می‌توانید تمام رویدادها را برداشته و آن‌ها را به عنوان پیام‌های Outbox ذخیره کنید.
پردازش Outbox ⚙️

پردازشگر Outbox کامپوننت بعدی است که به آن نیاز داریم. این می‌تواند یک فرآیند فیزیکی جداگانه یا یک background worker در همان فرآیند باشد.

من از Quartz برای زمان‌بندی background jobها برای پردازش Outbox استفاده خواهم کرد.
[DisallowConcurrentExecution]
public class OutboxProcessorJob(...) : IJob
{
public async Task Execute(IJobExecutionContext context)
{
// پیام‌های پردازش نشده را از دیتابیس واکشی می‌کند
var messages = await connection.QueryAsync<OutboxMessage>(...);
foreach (var message in messages)
{
try
{
// پیام را deserialize کرده و به message broker منتشر می‌کند
await publishEndpoint.Publish(deserializedMessage);

// پیام را به عنوان پردازش شده علامت‌گذاری می‌کند
await connection.ExecuteAsync(
"UPDATE outbox_messages SET processed_on_utc = @ProcessedOnUtc WHERE id = @Id",
...);
}
catch (Exception ex)
{
// خطا را لاگ می‌کند
}
}
await transaction.CommitAsync();
}
}


یک راه جایگزین برای پردازش پیام‌های Outbox، استفاده از Transaction log tailing است. ما می‌توانیم این را با استفاده از Postgres logical replication پیاده‌سازی کنیم.

ملاحظات و مزایا و معایب 🧐

الگوی Outbox، ضمن کارآمدی، پیچیدگی و نوشتن‌های اضافی در دیتابیس را به همراه دارد.

🔹 توصیه می‌کنم مکانیزم‌های تلاش مجدد (retry) را در پردازشگر Outbox برای بهبود قابلیت اطمینان پیاده‌سازی کنید.

🔹 ضروری است که مصرف‌کنندگان پیام idempotent را پیاده‌سازی کنید.

🔹 با گذشت زمان، جدول Outbox می‌تواند به طور قابل توجهی رشد کند. مهم است که یک استراتژی آرشیو از همان ابتدا پیاده‌سازی کنید.

مقیاس‌پذیری پردازش Outbox 🚀

با رشد سیستم شما، ممکن است یک پردازشگر Outbox نتواند حجم پیام‌ها را مدیریت کند.

🔹 یک رویکرد ساده، افزایش فرکانس اجرای job پردازشگر Outbox است.

🔹 یک استراتژی مؤثر دیگر، افزایش اندازه بچ (batch size) هنگام واکشی پیام‌های پردازش نشده است.

🔹 برای سیستم‌های با حجم بالا، پردازش Outbox به صورت موازی می‌تواند بسیار مؤثر باشد. یک مکانیزم قفل‌گذاری برای ادعای بچ‌های پیام پیاده‌سازی کنید.

جمع‌بندی

الگوی Outbox یک ابزار قدرتمند برای حفظ سازگاری داده در سیستم‌های توزیع‌شده است. با جداسازی عملیات دیتابیس از انتشار پیام، الگوی Outbox تضمین می‌کند که سیستم شما حتی در مواجهه با شکست‌ها، قابل اعتماد باقی بماند.

به یاد داشته باشید که مصرف‌کنندگان خود را idempotent نگه دارید، استراتژی‌های مقیاس‌پذیری مناسب را پیاده‌سازی کنید، و رشد جدول Outbox خود را مدیریت کنید.

🔖 هشتگ‌ها:
#SoftwareArchitecture #SystemDesign #Microservices #DistributedSystems #OutboxPattern #EventDrivenArchitecture #DataConsistency
📖 سری آموزشی کتاب C# 12 in a Nutshell

🏛 کالبدشکافی وراثت در #C: سازنده‌ها، required و ترتیب اجرا

وقتی کلاس‌های فرزند می‌خوان آبجکت بسازن، چه اتفاقی پشت صحنه میفته؟ امروز به عمیق‌ترین قوانین ساخت و ساز در سلسله‌مراتب وراثت شیرجه می‌زنیم.

1️⃣ کلیدواژه base: صحبت با کلاس پدر

base
به شما اجازه میده از داخل کلاس فرزند، به اعضای کلاس پدر دسترسی داشته باشید. دو کاربرد اصلی داره:

صدا زدن عضو بازنویسی شده:
اگه متدی رو override کردید، با base می‌تونید به پیاده‌سازی اصلی در کلاس پدر دسترسی پیدا کنید.
public override decimal Liability => base.Liability + Mortgage;

صدا زدن سازنده پدر:
با سینتکس : base(...) صراحتاً سازنده مورد نظرتون در کلاس پدر رو صدا می‌زنید.
public class Subclass : Baseclass
{
public Subclass(int x) : base(x) { }
}


2️⃣ قوانین سازنده‌ها در وراثت ⚖️

• قانون اول: سازنده کلاس پدر همیشه قبل از سازنده کلاس فرزند اجرا میشه.

• قانون دوم (تله مهم): اگه شما : base(...) رو ننویسید، #C به صورت خودکار سعی می‌کنه سازنده بدون پارامتر پدر رو صدا بزنه. اگه کلاس پدر سازنده بدون پارامتر نداشته باشه، خطای زمان کامپایل می‌گیرید!

3️⃣ required members (از C# 11):

جایگزین مدرن
گاهی وقتا زنجیره سازنده‌ها خیلی طولانی میشه. از #C 11 به بعد، می‌تونید پراپرتی‌ها یا فیلدهایی رو با کلمه کلیدی required مشخص کنید. این یعنی هر کسی که از کلاس شما آبجکت می‌سازه، مجبوره که این اعضا رو با استفاده از Object Initializer مقداردهی کنه.
public class Asset
{
public required string Name;
}
// درسته
Asset a1 = new Asset { Name = "House" };

// خطای زمان کامپایل! چون Name مقداردهی نشده
Asset a2 = new Asset();


💡نکته: برای اینکه به یک سازنده اجازه بدید این قانون رو دور بزنه، می‌تونید از اتریبیوت [SetsRequiredMembers] استفاده کنید.

4️⃣ ترتیب دقیق ساخت (Deep Dive) 🔬

وقتی یه آبجکت فرزند ساخته میشه، ترتیب اجرای دقیق عملیات به این صورته:

فاز اول (از فرزند به پدر):
•فیلدهای کلاس فرزند مقداردهی اولیه میشن.

• آرگومان‌های پاس داده شده به base(...) ارزیابی میشن.

• این روند تا بالاترین کلاس در سلسله‌مراتب ادامه پیدا می‌کنه.

فاز دوم (از پدر به فرزند):
• حالا بدنه‌ی سازنده‌ها، از کلاس پدر شروع شده و به سمت کلاس فرزند اجرا میشن.

مثال کتاب برای درک بهتر این ترتیب:
public class B
{
int x = 1; // مرحله ۳: این اجرا میشه
public B(int x)
{
// مرحله ۴: بدنه سازنده پدر اجرا میشه
}
}
public class D : B
{
int y = 1; // مرحله ۱: این اول از همه اجرا میشه
public D(int x)
: base(x + 1) // مرحله ۲: آرگومان base ارزیابی میشه
{
// مرحله ۵: بدنه سازنده فرزند در آخر اجرا میشه
}
}

🤔 حرف حساب و تجربه شما
درک این جزئیات، شما رو به یک متخصص واقعی در طراحی کلاس‌های شیءگرا تبدیل می‌کنه.

🔖 هشتگ‌ها:
#OOP #Inheritance #BestPractices #Constructor