تجربهٔ توسعهٔ سرور دنگی‌پال سامیک در ۲ هفته

توییتِ فلاسک: عضو جدید خانواده‌ دنگی‌پال ^_^
توییتِ فلاسک: عضو جدید خانواده‌ دنگی‌پال ^_^

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

آخرین چالشی که در سال ۹۷ داشتیم، طراحی یک سامانه برای سازماندهی و هماهنگی کار مهماندارها و آشپزهای کافی‌شاپ‌ها بود. به دلایلی، تنها ۲ هفته برای پیاده‌سازی کامل این برنامه فرصت داشتیم.

انتخاب تکنولوژی‌ها

اولین چالش (و سخت‌ترین) انتخاب تکنولوژی‌های مناسب بود. پروژهٔ نهایی می‌بایست:

  • در ۲ هفته آمادهٔ عرضه به بازار باشد؛
  • از رویدادهای Real-time پشتیبانی کند؛
  • توانایی ارائهٔ سرویس به حداقل ۱۰۰ کاربر آنلاین هم‌زمان را داشته باشد؛
  • با کمترین حاشیهٔ نگهداری، امکان توسعه و اضافه‌کردن امکانات داشته باشد.

پروژه از دو قسمت Front-End که یک اپلیکیشن PWA و Back-End تشکیل می‌شد، بنابراین هنگام انتخاب تکنولوژی سمت سرور که برعهدهٔ من بود، باید به چابکی پیاده‌سازی و استفاده از آن در سمت Front-End هم دقت می‌کردم.

برای معماری نحوهٔ مبادله‌ی اطلاعات، به جای REST از GraphQL استفاده کردم. GraphQ یک زبان برای نوشتن Query و درخواست‌های تغییر در داده (Mutation) است که اولین بار توسط Facebook در سال ۲۰۱۵ معرفی شد. استقبال مثبت جامعهٔ توسعه‌دهندگان از آن، باعث به‌وجودآمدن و رشد اکوسیستم‌هایی با نام Apollo و Prisma شد. هر دوی این اکوسیستم‌ها امکانات متعددی برای توسعهٔ Server و Client ارائه می‌دهند، اما اکوسیستم Prisma برای توسعهٔ سرور روی محیط اجرای Node.js امکانات بیشتر و پشتیبانی قوی‌تری نسبت به Apollo ارائه می‌داد.

علت انتخاب Node.js به عنوان Runtime Environment یا محیط اجرا این بود که:

  • در دنگی‌پال قبلاً دو Micro Service دیگر با همین تکنولوژی توسعه داده بودم؛
  • امکان استفاده از زبان آشنا و موردعلاقه‌ام، TypeScript، را داشتم؛
  • برای توسعهٔ امکانات Real Time دست بازتری داشتم؛
  • از طرف دیگر با درنظر‌داشتن تجربه‌های قبلی، حاشیهٔ نگهداری کمتری داشت.

البته، اگر فرصت بیش‌تری در اختیار داشتم، قطعاً از Python 3 برای توسعه استفاده می‌کردم؛ اما:

  • در توسعه‌ٔ وب با پایتون تجربهٔ خیلی کمی دارم؛
  • تجربهٔ نگهداری از یک برنامهٔ پایتون را نداشتم و به خاطر ارزش محصول برای مارکت نمی‌توانستم ریسک کنم.

به خاطر سازماندهی بهتر پروژه و علاقهٔ شخصی، از TypeScript نسخهٔ ۳.۳ برای توسعه استفاده کردم. تایپ‌اسکریپت این امکان را به من می‌داد که:

  • ریزالورها (Resolvers) را بهتر سازماندهی کنم؛
  • از Object Literalها برای افزایش خوانایی کد و آسان‌ترکردن توسعهٔ آن در آینده استفاده کنم؛
  • از Destructorها برای استفادهٔ بهینه از مقادیر دریافتی از سوی Client و مدیریت آن‌ها استفاده کنم؛
  • از لینتر (Linter) قوی‌ای مثل TSLint برای یافتن خطاهای کدنویسی قبل از اجرا استفاده کنم.

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

فریم‌ورک Yoga صرفاً یک لایه یا Middleware به فریم‌ورک آشنای Express اضافه می‌کند تا سرور توانایی خواندن و پردازش زبان GraphQL و ارائه پاسخ استاندارد را داشته باشد.

نرم‌افزار Prisma یک Data Layer است که مشابه ORMها عمل می‌کند و علاوه بر این، ابزارهای متعددی را برای مدیریت پایگاه‌داده ارائه می‌دهد، به این گونه‌ که در کمتر حالتی، نیاز به ارتباط مستقیم با پایگاه‌داده به وجود می‌آید و Prisma تقریباً به خوبی از پس کار می‌آید.

در طول فرآیند توسعه متوجه شدم که Prisma یکی از امکانات اساسی موردنیاز پروژه را هنوز ارائه نداده و آن امکان Aggregate کردن داده‌هاست؛ مثلاً شما نمی‌توانید جمع اعداد یک ستون را از آن بخواهید. دو راه‌حل نه چندان خوب برای این مشکل وجود دارد:

  • روی داده‌های موردنظر خودتان Query بزنید و مقدار Aggregate را خودتان به‌دست آورید؛ یا
  • مستقیماً به پایگاه‌داده وصل شوید.

من راه اول را بیش‌تر می‌پسندم. چون پریزما امکان فقط انتخاب یک ستون را با کمترین هزینه می‌دهد؛ از طرف دیگر Aggregation داده‌ها جزو امکانات اساسی هر ORM و ORM-like است و توسعه‌دهندگان هم قول داده‌اند به زودی این امکان را اضافه کنند.

داده‌های قبلی

اطلاعات کاربران و پذیرنده‌ها قبلاً در یک پایگاه MySQL دیگر میزبانی می‌شدند که آن پایگاه توسط اپلیکیشن سرور اصلی دنگی‌پال استفاده می‌شد.

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

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

راه اول را رد کردم، چون ساختار پایگاه‌داده اصلی مرتب با نیازهای بازار در حال تغییر بود. پس به سراغ راه دوم رفتم. برای پیاده‌سازی آن، یک API تحت پرتکل REST به صورت داخلی طراحی کردیم؛ به این صورت که سرویس سامیک اطلاعات را از API موردنظر می‌گرفت و در یک پایگاه‌دادهٔ Redis به صورت موقت ذخیره می‌کرد.

اما؛ مشکل آن‌جا بود که برخی از داده‌های اساسی کاربران به صورت نامنظم تغییر می‌کرد. بنابراین به یک کانال ارتباطی دیگر برای باخبر‌کردن سرویس سامیک از تغییر در داده‌های کاربران نیاز داشتیم؛ به عبارت دیگر به یک Message Broker نیاز داشتیم که بتواند بین سرویس اصلی (که با PHP نوشته شده) و سرویس سامیک ارتباط برقرار کند.

برای پیاده‌سازی Broker ترجیح دادم به جای این که سراغ نرم‌افزارهایی مثل RabbitMQ بروم، از خود امکاناتی که Redis ارائه می‌دهد استفاده کنم. تجربهٔ قبلی بیشتری با Redis داشتم و از طرفی، حجم پیام‌های مبادله‌شده از طریق Redis هم بسیار کمتر بود و توسعهٔ سرویس اصلی برای کارکردن با Redis آسان‌تر و سریع‌تر بود.

تجربهٔ توسعه

من تجربه‌ی زیادی درمورد کارکردن با زبان GraphQL و فریم‌ورک Yoga نداشتم. آخرین چیزی که با آن توسعه داده بودم، یک سرویس Micro Blogging بود که البته هیچ‌وقت تکمیل نشد. بنابراین در شروع توسعه، باید مجدداً دانسته‌هایم را درمورد این تکنولوژی‌ها مرور می‌کردم که البته قابلیت‌های زیادی هم در طول یک سالی که با GraphQL و اکوسیستم Prisma آشنا بودم اضافه یا کم شده بودند.

به طور کلی، مراحل توسعهٔ یک برنامهٔ سمت سرور با اکوسیستم Prisma به این صورت است:

محتوای عکس: مراحل توسعه: ۱. طراحی Datamodel ۲. بهبود Datamodel ۳. طراحی Schema ۴. نوشتن Resolver ۵. بهبود Resolver ۶. تست Resolver و تکرار این فرآیند
محتوای عکس: مراحل توسعه: ۱. طراحی Datamodel ۲. بهبود Datamodel ۳. طراحی Schema ۴. نوشتن Resolver ۵. بهبود Resolver ۶. تست Resolver و تکرار این فرآیند

برنامه‌ها به طور کلی دارای دو نقشه یا شِما هستند که نحوهٔ کارکرد برنامه را شرح می‌دهند، Datamodel و Functionality. دیتامدل ساختار پایگاه‌داده را تعیین می‌کند. برای مثال، تعیین می‌کند که یک Type به نام Post داریم که دارای دو فیلد Title و Content از نوع String است. شِمای Functionality (که معمولاً آن را به نام Schema می‌شناسند) امکاناتی که برنامه در اختیار Client قرار می‌دهد به همراهٔ شیوهٔ استفاده از آن و ساختار پاسخ برنامه را تعیین می‌کند.

مستندات تولیدشدهٔ GraphQL Playground از شِمای Functionality سرور سامیک. در عکس لیستی از از Queryهای قابل‌استفاده توسط کلاینت‌های محرزشده به همراه مقدار بازگشتی آن‌ها قابل‌مشاهده هستند.
مستندات تولیدشدهٔ GraphQL Playground از شِمای Functionality سرور سامیک. در عکس لیستی از از Queryهای قابل‌استفاده توسط کلاینت‌های محرزشده به همراه مقدار بازگشتی آن‌ها قابل‌مشاهده هستند.

این رویکرد نوشتن شِما چند مزیت دارید، مثلِ:

  • دیگر نیاز به درگیری با HTTP Verbها چه در سمت Client چه در سمت Server نیست. همهٔ اطلاعات با POST ارسال می‌شوند و محتوای درخواست گویای همهٔ جزئیات موردنیاز است؛
  • درخواست‌ها و پاسخ‌ها قابل‌پیشبینی هستند: اگر درخواست مطابق شِما نبود، قبل از رسیدن به لایهٔ Logic متوقف می‌شود و پاسخ‌ها هم طبق شِمای مشخصی قالب‌بندی می‌شوند.

با همهٔ این تعریف‌ها، نوشتن لایهٔ Controller یا Logic حالا ساده‌تر از همیشه است: درخواست‌ها قابل‌پیشبینی هستند و هم‌چنین خطاهای سرور. پس، منِ توسعه‌دهنده فقط نیاز داشتم که Logicها و نحوهٔ مبادلهٔ داده‌های دریافتی با پایگاه‌داده و اطلاعات ارسالی را بنویسم. می‌توانم بگویم که GraphQL برای توسعهٔ چابک بهترین انتخاب بود.

بعد از طراحی شِماها و نوشتن Resolverهای لازم، نیاز بود که Resolverها تست شوند و در صورت امکان، کارآیی آن‌ها بهبود پیدا کند. به خاطر کمبود وقت و البته بی‌تجربگی خودم در توسعهٔ چابک، نتوانستم تست‌های اتوماتیک بنویسم. راهی که برای تست عملکرد Resolverها می‌ماند، تست آن‌ها به خصوص Logicهای حیاتی به صورت دستی انجام می‌شد.

...و در آخر

توسعهٔ با استک Node.js و TypeScript برای من همیشه لذت‌بخش بوده. توسعهٔ سامیک دو جنبه متفاوت از تمام پروژه‌های قبلی که تا حالا انجام داده‌ام داشت: یکی نیاز به چابک‌بودن و توسعهٔ جدی با اکوسیستم Prisma. توسعهٔ سامیک هم‌چنان در جریان است. سامیک حالا به صورت آزمایشی در برخی از کافه‌های تهران به صورت آزمایشی استفاده می‌شود و بعد از تعطیلات نوروز هم راهش را به کافه‌های بیشتری باز می‌کند.

اشتیاق دارم تا Scale کردن و توسعهٔ بخش‌های Real-time برنامه را در آیندهٔ نزدیک انجام دهم و نتیجه‌های آن را به اشتراک بگذارم.