Post #466
181

Horizontally Scaling ASP.NET Core APIs With YARP Load Balancing✨️
C# @csharpgeeks
Showing posts older than #467 · Back to latest


⚖️ The Trade-Off
🧠 When Idempotent Consumer Isn’t Needed
🧭 Takeaway
🔖هشتگها:
#IdempotentConsumer #Idempotency #DistributedSystems #MessageBroker
🧱 1️⃣ The Idempotency Key
if (await dbContext.MessageConsumers.AnyAsync(c =>
c.MessageId == context.MessageId &&
c.ConsumerName == nameof(NoteCreatedConsumer)))
{
return;
}
⚡️ 2️⃣ Atomic Side Effects + Idempotency Record
using var transaction = await dbContext.Database.BeginTransactionAsync();
// write tags
dbContext.Tags.AddRange(tagEntities);
// write message-consumer record
dbContext.MessageConsumers.Add(new MessageConsumer { ... });
await dbContext.SaveChangesAsync();
await transaction.CommitAsync();
چرا این مهم است؟ 🤔
📬 3️⃣ Handling At-Least-Once Delivery
🔄 Deterministic vs Non-Deterministic Handlers
🧩 1. استفاده از Idempotency Key در فراخوانی خارجی
await emailService.SendAsync(new SendEmailRequest
{
To = user.Email,
Subject = "Welcome!",
Body = "Thanks for signing up.",
IdempotencyKey = context.MessageId
});
💾 2. ذخیرهی Intent بهصورت محلی
⚙️ Publisher-Side Idempotency (بگذارید Broker آن را مدیریت کند)
var message = new NoteCreated(note.Id, note.Title, note.Content)
{
MessageId = Guid.NewGuid() // یا میتوانید از note.Id استفاده کنید
};
await publisher.PublishAsync(message);
🧠 Implementing an Idempotent Consumer in .NET
internal sealed class NoteCreatedConsumer(
TagsDbContext dbContext,
HybridCache hybridCache,
ILogger<Program> logger) : IConsumer<NoteCreated>
{
public async Task ConsumeAsync(ConsumeContext<NoteCreated> context)
{
// 1. بررسی اینکه آیا این پیام قبلاً توسط این consumer پردازش شده است
if (await dbContext.MessageConsumers.AnyAsync(c =>
c.MessageId == context.MessageId &&
c.ConsumerName == nameof(NoteCreatedConsumer)))
{
return;
}
var request = new AnalyzeNoteRequest(
context.Message.NoteId,
context.Message.Title,
context.Message.Content);
try
{
using var transaction = await dbContext.Database.BeginTransactionAsync();
// 2. پردازش قطعی (Deterministic): استخراج تگها از محتوای یادداشت
var tags = AnalyzeContentForTags(request.Title, request.Content);
// 3. ذخیرهسازی تگها در پایگاه داده
var tagEntities = tags.Select(ProjectToTagEntity(request.NoteId)).ToList();
dbContext.Tags.AddRange(tagEntities);
// 4. ثبت اینکه این پیام پردازش شده است
dbContext.MessageConsumers.Add(new MessageConsumer
{
MessageId = context.MessageId,
ConsumerName = nameof(NoteCreatedConsumer),
ConsumedAtUtc = DateTime.UtcNow
});
await dbContext.SaveChangesAsync();
await transaction.CommitAsync();
// 5. بهروزرسانی Cache
await CacheNoteTags(request, tags);
}
catch (Exception ex)
{
logger.LogError(ex, "Error analyzing note {NoteId}", request.NoteId);
throw;
}
}
}
🧩 الگوی Idempotent Consumer در NET. (و چرا به آن نیاز دارید)
🚨 چه چیزی ممکن است هنگام انتشار پیام اشتباه پیش برود؟
await publisher.PublishAsync(new NoteCreated(note.Id, note.Title, note.Content));


چرا YARP را انتخاب کنیم؟
🔖هشتگها:
#YARP #ReverseProxy
📌معرفی YARP
کارکرد یک Reverse Proxy
نحوه مدیریت HTTP توسط Reverse Proxy

public sealed class GetUserPermissionsConsumer(
IPermissionService permissionService)
: IConsumer<GetUserPermissions>
{
public async Task Consume(ConsumeContext<GetUserPermissions> context)
{
Result<PermissionsResponse> result =
await permissionService.GetUserPermissionsAsync(
context.Message.IdentityId);
if (result.IsSuccess)
{
await context.RespondAsync(result.Value);
}
else
{
await context.RespondAsync(result.Error);
}
}
}
💭 نتیجهگیری
🔖هشتگها:
#MassTransit #RequestResponse #MessagingPattern
🛰 پیامرسانی Request-Response با MassTransit
public record GetOrderStatusRequest
{
public string OrderId { get; init; }
}
public class GetOrderStatusRequestConsumer : IConsumer<GetOrderStatusRequest>
{
public async Task Consume(ConsumeContext<GetOrderStatusRequest> context)
{
// Get the order status from a database.
await context.ResponseAsync<GetOrderStatusResponse>(new
{
// Set the respective response properties.
});
}
}
👥 دریافت سطح دسترسی کاربران در Modular Monolith
internal sealed class PermissionService(
IRequestClient<GetUserPermissions> client)
: IPermissionService
{
public async Task<Result<PermissionsResponse>> GetUserPermissionsAsync(
string identityId)
{
var request = new GetUserPermissions(identityId);
Response<PermissionsResponse, Error> response =
await client.GetResponse<PermissionsResponse, Error>(request);
if (response.Is(out Response<Error> errorResponse))
{
return Result.Failure<PermissionsResponse>(errorResponse.Message);
}
if (response.Is(out Response<PermissionsResponse> permissionResponse))
{
return permissionResponse.Message;
}
return Result.Failure<PermissionsResponse>(NotFound);
}
}
🛰 الگوی Request-Response Messaging با MassTransit
📨 مقدمهای بر الگوی Request-Response Messaging Pattern
✅ مزایا:
⚠️ معایب:



🧠 چه زمانی از Value Objectها استفاده کنیم؟
public interface IPricingService
{
decimal Calculate(Apartment apartment, DateOnly start, DateOnly end);
}
public interface IPricingService
{
PricingDetails Calculate(Apartment apartment, DateRange period);
}
💾 ذخیرهسازی Value Objectها با EF Core
🧱 Owned Types
public void Configure(EntityTypeBuilder<Apartment> builder)
{
builder.ToTable("apartments");
builder.OwnsOne(property => property.Address);
builder.OwnsOne(property => property.Price, priceBuilder =>
{
priceBuilder.Property(money => money.Currency)
.HasConversion(
currency => currency.Code,
code => Currency.FromCode(code));
});
}
چند نکته در مورد Owned Typeها:
🧩 Complex Types
public void Configure(EntityTypeBuilder<Apartment> builder)
{
builder.ToTable("apartments");
builder.ComplexProperty(property => property.Address);
}
🧾 نتیجهگیری
🔖هشتگها:
#DomainDrivenDesign #ValueObject #CleanCode #EntityVsValueObject #ImmutableObjects
public record Address
{
private Address(
string street,
string city,
string state,
string country,
string zipCode)
{
Street = street;
City = city;
State = state;
Country = country;
ZipCode = zipCode;
}
public string Street { get; init; }
public string City { get; init; }
public string State { get; init; }
public string Country { get; init; }
public string ZipCode { get; init; }
public static Result<Address> Create(
string street,
string city,
string state,
string country,
string zipCode)
{
// بررسی معتبر بودن آدرس
return new Address(street, city, state, country, zipCode);
}
}
🏗 کلاس پایه (Base Class)
✨ مزایای استفاده از کلاس پایه
public abstract class ValueObject : IEquatable<ValueObject>
{
public static bool operator ==(ValueObject? a, ValueObject? b)
{
if (a is null && b is null)
{
return true;
}
if (a is null || b is null)
{
return false;
}
return a.Equals(b);
}
public static bool operator !=(ValueObject? a, ValueObject? b) =>
!(a == b);
public virtual bool Equals(ValueObject? other) =>
other is not null && ValuesAreEqual(other);
public override bool Equals(object? obj) =>
obj is ValueObject valueObject && ValuesAreEqual(valueObject);
public override int GetHashCode() =>
GetAtomicValues().Aggregate(
default(int),
(hashcode, value) =>
HashCode.Combine(hashcode, value.GetHashCode()));
protected abstract IEnumerable<object> GetAtomicValues();
private bool ValuesAreEqual(ValueObject valueObject) =>
GetAtomicValues().SequenceEqual(valueObject.GetAtomicValues());
}
🧩 نمونه پیادهسازی Value Object آدرس
public sealed class Address : ValueObject
{
public string Street { get; init; }
public string City { get; init; }
public string State { get; init; }
public string Country { get; init; }
public string ZipCode { get; init; }
protected override IEnumerable<object> GetAtomicValues()
{
yield return Street;
yield return City;
yield return State;
yield return Country;
yield return ZipCode;
}
}
💡Value Objects در .NET (مبانی DDD)
چند نمونهٔ رایج از Value Objectها:
🧠 Value Objectها چه هستند؟
✨ ویژگیهای اصلی Value Objectها
⚖️ برابری ساختاری (Structural Equality)
🧱 پیادهسازی Value Objectها
public class Booking
{
public string Street { get; init; }
public string City { get; init; }
public string State { get; init; }
public string Country { get; init; }
public string ZipCode { get; init; }
public DateOnly StartDate { get; init; }
public DateOnly EndDate { get; init; }
}
public class Booking
{
public Address Address { get; init; }
public DateRange Period { get; init; }
}
⚙️ استفاده از C# Records
public record Address(
string Street,
string City,
string State,
string Country,
string ZipCode);