Message، Command یا Event؟ کدوم رو انتخاب کنم؟
مطالب آموزشی Event Sourcing:
- بخش اول مقدمهای بر Event Sourcing
- بخش دوم آشنایی مقدماتی با ساختار داخلی Event Store
- مقایسه رویکردهای State-Oriented و State-Transition
- مزیتهای Event Sourcing
- سلام به دنیا به روش Event Sourcing
- سلام به دنیا به روش Event Sourcing-بخش دوم
- بخش هفتم Projection
- ویرایش event ها در EventSourcing
- Message، Command یا Event، کدوم رو انتخاب کنم؟
مقدمه
در سیستمهای مبتنی بر معماری Event-Driven Architecture یا EDAها مکررا در مورد Message و Command و Event میشنویم. شاید در نگاه اول تفاوت بین اینها کاملا بدیهی و واضح باشد. اما گاهی اوقات فهم تفاوت بین اینها میتواند تریکی باشد. در این مقاله از سری مقالات Event Sourcing به تفاوت بین این سه موضوع مهم میپردازم.
- Message
با Message شروع میکنم. اگر به Wikipedia نگاهی بندازیم، به همچین پاراگرافی بر میخوریم:
A message is a discrete unit of communication intended by the source for consumption by some recipient or group of recipients. A message may be delivered by various means, including courier, telegraphy, carrier pigeon and electronic bus. A message can be the content of a broadcast. An interactive exchange of messages forms a conversation
Message یکی از مکانیزمهای مهم برقراری ارتباط بین بخشهای مختلف در یک نرمافزار است. ارتباط بین یک برنامه کلاینت و سرور بر اساس تبادل و ارسال و دریافت پیام است.
همچنین در OOP ارتباط بین objectها از طریق ارسال پیام انجام میشود.
“… was about messaging, not objects and drew a parallel to biological cells. …”
Alan Kay
مثال دیگر که هر روز با آن سروکار داریم، پیامهای HTTP است. دو نوع پیام Http داریم: Http Request و Http Response. همانطور که میتوانیم ببینیم این دو پیام جهت برقراری ارتباط بین برنامههای کلاینت و سرور مورد استفاده قرار میگیرند.
مثالی از ارسال و دریافت پیام در http را در زیر میتوانید مشاهده کنید.
همانطور که مشاهده میکنید Message در اینجا ابزاری است برای بیان اینکه به طرف مقابل اعلام کنید چه درخواستی دارید.
پس هدف، نیت و درخواست شما مهمترین بخش یک Message است. میتوان این جمله را اینطور نیز بیان کرد. هدف ما جهت انجام کاری در قالب یک Message بیان میشود.
تصمیم ما جهت انجام کاری در قالب یک Message پیادهسازی میشود.
مثلا قصد داریم لیستی از برنامه زمانبندی یک دکتر را در سایت https://xyz.com مشاهده کنیم. میتوانیم تصمیم خود را در قالب Http Request Message زیر پیادهسازی کرده و اجرا کنم:
Request URL: https://xyz.com/api/doctor/foo/schedule?date=2023-07-07
Request Method: GET
Referrer Policy: strict-origin-when-cross-origin
Path: api/doctor/foo/schedule
Scheme: https
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
- یک Message از چه چیزی تشکیل شده است؟
هر Message از دو بخش اصلی و مهم تشکیل شده است. یکی نام یا ماهیت Message است. همانطور که در بالا هم اشاره کردم این نام در واقع بیان کننده یک تصمیمی و یا اعلام کننده یک خبری است که در قالب پیام پیاده شده است. پس بهتر است در انتخاب نام مناسب دقت لازم را به خرج دهید. نام Message باید بیان کننده هدف و کانتکست باشد. Intention revealing name.
بخش دیگر هر پیام payload است. تمامی اطلاعاتی که همراه پیام است و کانتکست آن پیام را مشخص میکند.
Message مکانیزمی عمومی جهت برقراری ارتباط بین بخشهای مختلف برنامه است. چه در سطح abstract جهت برقراری ارتباط بین سرویسها(بر روی HTTP یا به کمک پروتکل gRPC یا به واسطه Message broker و …) و چه در سطح جزئیتر. مثل صدا زدن یک متد از یک کلاس.
ماهیت ارتباط نیز در اینجا میتواند همزمان یا غیرهمزمان (Sync یا Async)باشد.
در اینجا ما تفاوتی بین انواع تصمیمات و درخواست هایی که در قالب Message بیان میشود نداریم. چه لیستی از نوبتهای یک دکتر را بخواهیم مشاهده کنیم. چه بخواهیم یک نوبت را رزرو کنیم. یا اینکه پس از رزرو یک نوبت بخواهیم یک نوتیفیکشن برای دکتر و شخص رزرو کننده ارسال شود. همه اینها در قالب یک Message مدل میشوند.
بصورت خلاصه تا اینجا فهمیدیم که ارتباط بین بخشهای مختلف برنامه از طریق تبادل Message انجام میپذیرد. طبیعی است که بسته به نوع پیام یه به عبارتی دیگر بسته به نوع تعاملی که برقرار میشود، انتظار میرود که سیستم rection متفاوتی از خود نشان دهد. به عنوان مثال در تصویر بالا، پیامی که به سیستم میگوید: “بالانس فعلی حساب را به من نشان بده”، نسبت به پیامی که به سیستم میگوید: “۵۰$ را از حسابم به حساب فرد دیگر انتقال بده”، معانی کاملا متفاوتی برای سیستم خواهند داشت. این معانی اما در Message بصورت ضمنی-implicit وجود دارند. در دو بخش بعدی سعی میکنم بر روی دو نوع از تبادلاتی که از طریق Message انجام میشود تمرکز کرده و آنها را explicit کنم.
از آنجایی که موضوع این مقاله نیست، کوتاه بگم که به پیامهایی از جنس “ساپورت کردن تصمیمگیری کاربر” در فرهنگ واژگان Command-Query Responsibility Segregationیا CQRS، Query میگیم. این جنس پیامها بصورت ساده اطلاعاتی هستند که به کاربر در گرفتن تصمیمات مبتنی بر داده(Data-Driven Decision) کمک میکند. در مثال بالا، پیغام”بالانس حسابم را بهم نشان بده” یک Message از نوع کوئری است.
- Command
Command بصورت خلاصه اشاره به نوعی از Message دارد که در آن هدف ما اعمال کردن و به انجام رساندن تصمیمی است که گرفتیم. مثلا، در مثال بالا که در تصویر هم مشاهده میکنید، من تصیمم گرفتیم که ۵۰$ از حسابم به حساب نفر دیگری به عنوان قرض انتقال بدهم. این تصمیم در قالب یک Command به سیستم داده میشود. انتظاری که در این نوع پیامها داریم این است که در صورتی که همه شرایط برقرار باشد، در نهایت پول از حساب من کسر شده و به حساب طرف مقابل انتقال داده شود. در اینجا منظور از شرایط همان Acceptance Criteria است. به عنوان مثال به اندازه کافی پول در حساب من موجود باشد. حساب من و طرف مقابل فعال باشد و … .
Command تصمیماتی است که ما با این هدف که سیستم را از یک state به state دیگری ببریم میگیریم. در مثال بالا، پس از انتقال پول، بالانس حساب من ۵۰ دلار کاهش یافته و بالانس حساب شخصی که پول را بهش قرض دادهام ۵۰ دلار افزایش پیدا میکند. مثالهای دیگری از command شامل موارد زیر است:
- صندلی پرواز را رزرو کن
- سفارش را ثبت کن
- سفارش را کنسل کن
- سند حسابداری را در دفتر روزنامه ثبت کن
- آدرس من را تغییر بده
- کد تخفیف را بر روی سفارش اعمال کن
همانطور که میبینید وجه مشترک تمامی مثالهای بالا در این است که ما در نهایت انتظار داریم که در صورت اجرای موفق state، command سیستم نیز تغییر کند. به بیان دیگر proof of work در اینجا رفتن سیستم به state جدید است.
مهمترین و کلیدیترین نکتهی دربارهی این نوع از messageها این است که ما نسبت به نتایج care هستیم. تصمیم را به شکل دستوری به سیستم میدهیم. پولی را جابجا کن، این صندلی را رزرو کن، این محصول را به سفارشم اضافه کن و … . بهمین خاطر نامگذاری آنها عموما از قاعده زیر پیروی میکند:
A verb + a noun or a clause
- Fill + The Order
- Cancel + The Order
- Change + My Address
- Record + The Financial Transaction into The Ledger
- Calculate + The Salary of sb
طبیعت این نوع پیامها میطلبد که بصورت Sync پیادهسازی شوند. به این دلیل که نتیجهی آنها برای ما در همان لحظه مهم میباشد. Sync بودن بدین معنی است که ما منتظر پاسخ خواهیم ماند. این منتظر ماندن میتواند بصورت همزمان یا غیر همزمان باشد. یعنی در حین منتظر ماندن کار دیگری ممکن است انجام دهیم یا بیکار میمانیم و منتظر پاسخ command هستیم.
بصورت خلاصه، command نوعی از message است که در آن ما دستوری را به سیستم صادر میکنیم که نسبت به نتیجه آن care هستیم. به بیان دیگری از سیستم بصورت دستوری انتظار داریم کاری که بهش اعلام کردیم را انجام دهد.
- Event
Event اشاره به نوعی از message دارد که در آن ما قصد این را داریم که نتیجهی وقوع یک خبری را به بقیه اعلان کنیم. مثلا اعلام کنیم که: پولی بین دو حساب منتقل شده است، یا سفارشی ثبت شده است، یا سندی در دفتر روزنامه در حسابداری به ثبت رسیده، پروازی رزرو شده، آدرس کاربر تغییر کرده و … .
همانطور که میتوان مشاهده کرد در اینجا هدف ما این نیست که تصمیمی را در قالب یک دستور به سیستم اعلام کنیم و انتظار داشته باشیم که سیستم آن را اجرا کند. بلکه در عوض در این سناریوها اکثر مواقع event ماحصل اجرا شدن یکی از تصمیمات ما در سیستم است. به عنوان مثال ما به سیستم commandی را فرستادیم که پولی را بین دو حساب جابجا کن، بعد از اجرای موفقیت آمیز این دستور، event پول بین حسابها جابجا شد به بیرون خبر داده میشود.
میتوان اینطور برداشت کرد که event نتیجهی حاصل از تصمیمی بوده که ما لحظاتی قبل در قالب command به سیستم ارسال کردیم.
این منجر به یک تفاوت اساسی و بسیار مهم Event و Command میشود. بر خلاف command ما در اینجا نسبت به نتیجهی اعلان عمومی یک event یا بیان دقیقتر اتفاقاتی که باید پس از اعلان عمومی این event رخ بدهد، بی تفاوت هستیم. ما فقط خبر میدهیم که: پولی با موفقیت بین دو حساب جابجا شد.
این Event بیان کنندهی factهایی هستند که در سیستم رخداده اند. بهمین خاطر شکل نوشتار آنها بصورت noun + verb(past form) میباشد.
Money + is transferred
Order + is placed
Order + is canceled
ماهیت اطلاع رسانی event ایجاب میکند که شکل آن بصورت Async و fire-and-forget است. شما به دنیای بیرون(و درون) خبر میدهید که یک سفارش خریدی ثبت شده است. سایر ماژولهای سیستم اقدام مقتضی را در برابر دریافت این event انجام میدهند. به بیان دیگر این event باعث تریگر شدن فرآیندهایی در سایر ماژولها خواهد شد. ولی شما نیازی نیست که نسبت به انها care باشید.
Event گاهی اوقات ممکن است به دلایلی از جمله رسیدن به یک پوینت زمانی خاص رخ بدهد. مثلا سفارشی ثبت شده است. فرض کنید یک acceptance criteria داریم که هر سفارش پس از ثبت تا ۳۰ دقیقه زمان دارد که پرداخت شود در غیر اینصورت سفارش لغو میشود. در این مثال در صورتی که ۳۰ دقیقه از ثبت یک سفارشی گذشته باشد ولی کاربر اقدام به پرداخت و نهایی کردن آن نکند، سفارش بصورت اتوماتیک لغو میشود. نتیجه این لغو شدن نیز در قالب یک Event به دنیای بیرون(و درون!) اعلام میشود.
بصورت خلاصه ما وقتی از event استفاده میکنیم که قصد داشته باشیم وقوع یک تغییر state در سیستم را به بقیه اعلام کنیم. نکته مهمتر اینکه در این حالت ما نسبت به اقدامات بعدی که باید در سیستم رخ دهد care نیستیم.
پایان بخش نهم