Inside Event vs Outside Event
مطالب آموزشی Event Sourcing:
- بخش اول مقدمهای بر Event Sourcing
- بخش دوم آشنایی مقدماتی با ساختار داخلی Event Store
- مقایسه رویکردهای State-Oriented و State-Transition
- مزیتهای Event Sourcing
- سلام به دنیا به روش Event Sourcing
- سلام به دنیا به روش Event Sourcing-بخش دوم
- بخش هفتم Projection
- ویرایش event ها در EventSourcing
- Message، Command یا Event، کدوم رو انتخاب کنم؟
- بخش دهم: Internal Event vs External Event
. مقدمه
در بخش نهم در مورد تفاوت بین Message، Command و Event صحبت کردیم.
همینجا اشاره کنم در این مقاله به موضوع مهم Eventهای داخلی و خارجی میپردازم. در این مقاله عبارت “سرویس، ماژول، مایکروسرویس و Bounded context” همگی اشاره به یک موضوع دارند.
- Event
هر چند در بخشهای قبلی بصورت مفصل در مورد چیستی event صحبت کردم، اما اجازه بدید با این شروع کنیم که منظور از یک event چیست. در context این مقاله وقتی از event صحبت میکنیم در حقیقت از رویدادی صحبت میکنیم که قبلا اتفاق افتاده و به اتمام رسیده است. پس میتوان گفت event همان حقایق(fact)های رخداده هستند. صرفنظر از صحیح یا اشتباه بودن این رویدادها، تاثیر آنها هیچوقت نباید از بین برود. منظور از اشتباه بودن یک event این است، که مثلا به هر دلیلی ممکن است در سناریویی مثل انتقال پول از حساب Masoud به حساب Martin، پول اشتباهی به حساب Sarah منتقل شود. دلیل این امر که در هر صورت آن event را نباید پاک/ویرایش کرد این است که تا به این لحظه که این مقاله نگارش میشود، ماها هیچکدوم نتوانستیم در زمان به عقب برگردیم و کار اشتباهی که کردیم را پاک/ ویرایش کنیم. در این مورد بصورت مفصلتر در اینجا صحبت کرد. مثالهایی از event شامل موارد زیر میتواند باشد:
- A Product was added
- An Accounting transaction was posted
- The/A Mouse was clicked
- The User was deactivated
- A flight seat was reserved
- The financial period was closed
وقتی از event صحبت میکنیم دقیقا منظور همین مثالهایی است که در بالا آورده شده است. خبری را اعلام میکنیم که یک اتفاقی در دومین رخداده است. ممکن است در کنار این خبر دادن، جزئیات بیشتری از اتفاقی که رخداده است را نیز به دیگر اعلام کنیم. مثلا علاوه بر اینکه خبر بدهیم که ماوس کلیلک شده است، جزئیاتی در مورد مکانی که ماوس کلیلک شده است را نیز خبر بدهیم.
به این جزئیات که همراه یک event اطلاع رسانی میکنیم، payload میگوئیم. به این event اصطلاحا Carried-State event میگوئیم.
- Inside and Outside Date
Pet Halland اولین بار سال ۲۰۰۵ در مقاله ” Data on the Outside versus Data on the Inside“ به اهمیت تفکیک دیتا درون مرز یک سرویس و دیتاهای بیرون آن مرز پرداخت. Pet در این مفاله به اهمیت عنصر زمان به هنگام نگرش به دیتاها پرداخت. او زمان رو به دو مقوله “Then and Now” تقسیم و از این منظر به دیتاهایی که یک application با آنها مواجه میشود نگاه کرد.
By the time you see a distant object, it may have changed!
By the time you see a message, the data may have changed!
- Pet Halland
بصورت ساده Pet با استفاده از دو مفهوم ساده “Then and Now” دیتاها را از صرفنظر از نوع آنها، کسی که آنها را تولید کرده و … در دو وضعیت که توسط Pet،”Then and Now” نامیده بود، دسته بندی کرد. اهمیت این تفکیک و نگرش در این است که نسبت به تصمیمات طراحی مبتنی بر داده، علیالخصوص در سیستمهای توزیعشده بیشتر care باشیم.
- اهمیت این تفکیک در چیست؟
بهتر است قبل از ادامه دادن به این سوال فکر کنیم که چرا تفکیک eventها به eventهای درونی و بیرونی میتواند مهم باشد. بخش قبل را با این جمله تمام کردم: “اهمیت این تفکیک و نگرش در این است که نسبت به تصمیمات طراحی مبتنی بر داده، علیالخصوص در سیستمهای توزیعشده بیشتر care باشیم.“ این جمله کمی مبهم و گنگ است.
با چند مثال به این مسئله نگاه میکنم. فرض کنید که در حال رزرو یک صندلی پرواز هستید. بعد از اینکه یک صندلی از یک پرواز را انتخاب میکنید، معمولا این صندلی برای شما برای مدتی رزرو خواهد ماند، تا اینکه خرید را نهایی کنید.
Eventهای این چنینی، مربوط به language داخلی یک مایکروسرویس/bounded context هستند، و نباید به بیرون expose شوند. اینکه یک صندلی پرواز برای مدت کوتاهی در رزرو مسافر میماند تا خریدش را نهایی کند، مربوط به سرویس reservation میباشد. مثلا در این بازه زمانی که هنوز مسافر خریدش را تکمیل نکرده است، نباید برای وی بلیطی صادر شود، یا شرکت هواپیمایی از رزرو صندلی توسط این مسافر خبر دار شود. سایر side effectهایی که پس از خرید بلیط توسط مسافر اتفاق میافتد تنها وقتی معتبر هستند که وضعیت خرید توسط آن مسافر نهایی شود.
پس بصورت خلاصه در اینجا ما eventهایی ممکن است سروکار داشته باشیم، که مربوط به language و ماهیت وظایف داخلی آن سرویس هستند و نیازی نیست و نباید به دنیای بیرون اطلاع رسانی شوند.
یک مثال دیگر را بررسی کنیم. فرض کنید کاربری در سایت ما ثبتنام میکند. کاربران بسته به شرایط ممکن است در وضعیتهای مختلفی از جمله فعال یا غیر فعال و … قرار بگیرند. یکی از راههای طراحی event برای این مثال میتواند بصورت زیر باشد:
همانطور که میبینید در event اولی خبری در مورد وضعیت کاربر ثبتنامی وجود ندارد و نمیدانیم که وضعیت این کاربر فعال است یا غیر فعال. پابلیش کردن این event به دنیای بیرون باعث میشود که استفاده کنندگان دچار سر درگمی شوند. در حقیقت آنها یا باید دستی وضعیت کاربر را از سرویسی که آن event را پابلیش کرده است بگیرند، یا منتظر event بعدی باشند. که این مورد دوم، مشکلات طراحی جدیتر دیگری دارد. یکی از این مشکلات tight coupling از نوع temporal بوجود میآید. ترتیب و توالی رویدادها بسیار مهم خواهند شد. و مورد دیگر اینکه سرویس دوم مجبور است که دانش طراحی سرویسی که این eventها رو تولید و پابلیش کرده است را نیز بصورت جزئی بداند. این مورد باعث میشه که سرویسی که event ها را پابلیش کرده است نسبت به استفاده کنندگان از eventها stable شود. در اینجا stable در معنای منفی مد نظر است. زیرا شما دیگر براحتی نمیتواند تصمیمات طراحی که نسبت به ریز دانگی eventهای خود گرفتید را بدون هماهنگی با سایر سرویسها، تغییر دهید.
پس بصورت خلاصه ریز دانگی eventهایی داخلی یک bounded context نسبت به eventهایی که به جهت اطلاع رسانی به دینای بیرون وجود دارند، متفاوت میباشد.
ذکر این نکته هم در اینجا خالی از لطف نیست، که مسئله بالا رو میتوان به روشهای دیگری هم طراحی کرد که مشکل مطرح شده نیز مرتفع شود. اما بصورت کلی ریز دانگی eventهای یک دومین نسبت به eventهایی که به بیرون پابلیش میکنیم متفاوت هستند.
در مورد نامگذاری و همچنین نحوهی طراحی eventها و تفاوت در معنی event در کانتکستهای متفاوت، همانطور که قبلا هم قول دادم، حتما در پستی جداگانه بصورت مفصل صحبت خواهم کرد.
سناریوی دیگری را بررسی کنیم. فرض کنید که مسافری یکی از صندلیهای یک کوپه از قطار را رزرو کرده است. ممکن است جهت راحتی کار برای دریافت کننده این event در کنار دیتایی که به همراه این event به بیرون پابلیش میکنیم اطلاعات بیشتری از جمله تعداد صندلیهای خالی قابل رزرو در آن کوپه را هم قرار دهیم.
چالشی که در این حالت ممکن است بوجود بیاید این است که لزوما دریافت کنندهی این event نمیتواند مطمئن باشد که وضعیت نهایی آن کوپه قطار همان تعدادی است که در event دریافتی قید شده است. ممکن است در این بازه مسافر یا مسافران دیگری سایر صندلیهای آن کوپه را اشغال کرده باشند. این چالش علیالخصوص برای consumerهایی که خارج از سرویس رزرو قطار دارند، بیشتر به چشم میخورد. در اصل نمیتوان مطمئن بود که دیتایی که دریافت کردیم آخرین وضعیت و اخبار اتفاق افتاده در سرویسی است که آن event را منتشر کرده است. معمولا در این موارد موقع expose کردن یک event به بیرون اطلاعاتی این چنینی رو درون payload آن ایونت قرار نمیدهیم. در عوض لینکی در اختیار دریافت کنده event قرار میگیرد تا در صورت نیاز بتواند جزئیات مورد نظر را از طریق آن لینک دریافت کند.
بصورت خلاصه در حالت سوم ما با شرایطی مواجه هستیم که ممکن است وقتی eventی را از یک سرویس خارجی دریافت میکنیم، وضعیت آن سرویس تغییر کرده باشد. در همچین مواردی بهتر است در صورت نیاز به اطلاعات و جزیئات بیشتر در مورد آن event از طریق api آخرین وضعیت مورد نظر را دریافت کنیم.
- Inside Event
بصورت کلی Internal Event اشاره به تمامی eventهایی دارد که مصارف داخلی در یک bounded context دارد. اینها همان Domain Event هستند. اگر از EventSourcing استفاده میکنید، ایونتهایی که درون Event Store یک Bounded Context هستند نیز از دسته Inside Event هستند. تمامی eventهایی که توسط یک Aggregate تولید میشوند و خود Aggregate یا Projector مرتبط با آن از آن استفاده میکند. اینها از دسته eventهای داخلی هستند که بهتر است یا مستقیم به بیرون پابلیش نشوند یا با دقت این اتفاق بیافتد. همچنین ایونتهایی که جهت برقراری یکپارچی(integrity) بین aggregateهای یک bounded context نیز استفاده میشوند نیز در همین دسته قرار میگیرند.
مثالهایی از این دست eventها میتواند شامل موارد زیر باشد:
- An Item added to a basket
- A flight seat is reserved in pending state
- A new package is created
- An accounting entry is added to a financial transaction
- A new hotel’s photo is uploaded
- The Employee’s working hour’s is updated
- An item removed from the basket
- Outside Event
منظور از Outside event تمامی eventهایی که به جهت ایجاد یکپارچگی در بین bounded contextها مورد استفاده قرار میگیرد. به این دسته integration event نیز گفته میشود. در هر bounded context همیشه ما فقط نیاز داریم که فقط در صورتی که فرآیندها به یک مرحله خاصی رسیده باشند، به دنیای بیرون خبر بدهیم که یک اتفاق مهم در کسبوکار رخ داده است. به عنوان یک سند حسابداری فقط در صورتی که به مرحلهای رسیده باشد که آن سند را در journal پست کرده باشیم، به دنیای بیرون خبر میدهیم که همچین سندی در journal رکورد شده است. یا یک سبد خرید تنها وقتی توسط کاربر نهایی شد، به دنیای بیرون خبر داده میشود. صندلیهای رزرو شده یک قطار یا اتوبوس یا هواپیما پس از تائید نهایی توسط مسافر جهت خرید، به دنیای بیرون خبر داده میشود. وقتی تمامی ریویوهای یک مقاله به اتمام رسیده باشد و مقاله نمره قابل قبولی توسط داوران کسب کند، به دنیای بیرون خبر داده میشود.
در تمامی این مثالها اتفاقات خیلی زیادی تا قبل از رسیدن به مرحله مورد نظر جهت اطلاع رسانی به بیرون ممکن است رخ بدهد. مثلا ممکن است یک سند حسابداری بارها و بارها مورد بررسی و تغییر قرار بگیرد. آیتمهای آن کم یا زیاد شوند. تمامی این رخدادها بصورت یک internal event مدل میشوند که استفاده داخلی در همان bounded context را دارند. ما نیازی نداریم که این اتفاقات را به بیرون منعکس کنیم. ممکن است کاربر بارها و بارها اقدام به حذف یا اضافه کردن یک کالا به سبد خرید کند. تعداد آیتمها رو بیشتر یا کمتر کند و .. . در اینجا هم وضعیت تفاوتی ندارد. نیازی نداریم که این اتفاقات را به دنیای بیرون مخابره کنیم. تمامی این رویدادها باید توسط همان bounded contextی که وظیفه مدیریت سبد خرید مشتری را دارد استفاده(و البته تولید) شود.
مثالهایی از این دست eventها میتواند شامل موارد زیر باشد:
- A new financial transaction is posted to the journal
- The user is relocated to new address
- The salary is calculated
- The article is accepted by Jury
در مورد نحوهی پیادهسازی این دو روش در مقاله بعدی به تفضیل صحبت خواهم کرد.
پایان بخش دهم