C# Geeks (.NET) – Telegram
لاگینگ در NET. - بهترین شیوه‌ها 📝

لاگ‌ها باید به شما در رفع باگ‌ها کمک کنند و توضیح دهند که اپلیکیشن در حال حاضر چه کاری انجام می‌دهد. آن‌ها نباید شما را در متن غرق کنند یا آن یک خطی را که نیاز دارید، پنهان کنند. این راهنما نشان می‌دهد که چگونه لاگینگ را در NET. مدرن راه‌اندازی کنید، پیام‌های ساختاریافته (structured) بنویسید، سطح مناسب را انتخاب کنید و request correlation را اضافه کنید. همچنین، لاگ‌ها را با متریک‌ها اشتباه نگیرید، اشتباه است که برای بررسی تعداد فراخوانی‌های API در هر بازه زمانی X، لاگ اضافه کنید.

شروع سریع (Minimal API) 🚀

// Program.cs
var builder = WebApplication.CreateBuilder(args);
// لاگینگ در کنسول به صورت پیش‌فرض فعال است؛ می‌توانید آن را از طریق appsettings.json تغییر دهید
var app = builder.Build();

app.MapGet("/", (ILogger<Program> log) =>
{
log.LogInformation("Health probe hit at {UtcNow}", DateTime.UtcNow);
return "OK";
});

app.Run();


این کد از قبل در کنسول با timestamp، سطح لاگ، دسته‌بندی و پیام می‌نویسد. پیکربندی
در appsettings.json قرار دارد.

سطوح لاگ که منطقی هستند

🔬 Trace :
بسیار پرحرف، جریان گام‌به‌گام. در پروداکشن خاموش است.
🐞 Debug :
در طول توسعه مفید است؛ بعداً می‌توان آن را با خیال راحت غیرفعال کرد.

ℹ️ Information :
رویدادهای سطح بالا: شروع برنامه، تکمیل درخواست، رویدادهای بیزینسی.

⚠️ Warning :
شرایط غیرعادی: تلاش‌های مجدد (retries)، تایم‌اوت‌هایی که بازیابی می‌شوند.

Error :
شکست‌هایی که شما catch و مدیریت می‌کنید (شامل exception).

🔥 Critical :
اپلیکیشن سالم نیست.

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

لاگینگ ساختاریافته بهتر از الحاق رشته است
قالب‌های پیام (Message templates)، داده‌ها را برای کوئری زدن در آینده در فیلدها نگه می‌دارند. در اینجا از درون‌یابی رشته (string interpolation) خودداری کنید.
//  خوب
log.LogInformation("User {UserId} logged in from {Ip}", userId, ip);

// بد (کوئری زدن سخت است)
log.LogInformation($"User {userId} logged in from {ip}");

با قالب‌ها، ابزارها می‌توانند بر روی UserId یا Ip بدون تجزیه متن، فیلتر کنند.
استثناها را به عنوان اولین آرگومان وارد کنید:
try
{
await service.ProcessAsync(orderId);
}
catch (Exception ex)
{
log.LogError(ex, "Failed to process order {OrderId}", orderId);
}


پیکربندی لاگینگ از طریق appsettings.json ⚙️

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
},
"Console": {
"FormatterName": "json",
"FormatterOptions": {
"IncludeScopes": true,
"UseUtcTimestamp": true
}
}
}
}


دسته‌بندی‌های پرسروصدا (مانند جزئیات داخلی فریم‌ورک) را به Warning کاهش دهید.
فرمت‌دهنده JSON با جمع‌آوری‌کننده‌های لاگ به خوبی کار می‌کند و فیلدها را ساختاریافته نگه می‌دارد.

دسته‌بندی‌ها و DI🗃

دسته‌بندی لاگر، نوعی است که شما درخواست می‌کنید. این به فیلتر کردن و گروه‌بندی لاگ‌ها کمک می‌کند.
public sealed class BillingService(ILogger<BillingService> log)
{
public void Charge(Guid orderId, decimal amount)
=> log.LogInformation("Charging {OrderId} {Amount}", orderId, amount);
}


Scopeها و Correlation IDها 🔗

اسکوپ ها پراپرتی‌های اضافی را به هر خط لاگ داخل یک بلوک متصل می‌کنند. یک Correlation ID برای هر درخواست اضافه کنید تا بتوانید یک مسیر را در سراسر ماژول‌ها ردیابی کنید.
app.Use(async (ctx, next) =>
{
var cid = ... // Get Correlation ID from header or create a new one

using (loggerFactory.CreateLogger("Correlation").BeginScope(new Dictionary<string, object?>
{
["CorrelationId"] = cid
}))
{
// ...
await next();
}
});

حالا هر خط لاگ در آن درخواست شامل CorrelationId است.
Middleware لاگینگ درخواست (داخلی) 🌐


using Microsoft.AspNetCore.HttpLogging;

builder.Services.AddHttpLogging(o =>
{
o.LoggingFields = HttpLoggingFields.RequestMethod
| HttpLoggingFields.RequestPath
| HttpLoggingFields.ResponseStatusCode
| HttpLoggingFields.Duration;
});
var app = builder.Build();
app.UseHttpLogging();


این متد، مسیر، وضعیت و مدت زمان را ثبت می‌کند. از لاگ کردن bodyها خودداری کنید مگر اینکه دلیل محکمی داشته باشید.

مسیرهای پرترافیک: LoggerMessage.Define ⚡️

با پیش‌کامپایل کردن قالب‌های پیام، از تخصیص حافظه برای فرمت‌دهی رشته جلوگیری کنید. سورس جنریتور کد لاگینگ بهینه‌ای ایجاد می‌کند.
static class Logs
{
private static readonly Action<ILogger, string, Exception?> _cacheMiss =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(1001, "CacheMiss"),
"Cache miss for key {Key}");

public static void CacheMiss(this ILogger log, string key) => _cacheMiss(log, key, null);
}

// استفاده
log.CacheMiss(key);


لاگ‌های فایلی (اختیاری، با Serilog) 📂

ارائه‌دهندگان داخلی در فایل نمی‌نویسند. اگر می‌خواهید فایل‌های چرخشی (rolling files) به صورت محلی داشته باشید، Serilog را اضافه کنید:
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.File

در برنامه:
using Serilog;

var logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
.CreateLogger();

builder.Host.UseSerilog(logger);



✅️چه چیزی را لاگ کنیم (و چه چیزی را نه)❌️


👍 لاگ کنید: رویدادهای شروع/پایان، فراخوانی‌های خارجی (هدف + مدت زمان)، رویدادهای بیزینسی، هشدارها با زمینه، خطاهای مدیریت شده با stack traces.

👎 لاگ نکنید: اسرار (secrets)، body کامل درخواست/پاسخ با اطلاعات شخصی، حلقه‌های پرحرف، اسپم heartbeat.

اشتباهات رایج 🤦‍♂️

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

• لاگ کردن استثناها بدون پاس دادن خود آبجکت exception.

• نداشتن ارتباط (correlation) بین لاگ‌های یک درخواست.

• تبدیل همه چیز به Information یا Debug و هرگز کوتاه نکردن آن.

• نوشتن تصادفی اسرار در لاگ‌ها (توکن‌ها، پسوردها).

🔖 هشتگ‌ها:
#CSharp #DotNet #ASPNETCore #Logging #StructuredLogging #Observability #Serilog #Debugging
📖 سری آموزشی کتاب C# 12 in a Nutshell
👻 مبحث پیشرفته #C: فاینالایزرها (~Finalizers) و آخرین کلمات یک آبجکت


ما معمولاً نگران از بین بردن آبجکت‌ها در #C نیستیم، چون Garbage Collector (GC) این کار رو به صورت خودکار برامون انجام میده. اما گاهی وقتا یه آبجکت، منابعی خارج از کنترل دات‌نت (unmanaged resources) مثل دستگیره‌های فایل یا اتصالات شبکه رو مدیریت می‌کنه.

اینجا پای فاینالایزر (Finalizer) به میدون باز میشه.

1️⃣ فاینالایزر چیست؟

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

سینتکس فاینالایزر، اسم کلاسه که قبلش یه علامت مد (~) اومده:
class MyClass
{
~MyClass()
{
// کد پاک‌سازی منابع در اینجا قرار می‌گیرد
Console.WriteLine("Finalizing object!");
}
}

💡نکته فنی: این سینتکس در واقع یه میانبر شیک در #C برای override کردن متد Finalize از کلاس Object هست.

2️⃣ چرا تقریباً هیچوقت نباید فاینالایزر بنویسید؟ (مهم!) ⚠️


در ۹۹.۹٪ مواقع، شما به عنوان یک توسعه‌دهنده #C نباید مستقیماً فاینالایزر بنویسید. دلیلش اینه:

• ضربه به پرفورمنس: آبجکت‌هایی که فاینالایزر دارن، فرآیند پاک‌سازی حافظه توسط GC رو پیچیده و کند می‌کنن.

• غیرقطعی بودن: شما هیچ کنترلی روی اینکه فاینالایزر دقیقاً چه زمانی اجرا میشه، ندارید. ممکنه خیلی دیرتر از چیزی که انتظار دارید، اجرا بشه.

راه حل صحیح و مدرن برای مدیریت منابع، پیاده‌سازی اینترفیس IDisposable و استفاده از دستور using هست که قبلاً در موردش صحبت کردیم.

3️⃣ پس کی به درد می‌خوره؟ (تنها کاربرد منطقی) 💡

تنها کاربرد منطقی فاینالایزر، به عنوان یه مکانیسم پشتیبان یا Safety Net هست.

یعنی شما کلاس خودتون رو IDisposable می‌کنید و انتظار دارید که کاربر همیشه متد Dispose() رو (معمولاً با using) صدا بزنه. اما برای محکم‌کاری، یه فاینالایزر هم می‌نویسید که اگه کاربر یادش رفت Dispose() رو صدا بزنه، فاینالایزر به عنوان آخرین امید، اون منابع رو آزاد کنه تا از نشت منابع (resource leaks) جلوگیری بشه.

🤔 حرف حساب و تجربه شما
فاینالایزرها مثل دکمه Eject صندلی خلبان هستن؛ امیدوارید هیچوقت لازم نشه ازش استفاده کنید، ولی خوبه که بدونید وجود داره.

🔖 هشتگ‌ها:
#CSharp #DotNet #MemoryManagement #GarbageCollector
ساخت یک توزیع‌کننده (Dispatcher) سفارشی Domain Events در NET. 🧱


Domain events
یک راه قدرتمند برای جداسازی (decouple) بخش‌های مختلف سیستم شما هستند. به جای اتصال سفت و سخت منطق خود، می‌توانید رویدادها را منتشر کرده و بخش‌های دیگر کدتان را به آن رویدادها مشترک (subscribe) کنید. این الگو به ویژه در طراحی دامنه محور (Domain-Driven Design - DDD) ارزشمند است، جایی که منطق بیزینس باید متمرکز و منسجم باقی بماند.

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

📌ما پوشش خواهیم داد:


🔹 چرا ممکن است بخواهید از publish-subscribe در اپلیکیشن خود استفاده کنید.

🔹 نحوه تعریف انتزاع‌های پایه رویداد دامنه.

🔹 نحوه پیاده‌سازی و ثبت handlerها.

🔹 نحوه ساخت یک توزیع‌کننده رویدادهای دامنه.

🔹 مزایا و معایب و زمان در نظر گرفتن گزینه‌های دیگر.

چرا Domain Events اهمیت دارند؟ 🤔

قبل از شیرجه زدن در پیاده‌سازی، بیایید مشکلی را که رویدادهای دامنه حل می‌کنند، درک کنیم. این کد با اتصال سفت و سخت را در نظر بگیرید:
public class UserService
{
public async Task RegisterUser(string email, string password)
{
var user = new User(email, password);
await _userRepository.SaveAsync(user);

// 🔗 مستقیماً به سرویس ایمیل متصل است
await _emailService.SendWelcomeEmail(user.Email);

// 🔗 مستقیماً به سرویس آنالیتیکس متصل است
await _analyticsService.TrackUserRegistration(user.Id);

// اگر بخواهیم ویژگی‌های بیشتری اضافه کنیم چه؟
// این متد به رشد خود ادامه خواهد داد...
}
}


با رویدادهای دامنه، می‌توانیم این را جدا کنیم:
public class UserService
{
public async Task RegisterUser(string email, string password)
{
var user = new User(email, password);
await _userRepository.SaveAsync(user);

// رویداد را منتشر کن - بگذار بخش‌های دیگر سیستم واکنش نشان دهند
await _domainEventsDispatcher.DispatchAsync(
[new UserRegisteredDomainEvent(user.Id, user.Email)]);
}
}


اکنون UserService فقط بر روی ثبت نام کاربر تمرکز دارد، در حالی که دغدغه‌های دیگر از طریق event handlerها مدیریت می‌شوند.

انتزاع‌های پایه 🧬

بیایید با تعریف دو اینترفیس ساده که پایه و اساس سیستم رویداد ما را تشکیل می‌دهند، شروع کنیم:
// اینترفیس نشانگر برای تمام رویدادهای دامنه.
public interface IDomainEvent { }

// اینترفیس جنریک برای مدیریت رویدادهای دامنه.
public interface IDomainEventHandler<in T> where T : IDomainEvent
{
Task Handle(T domainEvent, CancellationToken cancellationToken = default);
}


پیاده‌سازی Handlerهای نمونه 📧📊

بیایید چند handler نمونه اضافه کنیم که نشان می‌دهند چگونه بخش‌های مختلف سیستم شما می‌توانند به یک رویداد یکسان واکنش نشان دهند:
// ارسال ایمیل خوش‌آمدگویی هنگام ثبت نام کاربر
internal sealed class SendWelcomeEmailHandler(...)
: IDomainEventHandler<UserRegisteredDomainEvent>
{
// ...
}

// ردیابی ثبت نام کاربر جدید در آنالیتیکس
internal sealed class TrackUserRegistrationHandler(...)
: IDomainEventHandler<UserRegisteredDomainEvent>
{
// ...
}


برای اینکه این کار کند، باید handlerهای خود را در کانتینر DI ثبت کنیم. می‌توانید این ثبت را با اسکن اسمبلی با Scrutor خودکار کنید. نکته مهم این است که چندین handler می‌توانند به یک رویداد یکسان واکنش نشان دهند.
توزیع‌کننده (Dispatcher) (Strongly Typed) 🧠

حالا به چیزی نیاز داریم که فراخوانی handlerها را هماهنگ کند.
public interface IDomainEventsDispatcher
{
Task DispatchAsync(IEnumerable<IDomainEvent> domainEvents, CancellationToken cancellationToken = default);
}
internal sealed class DomainEventsDispatcher(IServiceProvider serviceProvider)
: IDomainEventsDispatcher
{
// ... (پیاده‌سازی پیچیده با Wrapper برای جلوگیری از reflection در زمان اجرا)
}


این توزیع‌کننده از یک wrapper برای حذف reflection در حین اجرای handler استفاده می‌کند در حالی که ایمنی نوع را حفظ می‌کند. این به ما مزایای عملکردی اجتناب از reflection در مسیر اصلی (اجرای handler) را می‌دهد. فراموش نکنید که توزیع‌کننده را با DI ثبت کنید.

مثال استفاده 🎯

در اینجا نحوه استفاده از توزیع‌کننده رویدادهای دامنه در اپلیکیشن شما آمده است:
public class UserController(...) : ControllerBase
{
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] RegisterUserRequest request)
{
// کاربر را ایجاد کن
var user = await userService.CreateUserAsync(request.Email, request.Password);

// رویداد دامنه را منتشر کن
var userRegisteredEvent = new UserRegisteredDomainEvent(user.Id, user.Email);
await domainEventsDispatcher.DispatchAsync([userRegisteredEvent]);

return Ok(...);
}
}


محدودیت‌ها و مزایا و معایب ⚠️

این پیاده‌سازی کاملاً درون-فرآیندی (in-process) اجرا می‌شود، که پیامدهای مهمی دارد:

• بازخورد فوری: اگر هر handler شکست بخورد، استثنا بلافاصله به فراخواننده برمی‌گردد.

• کنترل فراخواننده: کدی که رویدادها را توزیع می‌کند، تصمیم می‌گیرد چگونه با شکست‌ها برخورد کند.

• نگرانی‌های قابلیت اطمینان: اگر فرآیند پس از موفقیت برخی handlerها اما قبل از تکمیل دیگران کرش کند، بازیابی خودکار وجود ندارد.

برای عوارض جانبی حیاتی که نباید از دست بروند، الگوی Outbox 📬 را در نظر بگیرید.

جمع‌بندی 📝

Domain events
یک الگوی قدرتمند برای جداسازی منطق بیزینس هستند و شما برای استفاده مؤثر از آن‌ها به یک فریم‌ورک سنگین نیاز ندارید. پیاده‌سازی‌ای که ما در اینجا ساختیم، یک پایه محکم فراهم می‌کند.

زیبایی ساختن راه‌حل خودتان این است که شما هر قطعه را درک می‌کنید، که دیباگ و سفارشی‌سازی را ساده می‌کند. این الگو به طور عالی در سیستم‌های Domain-Driven Design و Clean Architecture که در آن‌ها جداسازی منطق بیزینس حیاتی است، جای می‌گیرد.

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

🔖 هشتگ‌ها:
#CSharp #SoftwareArchitecture #DomainDrivenDesign #DDD #DomainEvents #CleanArchitecture #CQRS
📖 سری آموزشی کتاب C# 12 in a Nutshell

🏗 تقسیم کدهای بزرگ با partial در #C: کلاس‌ها و متدهای چندتکه

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

سی‌شارپ برای این کار یه راه حل عالی و تمیز داره: کلمه کلیدی partial.

1️⃣ کلاس‌های Partial:

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

مهم‌ترین کاربرد: جداسازی کدهای دست‌نویس شما از کدهای اتوماتیک.

فایل ۱ (اتوماتیک): PaymentForm.g.cs
// این بخش توسط یک ابزار ساخته شده
public partial class PaymentForm
{
// ... کنترل‌های دیزاینر و کدهای اتوماتیک
}

فایل ۲ (دست‌نویس): PaymentForm.cs
// این بخش رو شما می‌نویسید
public partial class PaymentForm
{
// ... منطق و ایونت‌های مربوط به فرم
}


2️⃣ متدهای Partial: قلاب‌های جادویی که ناپدید میشن! 🎩

این قابلیت به شما اجازه میده در یک بخش از کلاس (معمولاً کد اتوماتیک)، یک "قلاب" یا تعریف متد بدون بدنه بذارید. بعداً در بخش دیگه کلاس (کد دست‌نویس)، می‌تونید اون متد رو پیاده‌سازی کنید.

💡نکته جادویی: اگه شما هیچ پیاده‌سازی‌ای براش ارائه ندید، کامپایلر هم تعریف و هم تمام فراخوانی‌های اون متد رو از کد نهایی حذف می‌کنه! این یعنی هیچ هزینه پرفورمنسی نداره.

فایل ۱ (اتوماتیک):
public partial class PaymentForm
{
public void Submit()
{
// یه قلاب برای اعتبارسنجی قبل از ارسال
ValidatePayment(this.Amount);
// ...
}
// تعریف متد partial (بدون بدنه)
partial void ValidatePayment(decimal amount);
}

فایل ۲ (دست‌نویس):
public partial class PaymentForm
{
// پیاده‌سازی متد partial
partial void ValidatePayment(decimal amount)
{
if (amount > 1000)
{
// ... منطق اعتبارسنجی ...
}
}
}

3️⃣ متدهای Partial توسعه‌یافته (از 9 #C) 🚀

این نسخه جدیدتر، برای کار با Source Generatorها طراحی شده. در این حالت، شما تعریف متد رو می‌نویسید و انتظار دارید که Source Generator بدنه اون رو تولید کنه. این متدها دیگه اختیاری نیستن و باید پیاده‌سازی بشن و می‌تونن خروجی و پارامتر out هم داشته باشن.

🔖 هشتگ‌ها:
#CSharp #Programming #DotNet #CleanCode #Partial
5 شیوه برتر (Best Practice) برای لاگینگ ساختاریافته بهتر با Serilog 📝


سریلاگ یک کتابخانه لاگینگ ساختاریافته (structured logging) برای NET. است.Serilog از مقصدهای لاگینگ زیادی به نام Sinks پشتیبانی می‌کند. مقصدهای لاگ از سینک‌های کنسول و فایل گرفته تا سرویس‌های مدیریت شده لاگینگ مانند Application Insights را شامل می‌شود.
امروز، می‌خواهم ۵ نکته عملی برای لاگینگ ساختاریافته بهتر با Serilog را به اشتراک بگذارم.

1️⃣ از سیستم پیکربندی (Configuration System) استفاده کنید ⚙️


شما می‌توانید Serilog را در ASP.NET Core به دو روش پیکربندی کنید:
سیستم پیکربندی _ Fluent API

Fluent API
به شما امکان می‌دهد با کد، Serilog را به راحتی پیکربندی کنید. عیب آن این است که شما پیکربندی خود را هاردکد می‌کنید. هرگونه تغییر در پیکربندی نیازمند دیپلوی یک نسخه جدید است.
من ترجیح می‌دهم از سیستم پیکربندی ASP.NET برای راه‌اندازی Serilog استفاده کنم. مزیت آن این است که می‌توانید پیکربندی لاگینگ را بدون دیپلوی مجدد اپلیکیشن خود تغییر دهید.
شما باید کتابخانه Serilog.Settings.Configuration را نصب کنید.
این به شما امکان می‌دهد Serilog را با استفاده از سیستم پیکربندی، کانفیگ کنید:
builder.Host.UseSerilog((context, loggerConfig) =>
loggerConfig.ReadFrom.Configuration(context.Configuration));


در اینجا یک پیکربندی Serilog با سینک‌های Console و Seq آمده است. ما همچنین چند Serilog enricher را برای غنی‌سازی لاگ‌های اپلیکیشن با اطلاعات اضافی پیکربندی می‌کنیم.
{
"Serilog": {
"Using": ["Serilog.Sinks.Console", "Serilog.Sinks.Seq"],
"MinimumLevel": {
"Default": "Information",
"Override": { "Microsoft": "Information" }
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "Seq",
"Args": { "serverUrl": "http://localhost:5341" }
}
],
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"]
}
}


2️⃣ از لاگینگ درخواست Serilog استفاده کنید 🌐


شما می‌توانید کتابخانه Serilog.AspNetCore را نصب کنید تا لاگینگ Serilog را برای پایپ‌لاین درخواست ASP.NET Core اضافه کنید. این قابلیت، عملیات داخلی ASP.NET را به همان سینک‌های Serilog که رویدادهای اپلیکیشن شما ثبت می‌شوند، اضافه می‌کند.
تنها کاری که باید انجام دهید، فراخوانی متد UseSerilogRequestLogging است:
app.UseSerilogRequestLogging();


3️⃣ لاگ‌های خود را با CorrelationId غنی‌سازی کنید 🔗


چگونه می‌توانید تمام لاگ‌های مربوط به یک درخواست واحد را ردیابی کنید؟
شما می‌توانید یک پراپرتی CorrelationId به لاگ‌های ساختاریافته خود اضافه کنید.
این همچنین در چندین اپلیکیشن کار می‌کند. شما باید CorrelationId را با استفاده از یک هدر HTTP پاس دهید. برای مثال، می‌توانید از یک هدر سفارشی X-Correlation-Id استفاده کنید.

در RequestContextLoggingMiddleware، من CorrelationId را به LogContext Serilog اضافه می‌کنم. این کار آن را برای تمام لاگ‌های ایجاد شده در طول این درخواست اپلیکیشن در دسترس قرار می‌دهد.
public class RequestContextLoggingMiddleware
{
private const string CorrelationIdHeaderName = "X-Correlation-Id";
// ...
public Task Invoke(HttpContext context)
{
string correlationId = GetCorrelationId(context);
using (LogContext.PushProperty("CorrelationId", correlationId))
{
return _next.Invoke(context);
}
}
// ...
}
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 است

بقيه‌اش رو شما بگید.👇🏻