پیشخان توسعه‌دهندگان کافه‌بازار: از Monolithic به Microservices

شروع

پنل توسعه‌دهندگان کافه‌بازار وابستگی زیادی به پروژه‌ی اصلی بازار داشت، و این موضوع ادامه‌ی توسعه پنل و اضافه کردن امکانات جدید رو سخت کرده بود. به روز نبودن، کثیفی کد و وابستگی‌های دست‌و‌پا گیر ما رو به سمتِ بازنویسی پنل توسعه‌دهندگان برد.

هدف ما از بازنویسی پنل، کاهش هزینه‌ی توسعه و نگهداری سیستم بود. در این مسیر روش‌های مختلفی رو امتحان کردیم که بعضی از اونها نتیجه‌ی عکس داشت. طوری که نه تنها هزینه توسعه رو کاهش نمی‌داد بلکه بیشتر هم می‌کرد.

از یک سیستم یکپارچه (Monolithic) شروع کردیم، تا تولید چندین مایکروسرویس (Microservices) رفتیم و در نهایت به یک طراحی ساده رسیدیم تا روند توسعه رو ساده‌تر کنیم.

در این مسیر با چالش‌های فنی و ساختاری مربوط به تیم مواجه شدیم و نکاتی رو یاد گرفتیم که در ادامه با هم می‌بینیم.

خلاصه‌اش اینکه:

  • فقط زمانی کاری رو انجام بدیم که واقعا بهش نیاز داریم و Over-engineering نکنیم. از قدیم هم گفتن «سری رو که درد نمی‌کنه، دستمال نمی‌بندن» :-)
  • یک تیم متمرکز با هدف مشخص، عملکرد بهتری می‌تونه داشته باشه.

تاریخچه پنل بازار: از بندر تا پیشخان

پروژه بندر

پس از عبور از چالش‌های اولیه و به ثبات رسیدن بازار، از اونجایی که رابط کاربری پنل به‌روز نبود (با هر اکشن دوباره بارگذاری می‌شد، با موبایل سازگار نبود یا به اصطلاح Mobile-Friendly نبود و سایر مسائل اینچنینی) و تجربه کاربری خوبی نداشت، و به دلیل در هم تنیده بودنش با کد‌های بک‌اند (قالب‌های جنگو) خوانایی‌اش رو از دست داده بود و توسعه‌اش دشوار شده بود، تیم توسعه‌دهندگانِ وقت تصمیم گرفت پنل توسعه‌دهندگان رو بازنویسی کنه؛ با این هدف که کلاینت وب از بک‌اند جدا بشه و توسعه‌ی هر کدوم ساده‌تر بشه.

از این رو، پروژه‌ی «بندر» در سال ۱۳۹۴ ایجاد شد.

برنامه‌ها در «بندر» تخلیه و وارد «بازار» می‌شن :-D
برنامه‌ها در «بندر» تخلیه و وارد «بازار» می‌شن :-D


برای پیاده‌سازی کلاینت وب از AngularJS، و از اونجا که پروژه بازار با جنگو توسعه داده شده بود، برای بک‌اند از Django REST framework به صورت یک اپ روی کد بازار استفاده شد.

اما این پروژه در نهایت به دلیل Coupling زیادی که با کدهای دیگر بازار داشت متوقف و پس از مدتی کنار گذاشته شد.

مشکل این بود که اپ‌های جنگو پروژه بازار در مدل‌ها Coupling زیادی داشتند و بعضی از مدل‌ها در تعداد زیادی اپ استفاده می‌شدند و اگر می‌خواستیم برای پنل یک مدل رو تغییر بدیم باید در قسمت‌های مختلف تغییر ایجاد می‌کردیم. و اگر بقیه مدلی رو تغییر می‌دادن باید اپ پنل هم تغییر می‌کرد.

از طرفی جدا کردن داده‌های پنل هم کار سختی بود، چون همه‌ی داده‌ها روی پایگاه داده‌ی بازار بود. مثلا برای جدا کردن سرور بررسیِ برنامه‌ها و دسترسی به داده‌های مورد نیاز، یا باید مستقیما به دیتابیس بازار وصلش می‌کردیم یا روی بازار یک API می‌گذاشتیم تا داده‌های مربوط به پنل بررسی برنامه‌ها رو فراهم کنه.

بعد از این موضوع یک سری جلسه داشتیم که برای حل این مشکل چه کار کنیم؟ مثلا یک پایگاه داده‌ی مشترک ایجاد کنیم و همه بتونن ازش API بگیرن؟ اما این جلسات به تصمیم خاصی نرسید...

کمی از پروژه‌ی بندر

حدود ۳ ماه از کار روی پروژه بندر گذشته بود و ۲ هفته‌ی آخر سرعت و تلاش‌مون رو بیشتر کردیم که به OKR برسیم اما در انتها کدی که تولید شده بود به دلیل عجله برای اتمامِ پیاده‌سازی، کیفیت خوبی نداشت و شاید بدتر از کد قبلی پنل بود. در انتهای OKR یک سیستم باگی داشتیم که تا حدودی کار می‌کرد.

در فصل بعد مدیر محصول تغییر کرد و تیم به ۲ بخش تقسیم شد. قرار شد یک تیم پنل بررسی برنامه رو پیاده‌سازی کنه و تیم دیگه روی پنل توسعه‌دهندگان کار کنه.

از اونجایی که برای ادامه‌ی پروژه بندر با تخمین تیم حداقل ۲ ماه باید باگ‌فیکس انجام می‌شد، مدیر محصول همراه تیم تصمیم گرفتن که کار روی پروژه بندر متوقف بشه و تیم روی پنل قبلی فیچر بده.

بعد از یکی دو فیچر که به پنل قبلی اضافه کردیم، به دلیل هزینه‌ی فنی زیاد پیاده‌سازی دوباره تصمیم بر این شد که روی بندر کار کنیم. اما زمونه عوض شده بود D-:

بندر خراب شده بود و دیگه کار نمی‌کرد
بندر خراب شده بود و دیگه کار نمی‌کرد


مشکل این بود که در بازه‌ ۲ ماهه‌ای که پروژه بندر متوقف شد، کدهای بازار تغییرات زیادی کرده بود و چون کدهای بندر روی بازار مرج نشده بودن، دچار کانفلیکت‌های زیاد شده بود و درست کردنش به تغییر در بخش زیادی از بندر نیاز داشت.

از طرف دیگه، چون کدهای بازار در اون زمان Test Coverage خوبی نداشت، حتی بعد از رفع کانلفیکت‌ها مرج کردن بندر بدون ریسک نبود. چون ممکن بود بخش‌های دیگر بازار هم خراب بشه.

حجم زیاد تغییرات و وابستگی تمام اپ‌های جنگو روی مدل‌ دیتا باعث شده بود که با هر تغییر روی یک مدل، قسمت‌های دیگه هم نیاز به تغییر داشته باشند و بندر با تعطیل شدنِ موقت، عملا از کار افتاده بود.

این موضوع باعث شد تصمیم بگیریم پنل توسعه‌دهندگان رو همراه با داده‌های مربوطه از بازار جدا کنیم. اما در ادامه فهمیدیم که این جداسازی به اون سادگی که فکر می‌کردیم نیست :-D


پیشخان کافه‌بازار

یک سال از پایان پروژه بندر گذشته بود. علاوه بر مشکل Coupling پنل با کدهای بازار—که به کند شدن تیم، معطل شدن برای مرج و … منجر می‌شد—توسعه‌ی اون به دلیل کثیفی کد در طی زمان سخت شده بود، باگ زیاد رخ می‌داد و عیب‌یابی هم دشوار بود.

این‌ها دلایلی بود که ما رو به سمت بازنویسی پنل برد.

بنابراین جداسازی و بازنویسی پنل توسعه‌دهندگان رو شروع کردیم. و چون پنل مسیرِ ورودی برنامه‌های بازار بود، این پروژه «پیشخان توسعه‌دهندگان کافه‌بازار» نام گرفت.

در اون زمان (سال ۱۳۹۵) تیم‌های فنی کافه‌بازار به سمت استفاده از معماری مایکروسرویس در حرکت بودن و تب مایکروسرویس در شرکت داغ بود.

با دنبال کردن این جریانِ فنی، ایده‌ی ما برای جداسازی پنل از کافه‌بازار این بود که برای اهداف مختلف، سرویس‌های مجزا تعریف کنیم و تکه‌های مختلف رو در این سرویس‌ها پیاده‌سازی کنیم. چون:

  1. قسمت‌های مختلف کارکرد متفاوت از هم داشتن و در آینده تیم‌های دیگه‌ای می‌تونستن صاحب این سرویس‌ها بشن.
  2. سرویس‌ها می‌تونستن از تکنولوژی‌های متفاوتی استفاده کنن. البته در عمل با توجه به دانش و تجربه‌ای که داشتیم اکثر سرویس‌ها با پایتون و Django REST framework توسعه داده شدن.

برای این منظور سرویس‌های زیر طراحی شدن:

  • ناشر: مدیریت اطلاعات حساب توسعه‌دهنده‌ها و جدا کردن اونها از کاربران عادی.
  • مصدق: احراز هویت با نام کاربری و رمز عبور و احتمالاً در ادامه با ایمیل و یا با OAuth که تیم‌های دیگه هم بتونن ازش استفاده کنن.
  • اَپدارچی: مدیریت برنامه (اطلاعات و بسته‌ها و…) و فرآیند انتشار.
  • دَخل: مدیریت اطلاعات مالی برنامه مربوط به پنل توسعه‌دهندگان.
  • هَشتی: به عنوان API Gateway

و…

(در ادامه برای سادگی از عبارت «کافه» برای اشاره به پروژه بازار استفاده شده)

سرویس مصدق

سرویس مصدق نقشِ تصدیق اطلاعات کاربر رو داشت :-D و چون خودش اطلاعات کاربر رو نداشت باید از کافه اطلاعات کاربر رو می‌پرسید و توکن JWT رو تولید می‌کرد.

سرویس ناشر

ناشر بیشترین امکان برای جداسازی داده‌ها رو داشت و برای شروع با این سرویس پیش رفتیم. اما کافه به اطلاعات ناشر و کارپردازهای اون همچنان نیاز داشت و تصمیم گرفتیم با تغییر این اطلاعات در ناشر، داده‌های کافه رو هم به روز کنیم اما این وابستگی بصورت بدهی فنی (Technical Debt) موند و بازپرداخت نشد.

سرویس اَپدارچی؛ پروکسی یا لگاسی؟

اَپدارچی قرار بود تمام اطلاعات برنامه رو تا پیش از انتشار داشته باشه و انتظار داشتیم به یک سرویس نگهداری برنامه‌ها تبدیل بشه که دیگران ازش اطلاعات برنامه رو می‌پرسن.

اما مشکل این بود که خیلی از سرویس‌های قدیمی به اطلاعات اولیه برنامه (مثلا package_name) نیاز داشتن و بر این مبنا کار می‌کردن و جدا کردن این داده‌ها از پروژه‌ کافه، نیازمند همکاری تعداد زیادی از تیم‌ها بود. در حالی که مزیت این جداسازی در لحظه نسبت به سختی‌ انجامش کم بود و چندان قابل توجیه نبود.

از این بابت اطلاعات و لاجیک (Business Logic) کلی برنامه در کافه موند و ما لاجیک مربوط به پنل توسعه‌دهندگان رو با این سرویس جدا کردیم با این امید که مشکل جدا کردن اپ از کافه در آینده برطرف بشه.

از اونجا که اَپدارچی به دلیل نیاز به سینک اطلاعات با کافه نمی‌تونست دیتابیس مستقل خودش رو داشته باشه، باید اطلاعات برنامه رو از کافه می‌پرسید. برای این کار یک اپ به اسم Capi روی کافه‌ توسعه دادیم (مخفف Cafe API) که اطلاعات مد نظر سرویس‌های دیگه (مثل اَپدارچی) رو فراهم کنه.

اما پرسیدن اطلاعات برنامه از کافه در قسمت‌های مختلفِ لاجیک، کار بیهوده‌ای بود و کلی رفت و برگشت باید انجام می‌شد. در شرایطی باید اطلاعاتی خونده می‌شد و بررسی‌هایی انجام می‌شد و…

به دلیل وابستگی زیادی که لاجیک اَپدارچی به اطلاعات کافه داشت، در نهایت تصمیم بر این شد که داخل اپ Capi این چک‌ کردن ها انجام بشه و میزان قابل توجهی از لاجیک اَپدارچی، داخل Capi قرار بگیره.

به مرور زمان، اَپدارچی به پراکسی تبدیل شده بود که درخواست رو از سرویسی می‌گرفت و به Capi می‌گفت و پاسخ رو برمی‌گردوند.

از این بابت وجودش دیگه مزیتی نداشت و تنها کارها رو برای توسعه‌ی سیستم سخت کرده بود:

  • برای هر تغییر مرتبط با برنامه، یک مرج ریکوئست روی اَپدارچی اضافه می‌شد (علاوه بر Capi)
  • ارتباطات اضافی در شبکه به سیستم تحمیل می‌کرد

و این شروع حذف این نوع سرویس‌ها از پیشخان بود.

نتیجه‌‌ای که ما از این موضوع گرفتیم، این بود که:

«قبل از اضافه کردن مایکروسرویس باید به جدا کردن داده‌ها فکر کرد (ما اول به جدا کردن لاجیک فکر کرده بودیم). جایی که داده‌ها خیلی در هم تنیده‌ست، شاید ایجادِ API روی سیستم لگاسی (legacy system) ایده‌ی بهتری باشه»

در واقع برای هدف اَپدارچی، اپ Capi راه حل ما بود و نه یک سرویس جدا.

سرویس دخل

ایده ما این بود که لاجیک‌های مالی پنل رو در سرویس جدایی توسعه بدیم و از کافه جداشون کنیم. با این هدف که تیم توسعه‌دهندگان راحت‌تر بتونه روی این سرویس‌ کار کنه و تغییر ایجاد کنه.

برای این کار ابتدا لاجیک مربوط به تصفیه حساب و فاکتور فروش و… رو جدا کردیم و روی دخل پیاده کردیم.

سرویس قلک (که اطلاعات و لاجیک مالی کافه رو داشت) برای تهیه‌ی فاکتور و فایل فروش باید کوئری‌های سنگینی اجرا می‌کرد که خیلی زمان‌بر بود. طوری که حتی برای تعدادی از توسعه‌دهندگان به دلیل حجم بالای تراکنش‌ها ممکن بود درخواست تهیه گزارشات مالی با timeout مواجه بشه. از طرفی، تیمی که توسعه قلک رو به عهده داشت زمانی برای بهبود پرفورمنس تهیه‌ی گزارش مالی برای پنل توسعه‌دهندگان نداشت.

از این بابت فکر کردیم می‌تونیم در سرویس دخل این داده‌ها رو به صورت روزانه از قلک بگیریم و برای بازه‌ی زمانی مدنظر جمع کنیم و خیلی سریع‌تر این فاکتور و فایل‌ها رو بسازیم.

اما مشابه نیاز اَپدارچی به داده‌های کافه، دخل نیاز زیادی به داده‌های قلک داشت.

برای رفع این نیاز یک اپ به نام Gapi (مخفف Ghollak API) روی قلک توسعه دادیم که داده‌های مورد نیاز سرویس‌ها رو با API فراهم کنه.

اما لاجیک دخل هم وابستگی زیادی به داده‌های مختلف قلک داشت و کلی رفت و برگشت باید انجام میشه. به همین دلیل در عمل این قسمت از لاجیک‌ها داخل Gapi پیاده سازی و رفته رفته سرویس دخل هم به سرنوشت اَپدارچی دچار شد.


کمی از جزییات فنی

پروتکل ارتباطی

برای ارتباط بین سرویس‌ها، گزینه‌هایی مثل REST و gRPC مطرح بود و از اونجا که تعداد ریکوئست‌های پنل چندان زیاد نبود و مسئله پرفورمنس مطرح نبود، از REST استفاده کردیم تا سرعت توسعه رو بالا ببریم.

یک کار خوب که در ابتدا انجام دادیم این بود که با تحقیق روی قراردادهای های موجود، برای API های پیشخان style-guide آماده کردیم تا همه‌ی سرویس‌ها برای درخواست‌ها و پاسخ‌ها ساختار یکسانی داشته باشن.

این راهنما که شامل نحوه‌ی نام‌گذاری resource ها، کوئری‌ها، نحوه‌ی ارسال خطا، status code های مشخص و… می‌شد، کمک زیادی به یکپارچه بودن API ها کرد و باعث ساده‌تر شدنِ طراحی API ها و استفاده از اون‌ها در قسمت‌های مختلف مثل فرانت‌اند وب شد.

توسعه و دیپلوی

برای ساده‌ کردن اجرای سرویس‌ها، همه‌ی اونها رو داکرایز کردیم که در اون زمان خیلی در شرکت بدیهی نبود اما امروز بخشی عادی از روند کاریمون هست.

رفتن سمت معماری مایکروسرویس‌ شرایط توسعه رو متفاوت کرده بود. برای کار روی پیشخان باید تمام سرویس‌ها رو همراه با پروژه کافه‌ و قلک روی لوکال بالا می‌آوردیم، طوری که بتونن همدیگه رو ببینن و با هم صحبت کنن.

سرویس‌ها از راست: اَپدارچی، کافه، مصدّق، ناشر، دَخل، قلک
سرویس‌ها از راست: اَپدارچی، کافه، مصدّق، ناشر، دَخل، قلک


این در حالی بود که بالا آوردن پروژه کافه روی لوکال برای بار اول به تنهایی یکی دو روز طول می‌کشید. علاوه بر این، حالا سرویس‌هایی اضافه شده بودند که با هر تغییر تعدادی از اونها باید متوقف و دوباره اجرا می‌شدند.

با تغییر هر سرویس باید:

  1. کل یا بخشی از سرویس‌ها رو پایین می‌آوردیم
  2. تغییرات اون سرویس(ها) رو با گیت می‌گرفتیم
  3. روی لوکال داکر (Docker) رو بیلد می‌کردیم
  4. دوباره سرویس‌ها رو بالا می‌آوردیم.

این پروسه، توسعه‌ی هر دو سمتِ فرانت‌اند و بک‌اند پروژه رو کند می‌کرد.

برای سرعت دادن به این کار، یک ابزار با fabric توسعه دادیم که از فایل docker-compose پروژه‌ها استفاده می‌کرد و سرویس‌های اون‌ها رو بالا می‌آورد و بعد برای اینکه سرویس‌ها بتونن با هم در ارتباط باشن، داخل هر کانتینر (Container) در فایل hosts آی‌پی بقیه‌ی سرویس‌ها رو وارد می‌کرد که البته چالش‌هایی هم داشت. برای مثال اگر یک سرویس هنوز بالا نیومده بود، باید صبر می‌کرد تا اون سرویس بالا بیاد و دوباره تلاش کنه، که این کار رو کند می‌کرد.

بعدها ابزار توسعه‌ی دیگه‌ای رو استفاده کردیم که یکی از تیم‌های کافه‌بازار توسعه داده بود. و در اون به جای وارد کردن آی‌پی هر کانتینر در بقیه، از docker compose network استفاده می‌کرد.


چالش‌های فنی

با رفتن تیم‌ها به سمتِ مایکروسرویس ما هم در تیم توسعه‌دهندگان هم تصمیم گرفتیم با آینده‌نگری به دلایلی که پیش‌تر گفته شد، زمانی رو صرف این کنیم که سیستم رو به چه سرویس‌هایی بشکنیم.

به مرور متوجه شدیم که شکستنِ سیستم به تعداد زیادی سرویس‌، باعث طولانی شدن کار شده. مثلاً هر تسک کوچک بک‌اندی، ۳ مرج ریکوئست می‌خواست که گاهی ۲ نفر در تیم باید اون‌ها رو بازبینی می‌کردن و این کارمون رو کند می‌کرد.

علاوه بر این، پیچیدگی معماری و نداشتن راهنمای مشخص، توضیح کل سیستم به نیروهای جدید رو سخت کرده بود.

تعداد بالای سرویس‌ها، روند نگهداری سیستم رو هم طولانی می‌کرد. برای مثال بروزرسانی پکیج‌هایی که باگ‌فیکس برای اونها اومده بود—که باید برای تعداد زیادی سرویس انجام می‌شد. یا جمع‌آوری لاگ خطا از سرویس‌های مختلف.

بعلاوه نیازی که بعضی از سرویس‌ها برای صحبت با کافه و قلک داشتن، صرفاً پیچیدگی به سیستم اضافه کرده بود و وجود اون سرویس‌ها رو زیر سوال برده بود.

همانطور که پیش‌تر اشاره شد، دو سرویس اَپدارچی و دخل از این دسته بودند.

در اَپدارچی: قرار بود اطلاعات مربوط به برنامه—که تا قبل از انتشار که روی بازار بهش نیاز هست—رو مدیریت کنه. به این شکل که همه‌ی ریلیزها (Package Releases) در این سرویس جمع بشن و کافه صرفا برنامه منتشر شده رو دریافت کنه.

ولی در عمل این جداسازی اتفاق نیفتاد و اَپدارچی به یک سرویس واسط بین سرویس‌های پیشخان و کافه‌ تبدیل شد و در نهایت این سرویس حذف شد. (هرچند جداسازیِ برنامه از کافه با تمام چالش‌هایی که داشت شاید امکان پذیر بود اما به سختی‌ای که داشت نمی‌ارزید)

همینطور سرویس دخل بعد از پیاده سازیِ گزارش‌گیری، با تغییر schema دیتابیس قلک در فرآیند کاریِ تیم دیگه‌، از کار افتاد و به روز نگه‌داشتن لاجیک دخل متناسب با تغییرات قلک از توان تیم ما خارج بود. و این باعث شد کم کم به این نتیجه برسیم که گزارشات مالی رو خودِ قلک باید ایجاد کنه. در نهایت لاجیک دخل به قلک و اپ Gapi منتقل و سرویس دخل پاک شد.

شاید اگر داخل Gapi این امکان رو توسعه داده بودیم و برای اون تست می‌نوشتیم طوری که با تغییر اسکیما تست‌ها رد بشه، این اتفاق پیش نمی‌اومد و دردسر صحبت با یک سرویس اضافه (دخل) رو هم نمی‌داشتیم.

بعد از نمایان شدن این مشکلات، با بررسی مزایا و معایب برگشتن به یک معماری ساده و حذف پیچیدگی‌های وقت‌گیر، تصمیم گرفتیم سرویس‌هایی رو از سیستم حذف کنیم.

این سرویس‌ها صرفاً به پراکسی تبدیل شده بودن و در چشم‌انداز محصول هم دیده نمی‌شد تیمی بوجود بیاد که مدیریت این سرویس‌ها رو به عهده بگیره.

از این بابت سرویس‌های اَپدارچی، دخل و مصدق از پیشخان حذف شدن و لاجیک اونها در قسمت‌های دیگه قرار گرفت:

  • کارهای اَپدارچی داخل اپ Capi در کافه
  • کارهای دخل در اپ Gapi در قلک
  • کارهای مصدق در سرویس ناشر
سرویس‌ها دو به دو در هم ادغام شدن :-D
سرویس‌ها دو به دو در هم ادغام شدن :-D


سرویس‌های دیگری هم توسعه داده شده بودن که پیش از ریلیز نهایی از پیشخان حذف شدن.

در ابتدای شروعِ پیشخان، تکنولوژی‌های زیرساختی در شرکت در حال تحول بود و با ارائه یک سرویس جدید و برای تست و فیدبک اولیه، تیم‌های دیگه سراغ استفاده از این سرویس‌ها می‌رفتن.

زمانی که ما توسعه‌ی پیشخان رو شروع کردیم روش یکپارچه‌ای برای ذخیره‌ی فایل‌ها در سطح شرکت نداشتیم. از این بابت برای آپلود و دانلود فایل‌های استاتیک در پیشخان، یک سرویس مجزا به نام «انبار» توسعه دادیم که نیازهایی مثل آپلود، دانلود و تولید لینک دانلود زمان‌دار و… رو برطرف کنه.

این سرویس رو با Lua روی nginx توسعه دادیم تا پرفورمنس بهتری مثلا نسبت به پایتون و… داشته باشه.

اما پیش از اینکه از این سیستم در پروداکشن استفاده کنیم سرویس Ceph Object Storage توسط بچه‌های زیرساخت کافه‌بازار راه اندازی شد و تصمیم بر این شد که فایل‌های استاتیک پیشخان روی Ceph نگه داشته بشن و به علاوه، برای تست این سرویس زیر بار پروداکشن، پیش از نهایی شدن پیشخان برای فایل‌های پنل قبلی هم از Ceph استفاده بشه.

در نتیجه سرویس انبار پیش از استفاده از پیشخان حذف شد.

شاید با ارتباط و برنامه‌ریزی کردن در سطح بالاتر (بینِ محصولی) می‌شد از توسعه و بعد حذف سرویس انبار اجتناب کرد. به این شکل که تیم توسعه‌دهندگان فعلا این بخش از پروژه رو متوقف کنه و روی قسمت‌های دیگه متمرکز بشه. در هر صورت این هماهنگی دیر اتفاق افتاد و باعث کار اضافی برای تیم شد.

چالش دیگه‌ای که تیم داشت این بود که سازوکار اجزای سیستم در پنل قبلی برای تیم شفاف نبود. مثلاً وضعیت‌های مختلف برنامه و فرآیند انتشار یا وضعیت‌های مختلف ناشر که بطور مشخص چه چیزهایی هستن و در چه شرایطی این وضعیت‌ها اعمال می‌شن و…

به همین دلیل همه به مدیر محصول تیم—که از ابتدا در جریان موضوعات فنی پنل قبلی و پیشخان بود—مراجعه می‌کردن تا درباره پیاده‌سازی سرویس‌ها تصمیم بگیرن. این وابستگی هم تمرکز محصولی رو از مدیر محصول می‌گرفت و هم تبدیل به گلوگاه برای تیم شده بود.

در زمینه‌ی بردن پیشخان روی پروداکشن و گرفتن فید‌بک هم می‌تونستیم بهتر عمل کنیم.

کد‌هایی که زده بودیم دیر روی پروداکشن رفت و اگر این کار رو بطور تدریجی انجام داده بودیم، باگ‌ها زودتر مشخص و فیکس می‌شدن و با انبوهی از باگ مواجه نمی‌شدیم.

سعی کردیم این مسئله رو با جایگزین کردن بک‌اند پنل قبلی با API های سرویس‌های جدید انجام بدیم اما شاید می‌تونستیم روی پنل جدید قسمت‌هایی که انجام شده رو بصورت بتا منتشر کنیم و فید‌بک بگیریم و به مرور پنل رو تکمیل کنیم تا زمانی که امکان جایگزین کردنش با پنل قبلی فراهم باشه.


چالش‌های غیرفنی

مشکلاتِ غیر فنی عمدتاً حول تغییرات زیاد در تیم توسعه‌دهندگان و تغییر تمرکز تیم بود.

در زمینه‌ی عدم تمرکز، چالش‌ تیم توسعه‌دهندگان این بود که باید همزمان با انجام پروژه پیشخان، کارهایی روی پنل قبلی انجام می‌داد. بخشی از این کارها از سمت تیم‌های بررسی، حقوقی و مارکتینگ می‌اومد و بخشی دیگه از سمتِ تیم‌های فنی و این میزان context switch تمرکز تیم رو می‌گرفت و توسعه‌ی پیشخان رو کند می‌کرد.

در تغییرات تیم و نیروها، طی ۱ سال و نیم از شروع پیشخان، ۲۰ جابه‌جایی نیروی فنی و چندین نوبت تغییر ساختار در تیم توسعه‌دهندگان داشتیم:

  • در بازه‌ای یک تیم ۱۲-۱۳ نفری داشتیم.
  • دست‌کم دو مرتبه به ۲ تیم با هدف یکسان تقسیم شدیم.
  • دو مرتبه به ۲ تیم با اهداف متفاوت تقسیم شدیم:
    • در بازه‌ای یک تیم روی ارائه آمار جدید برنامه‌ها کار می‌کرد و تیم دیگه روی پیشخان.
    • در بازه‌ای یک تیم نیاز حقوقی بقیه تیم‌ها رو پاسخ می‌داد و یک تیم روی پیشخان کار می‌کرد.

این حجم تغییرات باعث می‌شد تیم از تجربه‌های قبلی درس نگیره و با تغییر افراد و ساختار، مدام در حال تجربه روش‌های مختلف باشه.

مثلاً بعد از تغییرات تیم، مسائل مشابهی در جلسات رترو مطرح می‌شد. از این قبیل که متدولوژی توسعه چی باشه؟ بورد چطور باشه؟ دیجیتال باشه یا نه؟ کارها رو تخمین بزنیم یا نه و چطور تخمین بزنیم و… . و در طی زمان درگیر مسائل تکراری غیرفنی بودیم.

مشکلِ دیگه‌ این بود که در این تیم‌ها در طول زمان، معمولاً زمان کاری مشترکِ خوبی نداشتیم.

بخش قابل توجهی از تیم نیروی پاره‌وقت بود و بخش کمی تمام‌وقت. این در حالی بود که یک روز از زمانِ حضورِ نیرو‌های پاره‌وقت صرف جلسات تیمی می‌شد.

نیازِ تیم به تعامل و نداشتنِ زمانِ حضور مشترک کافی، باعث کند شدنِ کار می‌شد. برای مثال زمانی که نیاز به تصمیم‌گیری فنی بود و فرد مورد نظر حضور نداشت. یا صاحب یک سیستم و فردی که برای اون سیستم merge request ارسال کرده در زمان‌های متفاوتی در تیم حاضر می‌شدند و رفت و برگشتِ کد زیاد می‌شد.

این عدم حضور، حسِ انجامِ کار و رسوندنِ محصول در تیم رو هم کم می‌کرد. چون همیشه تقریباً نیمی از تیم در لحظه حضور نداشت.

از نظر بررسی پیشرفت کار در طی زمان هم مشکل داشتیم.

در طی توسعه‌ی پیشخان، بیشترِ اوقات فرآیند تیمی رو بصورت کمّی دنبال نمی‌کردیم و بطور حسی تغییر روش می‌دادیم. مثلاً ابتدا با Kanban شروع کردیم. بعد سراغ Scrum رفتیم، بعد دوباره به Kanban برگشتیم و دوباره به Scrum تغییر روش دادیم. در بازه‌‌ای کمی به XP نزدیک شدیم (انجام pair programming). این تغییر ها معمولاً با تغییر تیم/افراد رخ می‌داد و خروجی صحبت افرادِ تیم در اون زمان بود.

پس از گذشت حدود ۱ سال از کار تیم روی پیشخان، هر OKR این حس رو داشتیم که این فصل کارهای پیشخان تموم میشه اما کار ها کِش میومدن! و این حس که مشکل داریم خیلی دیرتر دیده شد.

شاید اگر دنبال کردنِ پیشرفت پروژه رو جدی‌تر می‌گرفتیم و تیمی با تمرکز بیشتر و تغییر کمتر داشتیم، می‌تونستیم تصمیم‌های مربوط به معماری که گرفته بودیم رو زودتر بازبینی و مشکلاتش رو اصلاح کنیم.


حرف پایانی

در این نوشته با مرور مسیر ۳-۴ سال اخیر پنل توسعه‌دهندگان کافه‌بازار، سعی کردیم تجربیات خودمون رو در زمینه‌ی فنی و غیرفنی به اشتراک بذاریم. از مهم‌ترین‌ها می‌تونیم به موارد زیر اشاره کنیم:

  • ما در طراحی مایکروسرویس هرچند مطالعه داشتیم و می‌دونستیم که باید «از سرویس اندک شروع کنیم و زمانی نیاز شد اون رو بشکنیم» اما تجربه عملی و تحلیل درستی از «نیاز» و «زمانی» که باید این کار رو انجام بدیم نداشتیم.
  • وقتی یک سیستم لگاسی داریم که نمیشه داده‌ها رو ازش جدا کرد، بهتره سرویسی که به اون داده‌ها نیاز داره رو کنار اون سیستم لگاسی توسعه بدیم.
  • تغییرات ساختاری تیم، نرخ بالای ورود و خروج نیروها—که تجربه تیم رو از بین می‌برد—و عدم تمرکز تیم در طی زمان، عامل دیگری بود که باعث کندی روند توسعه‌ و شناسایی مشکلات طراحی سیستم شد.

در انتها از دوستانم که در گردآوری و تدوین این نوشته کمک کردند تشکر می‌کنم:

محمدرضا منتظری، محمدرضا بیکی (بیوک)، محمدحسین نوروزی، شروین حاجی‌اسماعیلی، وحید معصومی و آزاده آقایی.