GraphQL - نکات خوب و بد
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 15 دقیقه

GraphQL - نکات خوب و بد

GraphQL یک زبان کوئری برای APIها و یک runtime برای انجام آن کوئری‌ها با داده‌های موجود شما است. GraphQL یک تعریف کامل و قابل درک از داده‌ها در API شما فراهم می‌کند و همچنین به کلاینت‌ها قدرت این را می‌دهد که بپرسند دقیقا چه چیزی می‌خواهند، و نه بیشتر...

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

  1. نکات خوب
  2. آوردن داده دقیق
  3. یک درخواست، چند منبع
  4. سازگاری مدرن
  5. تخریب سطح فیلد
  6. نکات بد
  7. Cache کردن
  8. کارایی Query
  9. عدم تطابق داده‌ها
  10. 10) شباهت‌های طرح
  11. 11) نتیجه گیری

نکات خوب

آوردن داده دقیق

هر چقدر هم که بر روی اهمیت کاربردی بودن آوردن داده دقیق GraphQL تاکید کنیم، نمی‌توان گفت که بیش از حد بوده است. با GraphQL شما می‌توانید یک کوئری را به API خود بفرستید و دقیقا چیزی که می‌خواهید را به دست بیاورید؛ نه بیشتر و نه کمتر. واقعا به همین سادگی است. اگر این امکان را با طبیعت بصری مرسوم REST مقایسه کنید، درک خواهید کرد که این یک پیشرفت بزرگ در روشی که ما کارها را انجام می‌دهیم است.

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

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

موقعیتی را در نظر بگیرید که می‌خواهید یک اندپوینت API را فراخوانی کنید، و این اندپویت دو منبع دارد: هنرمند (artist) و آهنگ‌های او (tracks). برای این که بتوانید برای یک هنرمند خاص یا آهنگ‌های او درخواست کنید، شما یک ساختار API به مانند این خواهید داشت:

METHOD /api/:resource:/:id:

 با الگوی REST سنتی، اگر بخواهیم یک لیست شامل تمام هنرمندان را با استفاده از API فراهم شده بگردیم، باید یک درخواست GET را به منبع ریشه اندپوینت، به این صورت ارسال کنیم:

GET /api/artists

اگر ما یک کوئری برای تنها یک هنرمند از لیست هنرمندان بخواهیم چه؟ در این صورت باید به این صورت ID منبع را به اندپوینت متصل کنیم:

GET /api/artists/1

در اصل، ما باید دو اندپوینت مختلف را برای دریافت داده‌های مورد نظر خود فراخوانی کنیم. با GraphQL، هر درخواستی می‌تواند بر روی یک اندپوینت اجرا شود، در حالیکه انجام عمل‌ها و برگرشتن داده‌ها همگی در داخل خود کوئری تعریف شده‌اند. فرض کنید که می‌خواهید آهنگ و مدت زمان آن آهنگ از یک هنرمند را به دست بیاورید. با GraphQL، یک کوئری اینچنینی خواهیم داشت:

GET /api?query={ artists(id:"1") { track, duration } }

این کوئری API را راهنمایی می‌کند تا به دنبال هنرمندی با ID «۱» بگردد و سپس آهنگ و مدت زمان آن را بر می‌گرداند، و این دقیقا چیزی است که ما می‌خواهیم، نه بیشتر و نه کمتر. همین اندپوینت مشابه همچنین می‌تواند برای اجرای actionها داخل یک API هم انجام شود.

یک درخواست، چند منبع

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

موقعیتی را تصور کنید که کاربر می‌خواهد برای جزئیات یک هنرمند خاص (نام، ID، آهنگ‌ها و...) درخواست کند. با الگوی سنتی REST، این کار به حداقل دو درخواست به اندپوینت‌های /artists و /tracks نیاز خواهد داشت. گرچه با GraphQL، ما می‌توانیم تمام داده‌هایی که می‌خواهیم را در یک کوئری به مانند این مورد تعریف کنیم:

// درخواست کوئری

artists(id: "1") {

  id

  name

  avatarUrl

  tracks(limit: 2) {

    name

    urlSlug

  }

}

در اینجا، ما یک کوئری GraphQL تکی تعریف کردیم تا برای چندین منبع (هنرمندان و آهنگ‌ها) درخواست کنیم. این کوئری به این صورت منابع درخواست شده را بر خواهد گرداند:

// نتیجه کوئری

{

  "data": {

    "artists": {

      "id": "1",

      "name": "Michael Jackson",

      "avatarUrl": "https://artistsdb.com/artist/1",

      "tracks": [

        {

          "name": "Heal the world",

          "urlSlug": "heal-the-world"

        },

        {

          "name": "Thriller",

          "urlSlug": "thriller"

        }

      ]

    }

  }

}

همانطور که در داده‌های برگشتی در بالا می‌توان دید، ما منابع مربوط به هم /artists و هم /tracks را با تنها یک فراخوانی API دریافت کرده‌ایم. این یک امکان قدرتمند است که GraphQL فراهم می‌کند. همانطور که می‌توانید تصور کنید، کاربردهای این امکان برای ساختارهای API که به شدت قطعی هستند، نامحدود می‌باشد.

سازگاری مدرن

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

از آنجایی که GraphQL می‌تواند برای اتصال برنامه backend استفاده شود و نیازمندی‌های هر کلاینت (روابط تو در تو برای داده‌ها، دریافت فقط داده‌های مورد نیاز، نیازمندی‌های مصرف شبکه و...) را بدون اختصاص دادن یک API جدگانه برای هر کلاینت انجام دهد، این گرایش‌هایی جدید را تقبل می‌کند.

اکثر مواقع برای انجام این کار، این backend به چند میکروسرویس با عملکردهای متمایز تقسیم می‌شود. به این صورت، اختصاص دادن عملکردهایی خاص به میکروسرویس از طریق چیزی که آن را «دوختن طرح» می‌نامیم، ساده می‌شود. دوختن طرح، ساخت یک طرح عمومی از طرح‌های دیگر را ممکن می‌سازد. در نتیجه، هر میکروسرویس می‌تواند طرح GraphQL خود را تعریف کند.

برای به نمایش گذاشتن دوختن طرح، بیایید موقعیتی را در نظر بگیریم که ما دو API وابسته داریم. API جدید با نام «public Universes GraphQL» برای سیستم مدیریت رویداد Ticketmaster’s Universe، و یک API با نام Dark Sky weather بر روی Launchpad که توسط Matt Dionis‌ ساخته شده است. بیایید به دو کوئری که می‌توانیم به طور جداگانه در مقابل این دو API اجرا کنیم، نگاهی داشته باشیم. در ابتدا، با Universe AP، ما می‌توانیم جزئیات یک ID رویداد خاص را دریافت کنیم:

با Dark Sky weather API، ما می‌توانیم به این صورت جزئیات موقعی مکانی مشابه را به دست بیاوریم:

حال با دوختن طرح GraphQL، ما می‌توانیم عملیاتی را برای ادغام دو طرح انجام دهیم، به روشی که ما به راحتی بتوانیم آن دو کوئری را در کنار هم ارسال کنیم:

منسوخ کردن در سطح فیلد

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

برای مثال، ما یک API به مانند api.domain.com/resources/v1 داریم و در ماه‌ها و سال‌های بعدی، برخی تغییرات پیش خواهند آمد و منابع یا ساختارهای آن‌ها تغییر خواهند کرد. از این رو، بهترین کار در این شرایط این است که این API را به api.domain.com/resources/v2 بگشاییم تا تمام تغیرات اخیر را به دست بیاوریم.

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

در GraphQL، این که  APIها را در سطح فیلد منسوخ کنید، ممکن است. وقتی که یک فیلد خاص قرار است منسوخ شود، یک کلاینت خطای منسوخ کردن را در هنگام کوئری کردن فیلد دریافت خواهد کرد. پس از مدتی که کلاینت‌های زیادی از فیلد منسوخ شده استفاده نمی‌کنند، ممکن است این فیلد از طرح حذف شود.

در نتیجه، به جای ترجمه کامل API، این که به تدریج API را در گذر زمان بگشاییم بدون این که مجبور باشیم کل طرح API را مجددا ساختاربندی کنیم، ممکن است.

نکات بد

Cache کردن

Cache‌ کردن عبارت است از ذخیره سازی داده‌ها، تا درخواست‌های آینده برای آن داده‌ها بتوانند سریع‌تر انجام شوند؛ داده‌های ذخیره شده در یک cache می‌توانند نتیجه یک محاسبه زودتر یا تکرار داده‌های ذخیره شده در یک جای دیگر باشند. هدف cache کردن یک پاسخ API، در درجه اول برای این است که پاسخ‌های درخواست‌های آینده را سریع‌تر به دست بیاوریم. بر خلاف GraphQL، cache کردن در مشخصه HTTP ساخته شده است که APIهای RESTful می‌توانند به کار بگیرند.

با استفاده از REST، شما می‌توانید با استفاده از URL به منابع دسترسی داشته باشید، و از این رو خواهید توانست که در سطح منبع cache کنید؛ زیرا شما URL منبع را به عنوان یک مشخص کننده دارید. در GraphQL، با توجه به این که هر کوئری حتی با وجود این که بر روی موجودیت مشابهی فعالیت می‌کند، می‌تواند متفاوت باشد، این مسئله پیچیده می‌شود.

در یک کوئری شما ممکن است فقط نام یک هنرمند را بخواهید، در حالیکه در کوئری بعدی شاید آهنگ‌ها و تاریخ انتشار آهنگ‌های هنرمند را نیز بخواهید. در اینجاست که cache کردن در پیچیده‌ترین حد خود قرار دارد؛ زیرا به cache‌ کردن در سطح فیلد نیز خواهد داشت که به دست آوردن آن در GraphQL کار ساده‌ای نیست؛ زیرا فقط از یک اندپوینت استفاده نمی‌کند.

با توجه به این مسئله، جامعه GraphQL این مشکل را درک می‌کند و از آن زمان در تلاش بوده است که cache کردن را برای کاربران GraphQL ساده‌تر کند. کتابخانه‌هایی مانند Prisma و Dataloader (که بر روی GraphQL ساخته شده‌اند) توسعه داده شده‌اند که در سناریوهای مشابهی به ما کمک کنند. گرچه، این کتابخانه‌ها همچنان مواردی مانند cache کردن مرورگر و موبایل را پشتیبانی نمی‌کند.

کارایی Query

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

برای مثال، یک کاربر یک کوئری را تعریف می‌کند که برای لیستی از تمام کاربران که بر روی تمام آهنگ‌های یک هنرمند کامنت گذاشته‌اند، درخواست می‌کند. این کار نیازمند یک کوئری به مانند این مورد خواهد بود:

artist(id: '1') {

  id

  name

  tracks {

    id

    title

    comments {

      text

      date

      user {

        id

        name

      }

    }

  }

}

این کوئری ظرفیت گرفتن ده‌ها هزار داده در پاسخ را دارد.

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

برای کوئری‌های پیچیده، طراحی یک اِی‌پی‌آی REST ممکن است راحت‌تر باشد؛ زیرا شما می‌توانید چندین اندپوینت برای نیازهای مشخصی داشته باشید، و برای هر اندپوینت، شما می‌توانید کوئری‌های مشخصی تعریف کنید تا داده‌ها را به روش موثری دریافت کنید.  همچنین این کوئری‌ها با توجه به این که چندین فراخوانی شبکه می‌توانند زمان زیادی ببرند، ممکن است کمی بحث برانگیز باشند؛ اما اگر شما مراقب نباشید، چند کوئری بزرگ می‌توانند سرور شما را به زانوهایش در بیاورند.

عدم تطابق داده‌ها

همانطور که پیش‌تر و در هنگام ساخت با استفاده از GraphQL بر روی backend مثال زدیم، اغلب دیتابیس شما و GraphQL API، طرح‌های شبیه به هم ولی متفاوتی خواهند داشت، که این یعنی داشتن ساختار اسناد متفاوت. در نتیجه، یک آهنگ (track) از دیتابیس، یک ویژگی به نام آیدی (trackId) خواهد داشت، در حالیکه همان آهنگ مشابه که از طریق API شما دریافت شده است، ویژگی track را بر روی کلاینت خواهد داشت. این باعث بروز عدم تطابق داده در سمت کاربر / سمت سرور می‌شود.

دریافت نام هنرمند یک آهنگ خاص در سمت کلاینت را در نظر بگیرید. این کار به این صورت خواهد بود:

const getArtistNameInClient = track => {

  return artist.user.name

}

گرچه، انجام همین کار در سمت سرور به یک کد کاملا مشابه به این صورت ختم خواهد شد:

const getArtistNameInServer = track => {

  const trackArtist = Users.findOne(track.userId)

  return trackArtist.name

}

در نهایت، این یعنی شما روش عالی GraphQL برای کوئری کردن داده‌ها بر روی سرور را از دست خواهید داد. خوشبختانه، این مشکل بدون راه حل نیست. به نظر می‌رسد که شما می‌توانید به راحتی کوئری‌های GraphQL سرور به سرور را انجام دهید. چگونه؟ شما می‌توانید این کار را با منتقل کردن طرح قابل اجرای GraphQL خود به تابع GraphQL، در کنار کوئری GraphQL خود انجام دهید:

const result = await graphql(executableSchema, query, {}, context, variables);

طبق گفته Sesha Greif، نباید GraphQL را فقط به عنوان یک پروتکل کلاینت - سرور نبینید. GraphQL می‌تواند برای کوئری کردن داده‌ها در هر موقعیتی استفاده شود؛ شامل کلاینت به کلاینت با استفاده از Apollo Link State یا حتی در طی یک روند ساخت استاتیک با استفاده هاز Gatsby.

شباهت‌های طرح

وقتی که در حال ساخت با استفاده از GraphQL بر روی backend هستید، به نظر می‌رسد که نمی‌توانید از تکرار کد جلوگیری کنید؛ به خصوص وقتی که به طرح‌ها می‌رسیم. در ابتدا، شما به یک طرح برای دیتابیس خود نیاز دارید، و سپس یک طرح دیگر برای اندپوینت GraphQL. این شامل یک کد مشابه، اما نه خیلی یکسان می‌شود؛ به خصوص وقتی که به طرح‌ها می‌رسیم.

این که باید به طور منظم کد بسیار مشابهی برای  طرح‌های خود بنویسید، به اندازه کافی سخت است؛ اما این که باید به طور مداوم آن‌ها را همگام نگه دارید، حتی ناامید کننده‌تر است.

ظاهرا توسعه دهندگان دیگر متوجه این سختی شده‌اند و تا به اینجا، تلاش‌هایی در جامعه GraphQL صورت گرفته‌اند تا برطرف شود. در اینجا، دو راه حل معروفی که ما یافتیم را مشاهده می‌نمایید:

  1. PostGraphile یک طرح GraphQL برای دیتابیس PostgreSQL شما می‌سازد.
  2. Prisma هم به شما کمک می‌کند تا typeهایی را برای کوئری‌ها و جهش‌های خود بسازید.

نتیجه گیری

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

GraphQL یک ابزار قدرتمند است، و دلایل زیادی برای انتخاب آن در پروژه‌های خود وجود دارند. ما به یاد داشته باشید که مهم‌ترین و گاهی بهترین انتخاب، این است که بهترین ابزار را برای پروژه خود انتخاب کنید. نکات مثبت و منفی‌ای که من در اینجا نشان دادن، ممکن است همیشه صدق نکنند، اما ارزش این که هنگام کار با GraphQL آن‌ها را در نظر بگیرید را دارند.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
5 از 1 رای

/@er79ka

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

عرفان کاکایی

مقالات برگزیده

مقالات برگزیده را از این قسمت میتوانید ببینید

مشاهده همه مقالات