محمدعلی محمودیار، مالک محصول بانکداری دیجیتال داتین / وقتی میلیونها کاربر همزمان از یک سرویس استفاده میکنند، مهندسی نرمافزار دیگر درباره نوشتن کد نیست؛ درباره تصمیمگیری است. پس اگر سوال این باشد:
نقطه شروع بانکداری دیجیتال کجاست؟
پاسخش به طور قطع، مهندسی نرمافزار یا چند خط کد نیست. بلکه باید گفت بانکداری دیجیتال از تصمیمهایی که هیچ کاربری آنها را نمیبیند، شروع میشود. این سختترین بخش مهندسی نشده روایت یک بانکداری دیجیتال است.
تجربه مشتری فقط چیزی نیست که روی صفحه موبایل میبیند، پشت هر دکمه، دهها تصمیم معماری وجود دارد؛ تصمیمهایی که تعیین میکنند وقتی میلیونها کاربر همزمان وارد سیستم میشوند و هرکدام درخواست منحصربهخود را دارند، سرویس همچنان پایدار بماند. شاید در نگاه اول تصور کنیم، همه این تصمیمهایی که از آن صحبت میکنیم در «زیرساخت» گرفته میشود. اما پیش از آن باید به یک سوال مهمتر پاسخ دهیم:
اگر فرض کنیم زیرساخت یک محصول است، مشتری واقعی آن چه کسی است؟
در نگاه سنتی، زیرساخت مجموعهای از ابزارهاست؛ دیتابیس، کش، صف، لاگ، سامانه احراز هویت و دهها سرویس دیگر که کنار هم قرار گرفتهاند تا یک سیستم را اجرا کنند. در این نگاه، کافی است ابزارها نصب شوند، پیکربندی شوند و در دسترس باشند.

اما وقتی زیرساخت را یک محصول بدانیم، سؤال دیگری مطرح میشود: مشتری این محصول چه کسی است؟
پاسخ، مدیر زیرساخت یا تیم عملیات نیست؛ بلکه تمام محصولاتی هستند که هر روز از این زیرساخت استفاده میکنند.
در ویپاد، هر فیچری که توسعه داده میشود، مشتری زیرساخت است. از سرویسهای مالی گرفته تا قابلیتهای جانبی، همه برای انجام وظیفه خود به سرویسهایی مانند دیتابیس، کش، صف پیام، سامانه احراز هویت و ثبت لاگ وابستهاند. بنابراین زیرساخت فقط مسئول ارائه یک ابزار نیست؛ باید برای هر یک از این مشتریان تجربهای پایدار، قابل پیشبینی و استاندارد فراهم کند.
به همین دلیل، هر جزء زیرساخت نیز قواعد محصول خودش را دارد. برای مثال، لایه کش فقط یک حافظه موقت نیست؛ باید مشخص باشد چه دادهای اجازه ورود به آن را دارد، چه سرویسهایی مجاز به خواندن یا نوشتن هستند، اطلاعات چه مدت نگهداری شوند و سیاستهای دسترسی چگونه تعریف شوند. اینها دیگر تنظیمات فنی صرف نیستند؛ بخشی از طراحی محصول زیرساخت محسوب میشوند. اما دایره مشتریان به همینجا ختم نمیشود.
مشتریان پنهانی هم وجود دارند
در معماری یک بانک دیجیتال، بسیاری از سرویسها بهصورت مستقیم با زیرساخت در ارتباط نیستند، اما کیفیت عملکرد آن را تجربه میکنند.
اگر لایه کش یا صف پیام دچار اختلال شود، ممکن است در نگاه اول فقط یک سرویس داخلی آسیب دیده باشد؛ اما در عمل، اثر آن در کل اکوسیستم منتشر میشود. سرویسهای احراز هویت، ارائهدهندگان خدمات، سامانههای بیرونی و حتی محصولاتی که وابستگی مستقیمی به آن زیرساخت ندارند، میتوانند تحت تأثیر قرار بگیرند.
به همین دلیل، طراحی زیرساخت فقط به پاسخگویی به نیازهای امروز محدود نمیشود. هر تصمیم معماری باید اثر خود را بر کل اکوسیستم در نظر بگیرد؛ زیرا هر تغییر، هرچند کوچک، میتواند زنجیرهای از وابستگیها را تحت تأثیر قرار دهد.
در چنین نگاهی، موفقیت زیرساخت با تعداد ابزارهای مورد استفاده سنجیده نمیشود؛ بلکه با این معیار سنجیده میشود که چه تعداد محصول و سرویس میتوانند بدون نگرانی روی آن تکیه کنند.
پس زیرساخت فقط به محصول خودش پاسخگو نیست
وقتی از مشتریان زیرساخت صحبت میکنیم، منظور فقط سرویسها و فیچرهای داخلی یک محصول نیست. در یک اکوسیستم بانکی، هر تصمیم زیرساختی میتواند فراتر از مرزهای همان محصول اثر بگذارد.
ویپاد را در نظر بگیرید. این محصول بهتنهایی فعالیت نمیکند؛ بخشی از یک اکوسیستم بزرگتر است که با سرویسهای داتین، سامانههای پاد، سرویس احراز هویت (SSO) و مجموعهای از ارائهدهندگان خدمات (Providers) در تعامل است. این ارتباطها به این معناست که زیرساخت ویپاد تنها مسئول پایداری خود ویپاد نیست؛ بلکه بخشی از زنجیرهای است که عملکرد سایر اجزای اکوسیستم نیز به آن وابسته است.

برای مثال، ممکن است لایه کش در ظاهر فقط یک سرویس داخلی باشد، اما اگر طراحی، تنظیمات یا سیاستهای استفاده از آن بهدرستی تعریف نشده باشد، اختلال ایجادشده فقط به یک قابلیت در ویپاد محدود نمیشود. این اختلال میتواند بر ارتباط با سرویسهای احراز هویت، سرویسهای پاد یا حتی Providerهایی که خدمات خود را از طریق ویپاد ارائه میکنند نیز اثر بگذارد. در چنین شرایطی، مسئله دیگر یک «مشکل فنی» در یک جزء از سیستم نیست؛ بلکه اختلالی است که در کل اکوسیستم منتشر میشود.
به همین دلیل، در معماری زیرساخت، هر تصمیم باید با درنظر گرفتن وابستگیهای پیرامون آن گرفته شود. زیرساخت فقط برای پاسخگویی به نیازهای امروز یک محصول طراحی نمیشود؛ بلکه باید بتواند پایداری تعاملات میان دهها سرویس و سامانه وابسته را نیز تضمین کند. همین نگاه است که باعث میشود زیرساخت، از مجموعهای از ابزارهای فنی، به یک محصول تبدیل شود؛ محصولی که مشتریانش فقط تیم توسعه نیستند، بلکه کل اکوسیستمی هستند که روی آن تکیه کردهاند.
تصمیمهای معماری، فقط برای امروز گرفته نمیشوند
وقتی یک محصول در دل یک اکوسیستم فعالیت میکند، هیچ سرویسی مستقل نیست. احراز هویت، خدمات کارت، سرویسهای مالی و دهها سرویس دیگر، هرکدام توسط Providerهای مختلف ارائه میشوند و هرکدام نیز تعهد مشخصی برای پاسخگویی دارند؛ تعهدی که معمولاً در قالب SLA تعریف میشود.
فرض کنید یکی از این Providerها متعهد شده است که حداکثر ظرف ۱۵ ثانیه به هر درخواست پاسخ دهد. حالا اگر این زمان به پایان برسد و پاسخی دریافت نشود، چه اتفاقی باید بیفتد؟
شاید پاسخ ساده این باشد که «بیشتر منتظر بمانیم». اما در معماری یک بانک دیجیتال، همین تصمیم ظاهراً ساده میتواند آغاز یک اختلال بزرگ باشد. اگر سیستم بدون هیچ محدودیتی منتظر پاسخ سرویس بماند، هر درخواست جدید نیز به درخواستهای قبلی اضافه میشود. اتصالهای باز (Connection Pool) یکی پس از دیگری اشغال میشوند و منابعی که باید صرف پاسخگویی به سایر کاربران شوند، در انتظار سرویسی باقی میمانند که هنوز پاسخی نداده است.
در چنین شرایطی، مسئله دیگر فقط کندی یک Provider نیست؛ محصولی که قرار بود از آن سرویس استفاده کند نیز بهتدریج توان پاسخگویی خود را از دست میدهد.
به همین دلیل، یکی از مهمترین تصمیمهای معماری، تصمیم برای منتظر نماندن است.
در عمل، اگر پاسخ یک Provider از زمان توافقشده عبور کند، سیستم باید آن درخواست را Timeout تلقی کند و نتیجه را به کاربر بازگرداند. شاید این تصمیم در نگاه اول به معنی پایان نیافتن یک عملیات باشد، اما در واقع از گرفتار شدن کل سیستم جلوگیری میکند.
اما حتی این هم کافی نیست. اگر یک Provider بارها نتواند در بازه زمانی توافقشده پاسخ دهد، ادامه ارسال درخواست به آن فقط فشار بیشتری بر هر دو طرف وارد میکند. در چنین شرایطی، معماری باید تصمیم دیگری بگیرد؛ برای مدتی ارسال درخواست به آن سرویس را متوقف کند تا هم Provider فرصت بازیابی داشته باشد و هم منابع محصول بیهوده مصرف نشوند. الگوهایی مانند Circuit Breaker دقیقاً با همین هدف طراحی شدهاند. در غیر این صورت، یک اختلال کوچک میتواند مانند دومینو در کل اکوسیستم منتشر شود؛ ویپاد منابع خود را صرف انتظار میکند، Provider زیر بار درخواستها فرصت بازیابی پیدا نمیکند و سرویسهای دیگری مانند پاد نیز بهدلیل همین وابستگیها تحت تأثیر قرار میگیرند. در معماری بانکداری دیجیتال، گاهی مهمترین تصمیم این نیست که چگونه یک درخواست را به مقصد برسانیم؛ بلکه این است که چه زمانی ارسال آن را متوقف کنیم.
معماری خوب، بحران را پیشبینی میکند
یکی از رایجترین برداشتها درباره زیرساخت این است که تا زمانی که سیستم از کار نیفتاده، همه چیز درست پیش میرود. اما در عمل، زمانی که اختلال به کاربر رسیده باشد، برای بسیاری از تصمیمها دیگر دیر شده است.
پایداری یک بانک دیجیتال زمانی معنا پیدا میکند که ظرفیت سیستم، پیش از رسیدن به نقطه بحران، شناخته شده باشد. این یعنی از همان ابتدای طراحی، باید مشخص باشد زیرساخت برای چه حجمی از کاربران، چه تعداد درخواست و چه الگوی مصرفی ساخته شده است و در چه نقطهای نیاز به توسعه یا بازطراحی خواهد داشت.
برای مثال، تصور کنید یک کمپین بازاریابی در ویپاد قرار است در مدت کوتاهی میلیونها پیامک ارسال کند. چنین رویدادی تنها یک کمپین تبلیغاتی نیست؛ یک آزمون واقعی برای کل معماری سیستم است. قبل از شروع کمپین باید مشخص باشد در هر ساعت چه میزان درخواست وارد ویپاد خواهد شد، چه زمانی اوج ترافیک اتفاق میافتد و زیرساخت ما تا چه میزان توان پاسخگویی دارد. اما حتی پیشبینی نیز بهتنهایی کافی نیست.
سیستم باید قبل از کاربر متوجه مشکل شود
در یک معماری بالغ، اولین کسی که متوجه اختلال میشود کاربر نیست. زیرساخت باید پیش از آنکه افت کیفیت را به تجربه مشتری برساند، نشانههای اولیه را تشخیص دهد.
اینجاست که ابزارهای مانیتورینگ، از یک ابزار عملیاتی به بخشی از معماری محصول تبدیل میشوند. مانیتورینگ که فقط برای ثبت رخدادها نیست؛ بلکه به تیم محصول کمک میکند رفتار سیستم را بهصورت لحظهای مشاهده کند. اینکه در هر دقیقه چه تعداد درخواست وارد سیستم میشود، چند کاربر همزمان فعال هستند، نرخ خطاها چقدر است، زمان پاسخگویی سرویسها چگونه تغییر میکند یا چه تعداد Timeout رخ داده است، همگی شاخصهایی هستند که تصویری زنده از وضعیت محصول ارائه میکنند.
بر اساس همین دادهها، برای هر شاخص آستانههایی (Threshold) تعریف میشود؛ محدودههایی که عبور از آنها به معنی نزدیک شدن سیستم به شرایط غیرعادی است. در چنین شرایطی، تیم فنی پیش از آنکه کاربران اختلال را احساس کنند، میتواند درباره کاهش بار، محدود کردن بخشی از درخواستها یا توسعه ظرفیت زیرساخت تصمیم بگیرد. در واقع، یکی از مهمترین وظایف زیرساخت، فقط پاسخ دادن به درخواستها نیست؛ بلکه هشدار دادن پیش از آغاز بحران است.
اگر نرخ موفقیت یک سرویس از ۱۰۰ درصد به ۷۰ درصد کاهش پیدا کند، صرفا اطلاع داشتن از این افت کافی نیست؛ باید بتوان کل زنجیره درخواست را ردیابی کرد تا مشخص شود کدام سرویس یا ارائهدهنده در مسیر پردازش، باعث ایجاد تأخیر یا اختلال شده است. با این حال، معماری زیرساخت نباید تنها برای سناریوهای شناختهشده طراحی شود. در عمل، همواره موقعیتهایی رخ میدهند که از قبل پیشبینی نشدهاند و تنها در مقیاس واقعی خود را نشان میدهند.
به همین دلیل، معماری باید قابلیت تکامل مداوم داشته باشد و بر اساس رفتار واقعی سامانه بهروزرسانی شود. ممکن است در جریان بهرهبرداری مشخص شود که یک سرویس بیرونی، مانند دریافت اطلاعات از یک سامانه حاکمیتی، در بازههای زمانی مشخص با تأخیر پاسخ میدهد. اگر این وابستگی در معماری مدیریت نشده باشد، همان تأخیر میتواند منابع سرور را اشغال کند، ظرفیت پردازش را کاهش دهد و در نهایت بر تجربه کاربران سایر بخشهای سامانه نیز اثر بگذارد.
در چنین شرایطی، معماری باید امکان بازطراحی و افزایش ظرفیت را بدون ایجاد وقفه در سرویس فراهم کند. برای نمونه، با Stateless طراحی کردن سرویسها میتوان آنها را بهصورت افقی (Horizontal Scaling) توسعه داد و با افزودن سرورهای جدید، ظرفیت پاسخگویی را متناسب با حجم درخواستها افزایش داد. این تصمیمها زمانی اتخاذ میشوند که دادههای مانیتورینگ و تحلیل رفتار سامانه نشان دهند گلوگاههای جدیدی در حال شکلگیری هستند.
در نهایت، کاربر فرصتی برای انتظار ندارد تا تیمهای فنی پس از وقوع اختلال، علت آن را بررسی کنند. زیرساخت باید بهگونهای طراحی شود که بتواند پیش از آنکه مشکل به کاربر برسد، نشانههای آن را شناسایی کند، مسیر ایجاد اختلال را ردیابی کند و با تصمیمهای معماری مناسب، خود را با شرایط جدید تطبیق دهد. پس بلوغ یک زیرساخت دیجیتال، نه در نبود خطا، بلکه در توانایی آن برای مشاهده، تحلیل و اصلاح مداوم خود معنا پیدا میکند.
Sync یا Async؛ یک تصمیم فنی یا یک طرز فکر در طراحی محصول؟
قبل از اینکه درباره فناوریهای مورد استفاده یا الگوهای پیادهسازی صحبت کنیم، یک سؤال مهمتر وجود دارد؛ هر تصمیم معماری قرار است چه تجربهای برای کاربر نهایی بسازد؟ بسیاری از تصمیمهایی که در لایه زیرساخت گرفته میشوند، در ظاهر کاملاً فنی هستند، اما اثر آنها مستقیماً در تجربه محصول دیده میشود.
یکی از همین تصمیمها، انتخاب میان ارتباط همگام (Synchronous) و ناهمگام (Asynchronous) است. این انتخاب صرفاً یک ترجیح فنی نیست، بلکه تعیین میکند کاربر در لحظه تعامل با محصول چه تجربهای داشته باشد.

فرض کنید کاربر ویپاد گزینه «خروج از همه دستگاهها» را انتخاب میکند. اگر این فرآیند بهصورت همگام یا Sync طراحی شده باشد، کاربر تا پایان اجرای همه عملیات منتظر بماند؛ شاید چند ثانیه و شاید بیشتر. در این مدت ممکن است درخواست با Timeout مواجه شود یا کاربر تصور کند عملیات با خطا روبهرو شده است. در مقابل، اگر همین قابلیت بهصورت ناهمگام یا Async طراحی شود، سامانه میتواند بلافاصله درخواست را ثبت کند، به کاربر اطلاع دهد که فرآیند آغاز شده است و ادامه عملیات را در پسزمینه انجام دهد. در این مدل، کاربر معطل اجرای یک فرآیند پیچیده نمیشود و تجربه روانتری از محصول خواهد داشت.
این مثال نشان میدهد که تصمیمهای معماری صرفاً درباره نحوه ارتباط سرویسها نیستند؛ آنها مستقیماً بر UX اثر میگذارند. به همین دلیل، تیم زیرساخت نمیتواند صرفاً از زاویه فناوری به مسئله نگاه کند. برای انتخاب الگوی مناسب، ابتدا باید رفتار مورد انتظار محصول، میزان تحمل کاربر برای انتظار، حساسیت عملیات و حتی پیامهایی که در رابط کاربری نمایش داده میشوند، در نظر گرفته شوند.
در عمل، بسیاری از تصمیمهای فنی زمانی درست گرفته میشوند که نقطه شروع آنها کاربر باشد، نه فناوری. معماری زمانی به بلوغ میرسد که بتواند بین ملاحظات فنی، محدودیتهای زیرساخت و تجربهای که قرار است به کاربر ارائه شود، تعادل برقرار کند. شاید به همین دلیل است که نمیتوان زیرساخت را صرفاً مجموعهای از سرورها و سرویسها دانست؛ زیرساخت نیز مانند هر محصول دیگری باید بداند مشتریش چه انتظاری دارد و تصمیمهایش را بر همان اساس بگیرد.
هزینه هر تصمیم معماری را چه کسی میپردازد؟
اگرچه در بسیاری از مواقع، طراحی ناهمگام (Asynchronous) تجربه بهتری برای کاربر ایجاد میکند، اما این به آن معنا نیست که همیشه بهترین انتخاب است. هر تصمیم معماری، هزینهای دارد و این هزینه ممکن است یا در لایه محصول پرداخت شود یا در لایه زیرساخت. از نگاه محصول، بسیاری از عملیات را میتوان بهگونهای طراحی کرد که کاربر منتظر پایان پردازش نماند. سامانه درخواست را ثبت میکند، به کاربر اطلاع میدهد که فرایند آغاز شده است و نتیجه را در زمان مناسب در اختیار او قرار میدهد. در چنین سناریویی، تجربه کاربر روانتر است، احتمال Timeout کاهش پیدا میکند و کنترل خطا نیز سادهتر خواهد بود. اما همین تصمیم، از نگاه زیرساخت رایگان نیست. معماری ناهمگام نیازمند اجزای بیشتری است؛ صفهای پیام، مدیریت Eventها، مصرفکنندههای مختلف، مکانیزمهای Retry، مانیتورینگ جریان پیامها و مدیریت منابع، همگی پیچیدگیهایی هستند که به سیستم اضافه میشوند. هرچه تعداد سرویسها و وابستگیها بیشتر شود، این پیچیدگی نیز افزایش پیدا میکند و اگر بدون ضرورت از این الگو استفاده شود، هزینه نگهداری سیستم از مزایای آن بیشتر خواهد شد.
در مقابل، معماری همگام نیز اگرچه سادهتر به نظر میرسد، اما در سناریوهای پرترافیک یا فرایندهای زمانبر میتواند منابع را برای مدت طولانی درگیر نگه دارد و احتمال بروز اختلالات زنجیرهای را افزایش دهد. بنابراین، پاسخ این سؤال که یک سرویس باید همگام باشد یا ناهمگام، نه صرفاً در لایه فنی و نه صرفاً در لایه محصول پیدا میشود؛ بلکه نتیجه موازنهای میان این دو است.
در سامانههای مالی، این موازنه اهمیت بیشتری پیدا میکند. برای مثال، ممکن است عملیات پرداخت بلافاصله انجام شود، اما فرایندهای پس از آن مانند مغایرتگیری، کنترلهای مالی یا ترمیم خطاها بهصورت ناهمگام اجرا شوند. این طراحی باعث میشود اگر بخشی از فرایند با مشکل مواجه شد، سیستم بتواند بدون نیاز به دخالت کاربر، عملیات را دوباره اجرا کند یا با مکانیزمهای Retry و ترمیم، وضعیت را به حالت صحیح بازگرداند.
در نهایت، بلوغ معماری در این نیست که همهچیز را همگام یا ناهمگام طراحی کنیم؛ بلکه در این است که بدانیم هر تصمیم چه اثری بر تجربه کاربر، پیچیدگی زیرساخت و هزینه نگهداری سیستم خواهد گذاشت و متناسب با همان مسئله، الگوی مناسب را انتخاب کنیم.
Async به معنای UX بهتر نیست
یکی از برداشتهای رایج این است که استفاده از معماری ناهمگام (Asynchronous) صرفاً برای بهبود تجربه کاربر است؛ در حالی که این تنها یکی از کاربردهای آن است. در عمل، تصمیم برای استفاده از Sync یا Async همیشه از لایه محصول آغاز نمیشود. در بسیاری از موارد، اولویت اصلی حفظ پایداری زیرساخت و مدیریت صحیح خطاهاست و تجربه کاربر در گام بعدی قرار میگیرد.
برای مثال، اگر کاربر درخواستی را ثبت کند، احراز هویت شود و سپس اپلیکیشن را ببندد، دیگر نمیتوان مدیریت ادامه فرایند را به حضور او وابسته کرد. در چنین شرایطی، معماری باید بتواند مستقل از کاربر، عملیات را تا رسیدن به نتیجه نهایی دنبال کند؛ بهویژه اگر آن عملیات با تراکنشهای مالی یا تغییر وضعیت حساب کاربر در ارتباط باشد.
نمونههایی مانند پرداخت وجه، مغایرتگیری، کسر یا اضافهشدن سکه به حساب کاربر از همین جنس هستند. در این سناریوها، آنچه اهمیت دارد تنها اجرای موفق عملیات نیست؛ بلکه امکان مدیریت خطا، اجرای مجدد (Retry) و ترمیم فرایند در صورت بروز اختلال نیز اهمیت پیدا میکند. به همین دلیل، این دسته از عملیات معمولاً بهگونهای طراحی میشوند که حتی اگر یکی از اجزای سیستم در لحظه در دسترس نباشد، درخواست از بین نرود و سیستم بتواند پس از برطرف شدن مشکل، ادامه فرایند را بهصورت خودکار انجام دهد.
این رویکرد در ارتباط میان سرویسها نیز اهمیت بیشتری پیدا میکند. فرض کنید کاربر فاکتوری را در ویپاد پرداخت کرده است. پس از انجام پرداخت، لازم است سامانه ویبات از موفقیت این تراکنش مطلع شود. اگر این ارتباط بهصورت همگام انجام شود، سرویس پرداخت باید تا زمان پاسخ ویبات منتظر بماند. در نتیجه، کندی یا اختلال در بیداد میتواند فرایندی را که از نگاه کاربر به پایان رسیده است، متوقف کند.
در چنین سناریویی، اطلاعرسانی به ویبات بهصورت ناهمگام انجام میشود. پیام در صف قرار میگیرد و مستقل از سرویس اصلی ارسال میشود. اگر ارسال با خطا مواجه شود، مکانیزمهای Retry آن را دوباره اجرا میکنند، بدون آنکه فرآیند اصلی یا تجربه کاربر تحت تأثیر قرار بگیرد.
همین الگو در ارتباط با سرویسهای شخص ثالث نیز به کار گرفته میشود. برای مثال، در برخی خدمات ویپاد لازم است پس از انجام یک عملیات، سرویس ریتون نیز از نتیجه آن مطلع شود. این اطلاعرسانی نیز بهصورت ناهمگام انجام میشود تا در دسترس نبودن یا کندی سرویس ثالث، عملکرد سرویس اصلی را مختل نکند. به این ترتیب، وابستگی میان سامانهها کاهش پیدا میکند و هر سرویس میتواند بدون متوقفکردن سایر اجزای اکوسیستم، در زمان مناسب وظیفه خود را انجام دهد.
از این منظر، Async صرفاً ابزاری برای کاهش زمان انتظار کاربر نیست؛ بلکه یکی از مهمترین الگوهای معماری برای افزایش تابآوری سیستم، مدیریت وابستگیها و حفظ پایداری سرویسها در اکوسیستمهای بزرگ به شمار میرود.
دفاع از زیرساخت، پیش از رسیدن درخواست به محصول
همه تهدیدها از جنس افزایش بار ناشی از کمپینهای بازاریابی نیستند. گاهی زیرساخت باید برای شرایطی آماده باشد که اساساً با رفتار عادی کاربران همخوانی ندارد؛ از حملات هدفمند گرفته تا درخواستهای غیرمنطقی که میتوانند منابع سامانه را درگیر کنند.
برای مثال، در صفحه ورود ویپاد، کاربر شماره تلفن همراه خود را وارد میکند تا کد تأیید دریافت کند. طبیعی است که ممکن است یک یا دو بار شماره را اشتباه وارد کند یا به دلیل قطعی اینترنت دوباره درخواست ارسال کند. حتی اگر این اتفاق پنج یا شش بار هم تکرار شود، هنوز میتوان آن را بخشی از رفتار طبیعی کاربر دانست. اما اگر همان کاربر یا همان شناسه در مدت یک دقیقه، ده بار یا بیشتر درخواست ارسال کند، دیگر با یک رفتار عادی روبهرو نیستیم.
در چنین شرایطی، پیش از آن که این درخواستها به سرویسهای اصلی برسند، مکانیزمهای کنترلی وارد عمل میشوند. یکی از این مکانیزمها، Rate Limiting است. برای مثال، سامانه میتواند پس از عبور از تعداد مشخصی درخواست، از کاربر بخواهد یک دقیقه صبر کند و سپس دوباره تلاش کند. این تصمیم، علاوهبر جلوگیری از سوءاستفاده، اجازه نمیدهد منابع زیرساخت بیدلیل درگیر درخواستهایی شوند که احتمالاً ماهیت طبیعی ندارند.
این کنترلها نیز لزوماً در خود اپلیکیشن پیادهسازی نمیشوند. بخشی از آنها در لایه زیرساخت انجام میشود؛ جایی که سیاستهای امنیتی مستقل از منطق محصول اعمال میشوند. برای مثال، ممکن است تصمیم گرفته شود که دسترسی به یک سرویس فقط از IPهای داخل ایران امکانپذیر باشد یا این محدودیتها از طریق فایروال کاربردی WAF ،CDN یا سایر اجزای زیرساخت اعمال شوند. در چنین معماری، تیم توسعه محصول نیازی ندارد درگیر جزئیات این تصمیمها باشد؛ زیرا این مسئولیت در لایه زیرساخت مدیریت میشود.
هدف از این طراحی، فقط جلوگیری از حملات نیست. معماری زیرساخت باید بتواند رفتارهای غیرعادی را در همان لایههای ابتدایی تشخیص دهد و پیش از آنکه فشار به سرویسهای اصلی منتقل شود، آن را کنترل کند. هرچه این تصمیمها زودتر گرفته شوند، احتمال آنکه کاربر نهایی اثری از اختلال یا حمله را تجربه کند، کمتر خواهد بود.
تحمل خطا، مقاومت در برابر آن و بازیابی هوشمندانه
یکی از اصول کلیدی در معماری ویپاد، تفاوت بین تحمل خطا و مقاومت در برابر خطا است. ما از یک پروتکل مشخص استفاده کردیم که بر اساس فلسفه «سیستم باید کارش را ادامه دهد» طراحی شده بود. یعنی اگر یک نود یا یک اپلیکیشن سقوط میکرد، زیرساخت باید کار خود را با استفاده از مکانیزمهای بازیابی ادامه میداد. اما تنها نقطه مشترک که اکنون بین اپلیکیشن و سیستم وجود دارد، دیتابیس است. این دیتابیس، تنها منبع مشترک است که در واقع کنترل مستقیم روی آن نداریم و سرویسها از طریق آن کار میکنند. با این حال، ما پیشبینی کردهایم که این دیتابیس تحت حمله قرار بگیرد. به همین دلیل، یک لایه کش در نظر گرفتهایم که درخواستها را در کوتاهترین زمان پاسخ میدهد. در حالی که دیتابیس ما همچنان در حال پردازش است، کش درخواست را در خود نگه میدارد و بعد از این که دیتابیس آماده شد، نتیجه را ارائه میدهد.
معماری از بحرانهای واقعی یاد میگیرد و کامل میشود
مقاومسازی در برابر خطا، فقط به این معنا نیست که بتوان سناریوهای محتمل را از قبل پیشبینی کرد. معماری باید برای اتفاقاتی که انتظار وقوع آنها را داریم آماده باشد؛ از کار افتادن یک نود، یک سرویس یا حتی یک اپلیکیشن، همگی باید از قبل در سناریوهای Disaster Recovery دیده شده باشند تا سیستم بتواند بدون ایجاد اختلال جدی، مسیر بازیابی خود را طی کند. اما واقعیت این است که همه بحرانها قابل پیشبینی نیستند و بخشی از بلوغ معماری، از تجربههایی به دست میآید که تنها در مقیاس واقعی رخ میدهند.
یکی از این تجربهها به چند سال قبل و زمان برگزاری قرعهکشیهای ویپاد برمیگردد. همزمان با اعلام نتایج، تعداد زیادی از کاربران وارد اپلیکیشن میشدند تا ببینند برنده شدهاند یا خیر. همین رفتار، فشار قابلتوجهی را به زنجیرهای از سرویسها وارد میکرد که از لحظه ورود کاربر تا نمایش نتیجه قرعهکشی درگیر بودند.
بهجای آن که صرفاً به افزایش منابع زیرساخت فکر کنیم، زنجیره کامل این سرویسها تحلیل شد. بررسیها نشان داد یکی از سرویسها به گلوگاه تبدیل شده است؛ کندی همان سرویس، باعث کند شدن سایر سرویسها و در نهایت کل اکوسیستم میشد. این تحلیل، مسئله را از یک اختلال مقطعی به یک تصمیم معماری تبدیل کرد. در ادامه مشخص شد بخش مهمی از درخواستهای این سرویس، مربوط به دریافت اطلاعات پروفایل کاربران است؛ دادهای که برخلاف بسیاری از اطلاعات دیگر، در هر لحظه تغییر نمیکند.
بنابراین ضرورتی نداشت هر درخواست مستقیماً به پایگاه داده مراجعه کند. نتیجه این تحلیل، اضافه شدن یک لایه Cache بود تا اطلاعات پروفایل کاربران برای مدت مشخصی، مثلاً ۱۵ دقیقه، در حافظه نگهداری شود. در این مدت، تمام سرویسهایی که به این اطلاعات نیاز داشتند، بهجای مراجعه مکرر به پایگاه داده، از همان Cache استفاده میکردند و پس از پایان این بازه، اطلاعات دوباره بهروزرسانی میشد. این تغییر، علاوه بر کاهش فشار روی منابع مشترکی مانند پایگاه داده، زمان پاسخگویی سرویسها را نیز بهبود داد و احتمال ایجاد گلوگاه در زمان اوج ترافیک را کاهش داد. مهمتر از همه اینکه این تصمیم، از دل یک تجربه واقعی به دست آمد؛ تجربهای که نشان داد معماری یک سامانه، با هر بحران کاملتر میشود. هر اختلال، اگر بهدرستی تحلیل شود، میتواند به فرصتی برای بازنگری در تصمیمهای معماری و مقاومتر کردن کل اکوسیستم تبدیل شود.
مرزهای مسئولیت در معماری میکروسرویس
یکی از درسهایی که از تجربه ویپاد گرفتیم، این بود که از ابتدا نگاه به معماری یک نگاه انعطافپذیر بود. هرچند ساختار اولیه ما از همان ابتدا کاملاً مشخص نبود، اما با گذر زمان، تجربه بحرانها و حملات مختلف، به تدریج این معماری را به سمت یک ساختار کاملاً مسئولیتگرا هدایت کرد. هر میکروسرویس اکنون یک وظیفه مشخص دارد، و این باعث شده که در هر محصول جدیدی که توسعه میدهیم، به جای گسترش بیش از حد، دقیقاً بدانیم کدام بخش باید مسئولیت را به عهده بگیرد. این رویکرد باعث شده پیچیدگی ارتباطات کاهش یابد و در نتیجه هر سرویس مسئولیت خود را به دقت انجام دهد. بنابراین، در شرایط فعلی، معماری ما نه تنها پایداری بهتری دارد، بلکه هر تصمیم معماری هم با آگاهی از نقش دقیق هر بخش اتخاذ میشود.
برای ما هر رخداد عملی، فرصتی برای بازطراحی بخشی از معماری بوده است. برای مثال، تجربه رویدادهای پرترافیک نشان داد برخی وابستگیها فشار غیرضروری به دیتابیس وارد میکنند. نتیجه این تجربه، اضافه شدن لایه Cache برای نگهداری موقت اطلاعاتی مانند پروفایل کاربران بود؛ تغییری که بدون دست زدن به منطق کسبوکار، حجم قابل توجهی از درخواستها را از روی دیتابیس برداشت و پایداری سیستم را افزایش داد.
این تغییرات تنها به اضافه شدن یک ابزار یا یک لایه جدید محدود نماند. به مرور، مسئولیت هر میکروسرویس نیز شفافتر شد. امروز هر سرویس مرز مشخصی دارد و دقیقاً میداند چه کاری باید انجام دهد و چه کاری خارج از محدوده مسئولیت اوست. همین شفافیت باعث شده با توسعه محصولات جدید، به جای آنکه قابلیتهای تازه روی سرویسهای موجود انباشته شوند، ابتدا مشخص شود کدام سرویس مالک آن قابلیت است و اگر هیچ سرویسی مسئولیت آن را ندارد، یک میکروسرویس جدید با دامنه مسئولیت مشخص ایجاد شود.
این رویکرد از بزرگ شدن بیضابطه سرویسها و افزایش وابستگی میان آنها جلوگیری میکند. هرچه ارتباط میان میکروسرویسها بیشتر و پیچیدهتر شود، ریسک اختلالات زنجیرهای نیز افزایش پیدا میکند. به همین دلیل، یکی از اصولی که معماری ویپاد به مرور به آن رسیده، حفظ مرزهای مسئولیت و کنترل وابستگیها میان سرویسهاست.
در واقع، معماری امروز ویپاد نتیجه مجموعهای از تصمیمهایی است که در مواجهه با واقعیت گرفته شدهاند؛ تصمیمهایی که هر کدام پاسخی به یک مسئله واقعی بودهاند. از اضافهشدن Cache گرفته تا بازتعریف مرز سرویسها، همه این تغییرات نشان میدهند که معماری یک سند ثابت نیست، بلکه موجودیتی زنده است که همراه با محصول رشد میکند، از بحرانها درس میگیرد و برای آینده آمادهتر میشود.
وابستگیهای پنهان و بدهیهای فنی
یکی از چالشهای معماری در مقیاس بزرگ، وابستگیهایی است که به مرور زمان و گاهی بدون آنکه تیم متوجه شود، میان سرویسها شکل میگیرند. در یک معماری میکروسرویسی، هر سرویس باید تا حد ممکن مستقل باشد و تنها مسئولیت مشخص خود را بر عهده بگیرد. اما اگر مرز مسئولیتها بهدرستی تعریف نشود، سرویسها به اطلاعات و منطق یکدیگر وابسته میشوند و همین وابستگیهای پنهان، عیبیابی و توسعه سیستم را دشوار میکند.
برای مثال، ممکن است یک میکروسرویس برای ارائه یک خدمت، نیاز داشته باشد تشخیص دهد کاربر مجاز به استفاده از آن خدمت هست یا خیر. اگر این منطق در جای نادرستی پیادهسازی شود یا سرویس برای انجام آن به چندین سرویس دیگر وابسته باشد، در صورت بروز اختلال، کاربر تنها با یک خطا یا شکست در فرایند مواجه میشود؛ در حالی که منشأ واقعی مشکل ممکن است چند لایه پایینتر و در یکی از همین وابستگیهای پنهان باشد. همین موضوع، ردیابی خطا و نگهداشت سیستم را بهمرور پیچیدهتر میکند. در کنار این چالش، معماری با مسئله دیگری نیز روبهرو است؛ بدهی فنی. گاهی برای پاسخ سریع به یک نیاز کسبوکاری، کدی توسعه داده میشود که اگرچه وظیفه خود را انجام میدهد، اما از نظر ساختار، خوانایی یا قابلیت توسعه، کیفیت مطلوبی ندارد. این تصمیم ممکن است در کوتاهمدت منطقی باشد، اما اگر برای بازنگری آن برنامهریزی نشود، هزینه نگهداشت سیستم به مرور افزایش پیدا میکند.
به همین دلیل، بازآرایی و Refactor کردن کد باید بخشی از برنامه توسعه محصول باشد، نه کاری که تنها در زمان بروز مشکل انجام شود. ریفکتور، افزودن قابلیت جدید نیست، اما یکی از مهمترین سرمایهگذاریها برای حفظ کیفیت زیرساخت، کاهش پیچیدگی و افزایش سرعت توسعه در آینده محسوب میشود. بسیاری از این بدهیها حاصل محدودیت زمان هستند و گاهی نیز تا زمانی که محصول در مقیاس واقعی استفاده نشود، نقاط ضعف آن آشکار نمیشود؛ چه در لایههای زیرساخت و چه حتی در تجربهای که کاربر نهایی از محصول دریافت میکند.
معماری بانکداری دیجیتال؛ مجموعهای از تصمیمهای ناتمام
در نگاه اول، معماری نرمافزار مجموعهای از نمودارها، الگوها و فناوریها به نظر میرسد؛ اما در عمل، معماری چیزی جز مجموعهای از تصمیمها نیست. تصمیمهایی که هرکدام میان سرعت توسعه، پایداری، هزینه نگهداشت، مقیاسپذیری و تجربه کاربر تعادل برقرار میکنند. در ویپاد نیز معماری هیچگاه یک نسخه نهایی و تغییرناپذیر نبوده است. هر بحران، هر افزایش بار، هر وابستگی پنهان و حتی هر بدهی فنی، فرصتی برای بازنگری در تصمیمهای گذشته و بهبود تصمیمهای آینده بوده است. از انتخاب میان Sync و Async گرفته تا تعریف مرز مسئولیت میکروسرویسها، اضافهشدن Cache، طراحی مکانیزمهای بازیابی، مانیتورینگ، مدیریت بار و برنامهریزی برای Refactor، همگی بخشی از یک مسیر یادگیری مداوم هستند.
شاید مهمترین ویژگی یک زیرساخت بانکی این نباشد که هرگز با خطا مواجه نشود؛ بلکه این است که بتواند خطاها را پیش از تبدیل شدن به بحران شناسایی کند، از تجربهها درس بگیرد و بدون از دست دادن پایداری، خود را با نیازهای جدید تطبیق دهد.
در نهایت، آنچه کیفیت یک بانکداری دیجیتال را تعیین میکند، فقط فناوریهای بهکاررفته یا تعداد سرویسهای آن نیست؛ بلکه کیفیت تصمیمهایی است که در لایههای پنهان معماری گرفته میشوند. تصمیمهایی که شاید هیچگاه از نگاه کاربر دیده نشوند، اما تجربهای که او از یک محصول سریع، پایدار و قابل اعتماد دارد، نتیجه مستقیم همان تصمیمهاست.