Event Sourcing: ثبت داده ها بر اساس رویدادها
«نسخه کامل و جامع این مقاله به همراه جزئیات بیشتر، در imaniNova.ir منتشر شده است. در ادامه قطعه ای از این مقاله را مشاهده می کنید.»
در طی چند دهه گذشته، اکثر نرم افزارها بر پایه الگویی بنام CRUD (Create, Read, Update, Delete) توسعه یافته اند. وقتی کاربر پروژه ای تغییر آدرس می دهد، فیلد آدرس در بانک اطلاعاتی آپدیت می شود؛ وقتی خریدی انجام می دهد، دیتابیس موجودی حساب را کم می کند. این مدل به ظاهر ساده، شفاف و کارآمد است؛ اما یک چالش بنیان افکن در خود جای داده است: فقدان حقیقت تاریخی.
وقتی شما یک سطر از دیتابیس را ویرایش می کنید، وضعیت قبلی آن داده برای همیشه نابود می شود؛ مگر اینکه کدهای پیچیده ای برای لایه های جانبی مثل Audit Logging بنویسید که تازه آن هم معمولا ناقص یا جدا از جریان اصلی نرم افزار است. در سیستم های انترپرایز بزرگ مانند سامانه های بانکی، بورس، بیمه، مدیریت زنجیره تامین و فروشگاه های آنلاین، دانستن اینکه «در گذشته چه اتفاقی افتاده و چرا به وضعیت فعلی رسیده ایم» به اندازه وضعیت فعلی سیستم اهمیت دارد.
اینجاست که الگوی معماری Event Sourcing وارد میدان می شود. در این مقاله قصد داریم به طور کاملا تخصصی، عمیق و کاربردی، مفاهیم، ساختار، مزایا، چالش ها و پیاده سازی این الگوی قدرتمند را بررسی کنیم و ببینیم چطور می توان با کنار گذاشتن تفکر سنتی CRUD، سیستم های پایدارتر و انعطاف پذیرتری طراحی کرد.
Event Sourcing و تفاوت بنیانی آن با CRUDبرای درک عمیق مفهوم Event Sourcing، بهتر است آن را با دفاترداری حسابداری سنتی مقایسه کنیم. یک حسابدار حرفه ای هیچ گاه وقتی شما از حسابتان پول برمی دارید، مانده قبلی را با غلط گیر پاک نمی کند و عدد جدید را بنویسد! او هر عملیات (واریز، برداشت، کارمزد) را در یک سطر جدید ثبت می کند. مانده حساب شما در هر لحظه، حاصل جمع و تفریق تمامی سطرهای قبلی است. این دقیقا همان تفکر پشت Event Sourcing است.
در تفکر سنتی (State-based Persistence)، دیتابیس مخزنی از «وضعیت ها» است. در تفکر رویدادمحور (Event-based Persistence)، دیتابیس مخزنی از «اتفاقات متوالی» است.
ساختار فنی و آناتومی یک رویداد (Domain Event)
در معماری Event Sourcing، تمامی عملیاتی که رخ می دهند در قالب شیءهایی به نام Domain Event بسته بندی و ذخیره می شوند. نام رویدادها باید به صورت زمان گذشته (Past Tense) انتخاب شوند زیرا اتفاقی را توصیف می کنند که در گذشته واقع شده و دیگر قابل تغییر یا انکار نیست.
یک رویداد استاندارد معمولا شامل فیلدهای زیر است:
- Event ID: شناسه یکتا برای خود رویداد.
- Aggregate ID: شناسه موجودیت اصلی که رویداد متعلق به آن است (مثلا شماره حساب یا شماره سفارش).
- Event Type: نوع یا نام رویداد (مثلا OrderPlaced یا AccountDebited).
- Sequence Number / Version: شماره ترتیب یا نسخه رویداد در زنجیره موجودیت برای حفظ همگام سازی و جلوگیری از Concurrency.
- Timestamp: زمان دقیق وقوع رویداد.
- Payload / Data: اطلاعات و بدنه اصلی مرتبط با اتفاق رخ داده.
در متدولوژی Domain-Driven Design (DDD)، رویدادها نماینده «واقعیت های ثبت شده» (Facts) هستند.
- دستور (Command): یک درخواست است که ممکن است رد شود (مثلا P
laceOrderیا WithdrawMoney). دستورات مربوط به آینده/حال هستند و احتمال خطا دارند. - رویداد (Event): اتفاقی است که اعتبارسنجی شده، انجام گرفته و تمام شده است (مثلا O
rderPlacedیا MoneyWithdrawn).
اصل فنی: رویدادها غیرقابل تغییر (Immutable) هستند. شما نمی توانید گذشته را پاک کنید یا تغییر دهید؛ فقط می توانید با ثبت یک رویداد جدید (مثلا OrderCancelled) اثر آن را جبران (Compensate) کنید.
۲. بررسی عمیق و لایه ای فیلدهای یک Domain Event
یک رویداد استاندارد از دو بخش اصلی تشکیل می شود: متا دیتا (Envelope / Header) و بدنه اصلی (Payload / Data).
الف) بخش متادیتا (Metadata Envelope)
فیلدهایی که برای مدیریت زیرساختی، ردیابی (Tracing) و ترتیب دهی رویدادها استفاده می شوند:
eventId(GUID/UUID): برای شناسایی منحصربه فرد هر رویداد. این شناسه جهت رعایت اصل Idempotency (جلوگیری از پردازش تکراری یک رویداد در مصرف کننده ها) حیاتی است.aggregateId: مشخص می کند این رویداد مربوط به کدام موجودیت اصیلی (Aggregate Root) است. مثلا تمام رویدادهای مربوط به یک سفارش مشخص، دارای یک aggregateIdیکسان هستند تا بتوان Stream مربوط به آن سفارش را بازخوانی کرد.eventType: نام کلاس یا نوع رویداد برای Deserialization و Route کردن رویداد به Handler مناسب.version/sequenceNumber: شماره ترتیب رویداد روی یک Aggregate مشخص (از ۱ شروع می شود).
کاربرد حیاتی: جلوگیری از Optimistic Concurrency Control (OCC). اگر دو درخواست هم زمان بخواهند رویدادی با نسخه ۳ ثبت کنند، دیتابیس دومین درخواست را رد می کند تا تداخل داده پیش نیاید.timestamp: زمان دقیق وقوع (ترجیحا به فرمت UTC) برای کوئری های بازسازی زمانی (Time Travel) و آنالیزهای قانونی.correlationIdوcausationId(پیشرفته):
correlationId: یک کد پیگیری مشترک برای کل جریان کاری (مثلا پردازش یک خرید از شروع تا انتها).
causationId:شناسه دستور یا رویدادی که مستقیم باعث ایجاد این رویداد جدید شده است (برای ساخت درخت ردیابی/Tracing).- ادامه مقاله در
- https://imaninova.ir/ArticleView?event-sourcing-architecture-guide