پیادهسازی Event Sourcing در .NET Core: راهنمای جامع
مفاهیم اساسی Event Sourcing
Event Sourcing به معنی ذخیرهسازی تمام رویدادهایی است که وضعیت یک موجودیت را تغییر دادهاند، به جای ذخیرهسازی وضعیت نهایی آن.
رویدادها (Events)
رویدادها قلب Event Sourcing هستند. یک رویداد:
-
غیرقابل تغییر (Immutable) است: پس از وقوع، نمیتوان آن را تغییر داد یا حذف کرد.
-
بیانگر گذشته (Past Tense) است: نامگذاری رویدادها باید عملی را که انجام شده، توصیف کند (مثلاً OrderCreated یا FundsDeposited).
-
حاوی داده (Data) است: تنها شامل اطلاعات لازم برای توصیف عمل انجام شده است و مستقیماً پایگاه داده را بهروزرسانی نمیکند.
جریان رویداد (Event Stream)
مجموعه مرتبشدهای از رویدادها که وضعیت یک موجودیت خاص (مثلاً یک حساب بانکی یا یک سفارش) را از ابتدا تا انتها نشان میدهد. وضعیت فعلی موجودیت، از طریق بازپخش (Replay) این رویدادها به ترتیب زمانی، بازسازی میشود.
مزایای کلیدی Event Sourcing
-
مسیر ممیزی کامل (Full Audit Trail): از آنجایی که هر تغییر ثبت میشود، یک تاریخچه کامل از عملکرد سیستم در دسترس است. این امر برای سیستمهای مالی یا حقوقی بسیار حیاتی است.
-
بازسازی وضعیت (State Reconstruction): میتوان وضعیت سیستم را در هر نقطه زمانی در گذشته بازسازی کرد. این قابلیت یک "ماشین زمان اشکالزدایی" فراهم میکند.
-
مقیاسپذیری و کارایی: عملیات نوشتن (Write Operations) صرفاً الحاق رویدادها هستند که معمولاً سریعتر از بهروزرسانی وضعیت درجا هستند و تداخل کمتری دارند.
-
انعطافپذیری برای مدلهای خواندنی: میتوان مدلهای خواندنی (Read Models) جدیدی را در آینده با استفاده از بازپخش رویدادهای تاریخی ایجاد کرد، بدون نیاز به تغییر مدل اصلی (Write Model).
ترکیب Event Sourcing با CQRS در .NET Core
Event Sourcing اغلب با الگوی Command Query Responsibility Segregation (CQRS) ترکیب میشود تا سیستم کارآمدتری ایجاد شود. .NET Core بستر بسیار مناسبی برای پیادهسازی این ترکیب است.
CQRS و ES
CQRS مسئولیت عملیاتهای خواندن (Queries) و نوشتن (Commands) را در مدلهای جداگانه تقسیم میکند.
-
مدل فرمان (Command Model/Write Side):
-
دستورات (Commands) دریافت میشوند (مثلاً CreateOrderCommand).
-
دستور توسط ریشه تجمیع (Aggregate Root) پردازش میشود.
-
به جای تغییر مستقیم وضعیت، ریشه تجمیع رویدادهایی را منتشر میکند (مثلاً OrderCreatedEvent).
-
این رویدادها در Event Store (مثلاً EventStoreDB، Azure Cosmos DB یا PostgreSQL با Marten) ذخیره میشوند.
-
-
مدل پرس و جو (Query Model/Read Side):
-
رویدادها توسط Event Handlers مصرف میشوند.
-
این Handlers دادهها را برای نمایش در مدلهای خواندنی (Read Models) بهینهسازی شده برای پرس و جو (مثلاً با استفاده از Entity Framework Core، MongoDB یا Redis) ذخیره میکنند.
-
پیادهسازی در .NET Core (مثال ساختاری)
برای پیادهسازی این ساختار در .NET Core، میتوان از الگوها و کتابخانههای زیر استفاده کرد:
-
Dependency Injection: برای مدیریت وابستگیها در سراسر سیستم (.NET Core این قابلیت را به صورت داخلی دارد).
-
MediatR/MassTransit: برای ارسال دستورات، پرس و جوها و رویدادها در داخل یا بین سرویسها.
1. تعریف رویدادها (Events)
رویدادها کلاسهای دادهای سادهای هستند که اطلاعات تغییر وضعیت را در خود دارند.
public abstract record Event(Guid AggregateId, int Version);
public record OrderCreatedEvent(
Guid AggregateId,
int Version,
DateTime CreationDate,
Guid CustomerId
) : Event(AggregateId, Version);
// ... سایر رویدادها
2. ریشه تجمیع (Aggregate Root)
ریشه تجمیع مسئول اجرای منطق دامنه و اعمال رویدادها برای بازسازی وضعیت است. در C#، این کار با استفاده از متدهای Apply انجام میشود.
public class OrderAggregate : AggregateRoot
{
private Guid CustomerId { get; set; }
private OrderStatus Status { get; set; } = OrderStatus.Pending;
// متد بازسازی وضعیت از رویدادها
public void Apply(OrderCreatedEvent @event)
{
Id = @event.AggregateId;
Version = @event.Version;
CustomerId = @event.CustomerId;
Status = OrderStatus.Pending;
}
// متد منطق کسب و کار که رویداد تولید میکند
public static OrderAggregate Create(Guid customerId)
{
var aggregate = new OrderAggregate();
var @event = new OrderCreatedEvent(Guid.NewGuid(), 1, DateTime.UtcNow, customerId);
aggregate.ApplyChange(@event); // ثبت رویداد برای ذخیرهسازی
return aggregate;
}
// ... سایر متدها
}
3. ذخیرهسازی رویداد (Event Store)
اینترفیس ذخیرهسازی رویداد باید قابلیتهای زیر را داشته باشد:
-
SaveEvents(Guid aggregateId, IEnumerable<Event> events, int expectedVersion): ذخیره رویدادهای جدید.
-
GetEventsForAggregate(Guid aggregateId): بازیابی جریان رویداد برای بازسازی وضعیت.
public interface IEventStore
{
Task SaveEventsAsync(Guid aggregateId, IEnumerable<Event> events, int expectedVersion);
Task<List<Event>> GetEventsForAggregateAsync(Guid aggregateId);
}
4. مدلهای خواندنی (Read Models) و Event Handlers
Event Handlers رویدادها را دریافت کرده و مدلهای خواندنی را بهروزرسانی میکنند. این مدلها بهینهسازی شدهاند تا پرس و جوهای سریع و پیچیده را پشتیبانی کنند.
public class OrderCreatedHandler : IEventHandler<OrderCreatedEvent>
{
private readonly IReadModelRepository _readModelRepository;
public OrderCreatedHandler(IReadModelRepository readModelRepository)
{
_readModelRepository = readModelRepository;
}
public async Task HandleAsync(OrderCreatedEvent @event)
{
// تبدیل رویداد به یک مدل خواندنی ساده
var orderReadModel = new OrderReadModel
{
Id = @event.AggregateId,
CustomerId = @event.CustomerId,
Status = "Pending"
};
await _readModelRepository.AddOrderAsync(orderReadModel);
}
}
چالشها و بهترین روشها در .NET Core
پیادهسازی Event Sourcing نیازمند درک عمیقی از اصول معماری است و چالشهایی را نیز به همراه دارد.
1. سازگاری رویداد (Event Versioning)
با تکامل سیستم، ساختار رویدادها ممکن است نیاز به تغییر داشته باشد. این یکی از بزرگترین چالشهای Event Sourcing است، زیرا رویدادها غیرقابل تغییرند.
-
بهترین روش: از یک استراتژی سازگاری تدریجی استفاده کنید. مطمئن شوید Event Handlers شما میتوانند نسخههای قدیمی رویدادها را هم مدیریت کنند. میتوان یک فیلد Version به هر رویداد اضافه کرد.
2. بازسازی وضعیت (State Reconstruction Performance)
در سیستمهای بزرگ، بازپخش صدها یا هزاران رویداد برای بازسازی وضعیت یک موجودیت میتواند پرهزینه باشد.
-
بهترین روش: استفاده از Snapshotting. هر چند وقت یکبار، وضعیت فعلی (Snapshot) موجودیت را ذخیره کنید تا هنگام بازسازی، تنها رویدادهای بعد از آخرین Snapshot بازپخش شوند.
3. سازگاری نهایی (Eventual Consistency)
در معماری CQRS و ES، مدل نوشتن و مدل خواندن از هم جدا هستند. این یعنی پس از ذخیره یک رویداد، بهروزرسانی مدل خواندن مدتی طول میکشد.
-
بهترین روش: پذیرش و مدیریت این سازگاری نهایی. رابط کاربری (UI) باید برای این تاخیرهای کوتاه آماده باشد و بتواند وضعیت را بهطور مناسبی نشان دهد (مثلاً با نمایش پیام "در حال پردازش").
4. تراکنشهای توزیع شده (Distributed Transactions)
اگر Event Sourcing با میکروسرویسها ترکیب شود، هماهنگی بین سرویسها (تراکنشهای توزیع شده) چالشبرانگیز میشود.
-
بهترین روش: استفاده از الگوی Saga برای مدیریت جریانهای کاری طولانی مدت و هماهنگی توزیعشده با استفاده از رویدادها.
ابزارهای Event Sourcing در اکوسیستم .NET
در .NET Core، چندین ابزار و کتابخانه برای تسهیل پیادهسازی Event Sourcing وجود دارد:
| ابزار | توضیحات |
| EventStoreDB | یک پایگاه داده رویداد اختصاصی با کارایی بالا که برای Event Sourcing بهینهسازی شده است. بهترین انتخاب برای Event Store. |
| Marten | یک کتابخانه C# که PostgreSQL را به یک Event Store و Document Store قدرتمند تبدیل میکند. استفاده از Marten در پروژههای .NET Core بسیار رایج است. |
| MediatR | یک پیادهسازی ساده از الگوی Mediator در C# که به خوبی برای جدا کردن Command Handlers و Event Handlers استفاده میشود. |
| MassTransit/Rebus | فریمورکهای Service Bus که برای انتقال و مدیریت رویدادها بین سرویسها در یک معماری رویدادمحور (Event-Driven Architecture) استفاده میشوند. |
| Entity Framework Core (EF Core) | میتواند برای پیادهسازی مدلهای خواندنی ساده (Read Models) در کنار پایگاه داده رابطهای استفاده شود. |
جمعبندی
پیادهسازی Event Sourcing در .NET Core با ترکیب اصول Domain-Driven Design (DDD)، CQRS، و استفاده از ابزارهای بومی پلتفرم مانند Dependency Injection و کتابخانههای تخصصی مانند Marten یا EventStoreDB، امکان ایجاد سیستمهای بسیار قدرتمند و انعطافپذیر را فراهم میکند. اگرچه پیچیدگیهای معماری را افزایش میدهد، اما مزایای آن در ایجاد سیستمهایی که نیاز به مقیاسپذیری بالا، ممیزی کامل و انعطافپذیری در تکامل دارند، ارزش سرمایهگذاری را دارد. برای شروع، توصیه میشود با یک پروژه کوچک و ساده Event Sourced، مفاهیم اساسی مانند Aggregate Root و Event Stream را به صورت عملی تجربه کنید.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.