قسمت ششم میکروسرویس‌ها: مدیریت داده‌ها در میکروسرویس‌ها

در قسمت اول از این مجموعه با هم در مورد کلیات این معماری صحبت کرده و مزایا و معایب این معماری را بررسی کردیم. در دومین قسمت به سراغ API Gatewayها، نقش آن‌ها در توسعه میکروسرویس و باید‌ها و نباید‌های یک API Gateway صحبت کردیم. سپس در قسمت سوم در مورد ارتباط بین سرویس‌ها و انواع روش‌های برقراری ارتباط صحبت کردیم و نهایتا در چهارمین قسمت در مورد تکنولوژی‌های توسعه میکروسرویس‌ها صحبت کردیم. سپس در پنجمین قسمت از این مجموعه در مورد Service Discovery مطالبی را بررسی کردیم. حالا در ششمین قسمت از این مجموعه قصد داریم مطالبی را در مورد داده‌ها و مدیریت گردش اطلاعات در میکروسرویس‌ها بیان کنیم.1. مقدمه: مشکلات داده‌های توزیع شده در میکروسرویس‌هابه طور معمول هنگامی که نرم افزاری را به روش monolithic توسعه می‌دهیم از دیتابیس‌های رابطه‌ای استفاده می‌کنیم که این پایگاه داده‌ها به صورت توکار ویژگی ACID را برای تراکنش‌ها به همراه می‌آورند. ACID که مخفف چهار کلمه atomicity، consistency، isolation و durability می‌باشد ضمانت‌هایی را به ما می‌دهد که در ادامه مختصری در موردشان صحبت خواهیم کرد.اول atomicity: به خاصیت همه یا هیچ نیز معروف است تضمین می‌کند که در یک تراکنش اگر چندین عملیات انجام شود، یا همه آن‌ها کامل انجام می‌شوند یا هیچ کدام اجرا نخواهند شد. برای مثال فرض کنید در یک ‍پایگاه داده لازم است اطلاعات یک جدول به روز رسانی شده و اطلاعات جدولی دیگر حذف شود و نتیجه انجام این دو عملیات در جدول سومی ثبت شود. در این شرایط یا هر سه عملیات کامل انجام می‌شود یا اگر به هر دلیلی هر کدام از اعمال انجام نشود، کل روال کنسل می شود و اگر فرض کنیم در مرحله حذف به مشکل برخورد کرده باشیم، اطلاعات به روز شده نیز مجدد به حالت اولیه خود باز می‌گردند.دوم consistency: به اصطلاح سازگاری به ما تضمین می‌دهد که اگر یک تراکنش کامل انجام شود قطعا پایگاه داده از یک وضعیت صحیح به وضعیت صحیح دیگری خواهد رفت. برای مثلا فرض کنید که در حساب شماره ۱ مبلغ ۱۰۰ تومان پول وجود دارد و در حساب شماره ۲ هم ۲۰۰ تومان پول وجود دارد و در مجموع ۳۰۰ تومان پول در بانک داریم. حال اگر مبلغ ۵۰ تومان از حساب یک به حساب دو منتقل شود بعد از تکمیل تراکنش قطعا در حساب ۱ مبلغ ۵۰ تومان و در حساب شماره ۲ مبلغ ۲۵۰ تومان وجود خواهد داشت و مبلغ کل کماکان ۳۰۰ تومان باقی می‌ماند و ممکن نیست مبلغ کل برای مثلا بعد از جابجایی مثلا ۳۲۰ تومان بشود.سوم isolation: تضمین می‌کند که در صورتی که چندین تراکنش در پایگاه داده همزمان اجرا شوند، این تراکنش ها از حضور یکدیگر اطلاع نداشته باشند و اجرای همزمان چندتراکنش تاثیر منفی در روند اجرای سایرین نداشته باشد. اگر هم نیاز باشد تراکنش ها تاثیری بر یکدیگر داشته باشند این تاثیر به شکلی است که در پایان با بررسی نتیجه گویا دستورات به صورت سریال انجام شده اند نه همزمان.چهارمdurability: به قانون پایداری معروف است به ما اطمینان می‌دهد بعد از پایان یک تراکنش نتیجه اجرای آن تحت هیچ شرایطی به طور ناخواسته از پایگاه داده حذف نخواهد شد و قاعدتا undo نمی‌شود.در نتیجه این ویژگی جذاب در یک برنامه monolithicبه سادگی می‌توانیم یک تراکنش را شروع کنیم و تعداد زیادی عملیات انجام دهیم و در پایان تراکنش را commit کنیم و خیالمان از همه اتفاقات پایگاه داده راحت باشد.ویژگی جذاب دیگری که در دیتابیس های رابطه‌ای در اختیار داریم زبان SQL است. شاید استفاده زیاد از این زبان ارزش‌ها و مزایای فوق العاده آن‌را کمرنگ کرده باشد، اما با کمی دقت خواهیم دید به کمک این زبان، گویی چوب جادو در دست داریم. با چند دستور داده‌ها را از چند جدول مختلف می‌خوانیم و ترکیب می‌کنیم و خلاصه سازی می‌کنیم و نیتجه را مشاهده می‌کنیم و در پشت صحنه query planner وظیفه تفسیر، بهینه‌سازی و برنامه ریزی برای اجرای دستورات ما را به عهده می‌گیرد. نیازی نیست که نگران هیچ یک از این مراحل باشیم، فقط کافیست چوب جادو را تکان دهیم و ورد مخصوص را بخوانیم.متاسفانه اگر از روش میکروسرویس برای توسعه نرم افزارهای خود استفاده کنیم بسیاری از این ویژگی‌ها را از دست خواهیم داد. هنگام توسعه میکروسرویس‌ها هر سرویس باید کل داده‌های خود را به صورت مستقل و در پایگاه داده ای مستقل در اختیار داشته باشد. اگر سرویسی به داده‌های سرویس دیگر نیاز داشته باشد، فقط از طریق APIهای ارائه شده اجازه دسترسی به داده‌های آن سرویس را خواهد داشت. این روش توسعه به ما اطمینان می‌دهد که هر کدام از سرویس‌ها به صورت مستقل امکان ارائه خدمات را خواهند داشت. در کنار این استقلال، با توجه به اینکه هر سرویس می‌تواند با ابزارها و فریم‌ورک‌های خاص خود توسعه داده‌ شود این امکان وجود دارد که برای هر سرویس و با توجه به نیاز‌های خاص آن از DB Engineهای مختلفی استفاده شود. ممکن است در یک سرویس از یک NoSQL خاص استفاده شود و سرویسی دیگر از یک پایگاه داده رابطه‌ای برای نگهداری داده‌های خود استفاده کند. برای مثال سرویسی که نیاز به جستجوی متن دارد ممکن است از Elastic Search استفاده کند. در سرویسی دیگر ممکن است نیاز به نگهداری روابط بین موجودیت‌ها داشته باشیم پس استفاده از یک گراف دیتابیس مثل Neo4J کار توسعه را ساده‌تر خواهد کرد. این ترکیب DB Engineهای مختلف در سیستم را اصطلاحا Polyglot Persistence می‌نامیم.هنگامی که از این روش‌های نگهداری داده‌ها استفاده می‌کنیم مزایای زیادی مثل توزیع پذیری، عدم وابستگی، بهروه‌وری بالاتر و ... را به دست خواهیم آورد. در کنار این مزایا اما چالش‌های زیادی برای نگهداری این داده‌های توزیع شده پیش رو خواهیم داشت.اولین و شاید بزگترین چالش در این راه مدیریت تراکنش‌‌هایی است که انجام مراحل و نگهداری داده‌های آن در چندسرویس مختلف انجام می‌شود. برای مثال فرض کنید یک سیستم ثبت سفاروش آنلاین داریم. در سرویس مدیریت مشتریان مشخصات مشتری و اعتبار آن‌ها نگهداری می‌شود. در سرویس سفارشات نیاز است داده‌های مشتری و اعتبار آن‌ها پیش از ثبت سفارش اعتبار سنجی شود. در یک برنامه monolithic به سادگی از جدولی از پایگاه داده کوئری می‌گیریم اما در یک برنامه میکروسرویس انجام این کار ممکن نیست. چگونگی جدا بودن اطلاعات این دو سرویس را در تصویر زیر مشاهده می‌کنید.هر سرویس داده‌های خود را نگهداری می‌کندهمانطور که در تصویر مشاهده می‌کنید، سیستم سفارشات اجازه دسترسی مستقیم به داده‌های سیستم مشتری‌ها را ندارد و تنها با استفاده از APIهای این سرویس می‌تواند از داده‌های آن استفاده کند. در مثال بالا سرویس سفارشات تنها نیاز به خواندن اطلاعات سرویس مشتری‌ها دارد. مشکل زمانی مشخص‌تر می‌شود که نیاز به تغییر داده‌های هر دو سرویس به صورت همزمان داشته باشیم. در همین مثال فرض کنید بعد از ثبت یک سفارش متناسب با رقم سفارش نیاز باشد که اطلاعات اعتبار مشتری نیز به روز شود. در چنین شرایطی به ناچار باید از تراکنش‌های توزیع شده یا اصطلاحا two-phase commit استفاده کنیم اما در صورتی که با تئوری CAP آشنا باشید میدانید که باید بین Availability و Consistency یکی را انتخاب کنید که معمولا برنده این رقابت Availability است.با توجه به اینکه برخی از NoSQLها از 2PC پشتیبانی نمی‌کنند و معمولا Availability مهم‌تر از Consistency است باید راهکار جدیدی برای این مهم پیدا کنیم.چالش دوم هنگامی معلوم می‌شود که نیاز داشته اطلاعاتی را از دو سرویس مختلف به دست بیاوریم. با کمی بررسی در مثال بالا کاملا واضح است که نیاز داریم در بخشی از برنامه، اطلاعات مشتریان و سفارشات آن‌ها همزمان نمایش داده شود.در صورتی که فقط بر اساس APIهای ارائه شده بخواهیم داده‌ها را نمایش دهیم باید در Application یک Join ایجاد کنیم و داده‌های هر مشتری را به سفارشات مشتری متصل کنیم.2. توسعه بر مبانی Eventها:در بسیاری از برنامه‌ها راهکار مشکل استفاده از Eventها است. در این روش‌ هر زمانی که یکی از داده‌های مهم سرویس به تغییر می‌کند یک Event در سیستم ایجاد و ارسال می‌شود و سایر سرویس‌های موجود در سیستم می‌توانند در صورت نیاز این Event را مدیریت کننده و داده‌های داخلی خود را با توجه به این Event به روز کنند. این تغییر داده‌ها خود می‌تواند موجب ایجاد چندید Event دیگر در سیستم شود.از Eventها برای پیاده سازی تراکنش‌های اپلیکیشن نیز می‌توان بهره برد. هر تراکنش شامل چند مرحله است. در هر مرحله در یک میکروسرویس داده‌‌هایی به روز می‌شوند و با تکمیل این مرحله یک Event ایجاد می‌شود که موجب فعال شدن مرحله بعد می‌شود و در نهایت با اتمام همه مراحل تراکنش به پایان می‌رسد.در تصویر زیر روال بررسی اعتبار لازم برای مشتری را در سیستم ثبت سفارش مشاهده می‌کنید. در این سیستم میکروسرویس‌ها از یک Message Broker برای انتقال Event ها استفاده می‌کنند. تولید و مدیریت سفارش به کمک Eventهادر این سیستم بعد از ایجاد یک سفارش یک Event برای سفارش ایجاد شده با وضعیت New ایجاد می‌شود.مدیریت کاربران Event را مدیریت کرده و پاسخ می‌دهددر پاسخ به رخداد قبلی، سرویس مدیریت کاربران مقدار مورد نیاز از اعتبار کاربر را رزرو کرده و Event متناسب با این تغییر را ارسال می‌کند.سرویس ثبت سفارش با توجه به نتیجه عملکرد صحیح انجام می‌دهدسرویس ثبت سفارش Event ارسالی از مدیریت مشتری را دریافت کرده و با توجه به داده‌‌های آن وضعیت سفارش را به OPEN تغییر می‌دهد.البته در سناریو‌های پیچیده‌تر و واقعی ممکن است مراحل و تغییر وضعیت‌هایی بیش از آنچه در این مثال دیدیم وجود دارد. مثلا اینکه این با رزرو اعتبار کاربر باید موجودی انبار بابت سفارش هم رزرو شود و ... که برای جلو گیری از پیچیده تر شدن مثال به همین اندازه اکتفا می‌کنیم.در این روش در ابتدا هر سرویس به صورت atomic داده‌های داخلی خود را تغییر می‌دهد و سپس یک Event در سیستم ایجاد می‌کند. در ادامه Message Broker تضمین می‌کند که این Event به تمامی سرویس‌ها برسد و در سومین مرحله هر سرویسی که این Event را دریافت کرده می‌تواند عملیات اختصاصی خود را به صورت Atomic انجام دهد و این چرخه را ادامه دهد. دقت کنید که به این روش نمی‌توانیم به خاصیت ACID برسیم و تنها چیزی که در این سیستم می‌شود صحت داده‌ها بعد از بازه ای از زمان است که اصطلاحا به این حالت eventual consistency می‌گویند.شما از این Eventها می‌توانید برای مدیریت داده‌های materialized view نیز استفاده کنید. materialized viewها که شامل داده‌های از پیش join شده و آماده مصرف هستند، در پایگاه داده‌هایی نگهداری می‌شوند که توسط همه یا برخی سرویس‌های حاضر در سیستم به صورت مشترک قابل دسترس است. سرویسی که مسئول به روز رسانی داده‌های Viewها است تقریبا تمامی Eventهای موجود در سیستم را دریافت و مدیریت می‌کند و با توجه به هر Event داده‌های صفر تا چندین View را به روز می‌کند. در شکل بعد سرویسی را مشاهده می‌کنید که مسئول به روز رسانی View مربوط به سفارشات مشتری‌ها است.نگهداری اطلاعات سفارشات و مشتری‌ها که در اختیار هر دو سرویس‌ استهنگامی که هر کدام از Eventهای مرتبط با Customer یا Order در سیستم رخ می‌دهد، این سرویس داده‌های این View را به روز می‌کند. داده‌های این View را می‌توانید در یک دیتابیس مبتنی بر سند مثل MongoDB نگهداری کنید و برای هر کاربر یک سند ثبت کنید که داخل خود آرایه ای از سفارشات دارد. توسعه سیستم‌ها به این روش مزایا و معایبی در پی خواهد داشت. توانایی ایجاد و مدیریت تراکنش‌هایی در سطح چندین سرویس و در نهایت دست‌یابی به Eventually Consistency یکی از بزرگترین مزایای این روش توسعه است. توانایی ایجاد و مدیریت materialized viewها و OLAPهای تقریبا همیشه به روز از دیگر ویژگی‌های برتر این روش توسعه است. در مقابل این مزایا پیچیدگی‌های فنی و غیرفنی پیاده سازی این روش یکی از بزرگترین معایب این روش توسعه است. نیاز به پیاده سازی روال‌هایی برای rollback کردن تراکنش‌هایی که به این روش پیاده سازی می‌شوند کاری سخت و زمانگیر است. ضمن اینکه در این سیستم سرویس‌ها باید توانایی کارکردن با داده‌هایی را داشته باشند که تراکنش آن‌ها هنوز به پایان نرسیده. در زمانی که هنوز یک تراکنش به پایان نرسیده سرویس‌هایی که از materialized viewها استفاده می‌کنند نیز ممکن است داده‌های غیر صحیح را مشاهده کنند. مشکل بعدی که ممکن است در سیستم بروز کند دریافت دوباره Eventها است. در واقع Message Broker تضمین می‌کند که Eventها حتما یک بار به سرویس‌ها می‌رسد اما تضمینی بابت جلوگیری از دوباره ارسال کردن یک Eventتکراری ارائه نمی‌کنند.3. ویژگی‌ Atomicity هنگام استفاده از Eventها:بیایید با هم کمی با جزئیات بیشتری به روال بالا دقت کنیم. ابتدا یک سفارش ثبت می‌شود و ثبت یک Event در سیستم ایجاد و ارسال می‌گردد. تا اینجای کار همه چیز خوب و عادی است اما اگر سفارش ثبت شود و سپس پیش از ایجاد و ارسال Event در سیستم دچار اختلال شویم چه اتفاقی می‌افتد؟ پاسخ ساده است داده‌های نامعتبر در سیستم ایجاد می‌شود. 3.1. ایجاد و ارسال Eventها در تراکنش‌های محلی:یک راه برای به دست آوردن atomicityدر این شرایط ایجاد یک پردازش چند مرحله‌ای و مدیریت مراحل به کمک یک تراکنش محلی است. تکنیکی که برای پیاده سازی این روش باید از آن استفاده کنیم ایجاد یک جدول برای Eventها است. این جدول به عنوان یک message queue در سیستم عمل می‌کند که eventها در آن ذخیره می‌شوند. هنگامی که یک موجودیت در سیستم تغییر می‌کند داده‌های Event معادل با این تغییر هم در این جدول ذخیره می‌شود. این ثبت اطلاعات همراه با تغییر داده‌های اصلی در یک تراکنش انجام می‌شود و به این روش، می‌توانیم مطمئن شویم که داده‌های Eventها از دست نمی‌رود و در صورت بروز مشکل داده‌ها در دیتابیس ثبت شده و با رفع مشکل سرویس ، ارسال Eventها از سر گرفته می‌شود.به دست آوردن atomicity به کمک جدول کمکی و تراکنش‌های محلیدر این سیستم، ابتدا یک سفارش ثبت می‌شود و همزمان با آن یک Event هم با وضعیت ارسال نشده در جدول ثبت می‌شود. در ادامه داده‌ها از جدول Event دریافت شده و Event در سیستم ارسال می‌شود و در صورت صحت انجام کار، وضعیت Eventبه ارسال شده تغییر می‌کند و تراکنش با موفقیت به اتمام می‌رسد.بزرگترین مزیت استفاده از این روش اطمینان از انتشار Event به ازای هر تراکنش داخلی بدون درگیر شدن با روال‌های 2PC است. مشکلات پیاده سازی این روش موقع کار با بعضی NoSqlها و احتمال فراموشی توسعه‌دهنده برای ذخیره Event یا مدیریت آن هم از مشکلات این روش است.3.2. کاوش در Transaction Log:راه بعدی برای دستیابی به Atomicity بدون استفاده از 2PC تولید و ارسال Eventها به کمک Transaction Log می‌باشد. در این روش هنگامی که برنامه اصلی داده‌ای را تغییر می‌دهد به جز داده‌های اصلی، تغییری در Transaction Log هم ثبت می‌شود. حال در برنامه ای دیگر به سراغ این Transaction Log آمده و تغییرات را از روی این فایل به دست آورده و بر اساس آن Eventها را در سیستم تولید و منتشر می‌کنیم. در تصویر بعد این حالت پیاده سازی را مشاهده می‌کنید.پردازش transaction log توسط سیستم مدیریت Eventهامثالی از این روش را می‌توان در LinkedIn databus مشاهده کرد. با استفاده از این سیستم شما می‌توانید لاگ‌های DB Engineهای مختلفی مثل اوراکل یا MySql را پردازش کرده و با تشخیص تغییر داده‌ها Event‌هایی را در سیستم منتشر کنید.به عنوان مثال دیگری از این روش می‌توان به streamها در AWS DynamoDB اشاره کرد.به کمک این مکانیزم تمامی تغییرات داده‌ها شامل ثبت، حذف و به روزرسانی به مدت 24 ساعت در یک توالی زمانی نگهداری می‌شوند.حال برنامه مدیریت تراکنش‌ها می‌تواند داده‌های این جدول را خوانده و Eventهای مرتبط با آن‌ها را منتشر کند.باز هم در پایان یک بخش باید به سراغ بررسی مزایا و معایب استفاده از یک روش توسعه برویم. تضمین ایجاد و انتشار Event بدون درگیر شدن با 2PC یکی از مزایای این روش است. به کمک این روش روال تولید و توزیع Event‌ها از چرخه برنامه اصلی خارج می‌شود و این کار می‌تواند به ساده شدن روال تولید و بهینه شدن انجام کار‌ها کمک کند. بزگترین ایراد این روش اما تفاوت در Transaction Logها در موتور‌های دیتابیس‌‌ مختلف است. حتی بعضا ساختار و شرایط Transaction Log بین دو نسخه متفاوت از یک DB Engine هم متفاوت است. دشواری واکشی داده‌های سطح‌بالایی مثل اطلاعات یک Event از داده‌های سطح پایین ذخیره شده در لاگ‌ها نیز یکی دیگر از معایب این روش پیاده سازی است.3.3. استفاده از Event Sourcing:در این روش برای دستیابی به Atomicity از روشی کاملا متفاوت با آنچه تا کنون دیده‌ایم استفاده می‌شود. در این روش به جای اینکه وضعیت یک Entity را ذخیره کنیم، وقایعی که روی این Entity رخ می‌دهد را ذخیره می‌کنیم. حال برای اینکه به وضعیت فعلی یکی Entity دسترسی داشته باشیم می‌توانیم وقایع رخ داده روی این Entity را از ابتدا اجرا کنیم تا مجدد Entity در وضعیت نهایی خود قرار بگیرید. در این روش هیچ داده‌ای تغییر نمی‌کند و اطلاعاتی نیز حذف نمی‌شود بلکه صرفا وقایع روی Entityها رخ می‌دهد و به ترتیب به پایگاه داده اضافه می‌شوند. از آنجایی که ذخیره شدن وقایع یک عملیات است، به صورت پیش فرض یک عملیات atomic است.برای درک بهتر این الگو بیایید نگاهی به مثال ثبت سفارش و پیاده سازی آن به کمک Event Sourcing بیاندازیم. در روش معمول به ازای هر سفارش یک ردیف داده در جدول Order ثبت می‌شود و اقلامی هم که در سفارش وجود دارند در جدول OrderLineItem یک ردیف معادل دارند. اما هنگام استفاده از Event Sourcing دیگر داده‌ها در جدول Order ثبت نمی‌شوند بلکه برای هر وضعیت سفارش مثل جدید، تایید شده، حمل شده یا کنسل شده یک ردیف در جدول Eventها ثبت می‌شود. مقایسه نگهداری وضعیت با نگهداری رخداددر این روش تمامی داده‌های رخداد‌ها در یک پایگاه داده ثبت می‌شوند. داده‌های رخداد‌ها را می‌توان در جداول و پایگاه‌های داده عادی نیز ذخیره کرد. اما دیتابیس‌های اختصاصی برای نگهداری اطلاعات Eventها مثل Event Store نیز وجود دارند که APIهای تخصصی برای ثبت و مدیریت داده‌های Eventها در اختیار قرار می‌دهند.استفاده از این روش مزایای بسیاری داشته و مشکلات زیادی را حل می‌کند، برای مثال می‌توان به سادگی وضعیت Entity را در هر زمانی از گذشته تا به حال به دست آورد. می‌توان بدون درد سر روالی که اتفاق افتاده تا Entity در وضعیت فعلی قرار گرفته را مشاهده کرد. نیازی به نگهداری داده‌های اضافه برای دانستن دلیل و شرایط تغییر وضعیت Entityها نیست و ...اما در کنار این مزایا پیچیدگی پیاده سازی این روش، یا بهتر اگر بخواهیم بیان کنیم، نگرش جدیدی که باید به مقوله داده‌ها و نگهداری وضعیت Entityها داشته باشیم یکی از عیب‌های این روش است. ایراد دوم در این الگو این است که در صورتی که وقایع یک Entity زیاد باشد، زمان زیادی طول می‌کشد تا پردازش‌های لازم جهت رسیدن به وضعیت نهایی یک Entity انجام شود. سومین و آخرین ایراد این روش که در این مطلب بررسی می‌کنیم مشکل جستجو روی داده‌ها است. جستجو‌های معمول را هم نمی‌توان با سادگی با این روش انجام داد و نیاز به پردازش زیادی داریم تا بتوانیم پاسخ یک query ساده را از میان انبوهی از Eventها بیرون بکشیم. با اینکه این الگو یک الگوی مستقل و قابل پیاده سازی به تنهایی است اما اغلب همراه با الگوی CQRS طراحی و پیاده سازی می‌گردد تا مشکلات و ایرادات این الگو پوشش داده شود.4. جمع‌بندی:در معماری میکروسرویس‌ها هر میکروسرویس دیتابیس و داده‌های اختصاصی خود را در اختیار دارد و می‌توانند به تنهایی و مستقل از هر سرویس‌ دیگری خدمات خود را ارائه دهند. میکروسرویس‌های مختلف می‌توانند از DB Engineهای متفاوتی نیز استفاده کنند که قابلیت‌های ویژه‌ای برای آن سرویس خاص دارند که می‌تواند موجب بهینه یا ساده شدن انجام کار شود. اما این روش پیاده سازی چالش‌هایی را در مودر اشتراک و توزیع داده‌ها ایجاد می‌کند که توسعه دهندگان باید راهکار‌هایی برای آن‌ها بیاندیشند. یکی از راهکار‌های حل کردن این چالش‌ها استفاده از Eventها در سیستم است که در این مطلب این راهکار را با هم بررسی کردیم. در قسمت آینده در مورد استقرار میکروسرویس‌ها و چالش‌های آن صحبت خواهیم کرد.ادامه مطلب: http://vrgl.ir/8cRfJ