ورود به اعماق کوئری‌های GraphQL

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 19 خرداد 1398
دسته بندی ها : آموزشی

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

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

جدول محتوا:

  • کوئری‌ها
  • فیلدها
  • آرگومان‌ها
  • نام‌های مستعار
  • سینتکس عملیات
  • متغیرها
  • تکه‌ها (fragmentها)
  • دستور العمل‌ها
  • نتیجه گیری

کوئری‌ها

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

فیلدها

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

{
    players {
        name
    }
}

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

۱. root field (بازیکان) - که آبجکت شامل محموله (payload) می‌باشد.

۲. خود payload (محموله) که فیلدهای درخواست شده توسط کاربر می‌باشد.

این یک بخش ضروری از GraphQL است؛ زیرا سرور می‌داند که کلاینت دقیقا برای کدام فیلدها درخواست می‌کند و همیشه با داده‌های دقیق پاسخ می‌دهد. در موقعیت کوئری بالا، ما می‌توانیم این پاسخ را داشته باشیم:

{
    "players": [
        {"name": "Pogba"},
        {"name": "Lukaku"},
        {"name": "Rashford"},
        {"name": "Marshal"}
    ]
}

فیلد name‌ یک نوع رشته را بر می‌گرداند. در این مورد، نام بازیکنان تیم منچستر یونایتد. گرچه، ما به رشته‌ها محدود نیستیم. ما می‌توانیم فیلدهایی شامل تمام انواع داده داشته باشیم. مانند فیلد ریشه players که یک آرایه از آیتم‌ها را بر می‌گرداند.

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

آرگومان‌ها

کوئری‌های GraphQL ما را قادر می‌سازند تا آرگومان‌هایی را به فیلدهای کوئری و آبجکت‌های کوئری تو در تو منتقل کنیم. به علاوه، شما می‌توانید آرگومان‌هایی را به هر فیلد و آبجکت تو در تو در کوئری خود منتقل کنید، تا درخواست خود را عمیق‌تر کنید و چندین دریافت داشته باشید. آرگومان‌ها همین هدف مشابه را به مانند query parameters یا URL segments در REST تحقق می‌بخشند. ما می‌توانیم به سادگی آن‌ها را در فیلدهای کوئری خود منتقل کنیم، تا بیشتر مشخص کنیم که سرور چگونه باید به درخواست ما پاسخ دهد.

اگر به ایستگاه قبلی خود برای دریافت جزئیات یک بازیکن خاص مانند سایز پیراهن (shirt size) یا سایز کفش (shoe size) باز گردیم، در ابتدا باید آن بازیکن مورد نظر را با منتقل کردن یک آرگومان id مشخص کنیم، تا اون را از میان لیست بازیکنان در آوریم و سپس فیلدهایی که می‌خواهیم در محموله کوئری داشته باشیم را مشخص کنیم:

{
    player(id : "Pogba") {
        name,
        kit {
            shirtSize,
            bootSize
        }
    }
}

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

{
    "player": {
        "name": "Pogba",
        "kit": [
            {
            "shirtSize": "large",
            "shoeSize": "medium"             }
         ]

    }
}

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

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

نام‌های مستعار

اگر نگاه نزدیک‌تری به آخرین مثالمان داشته باشید، متوجه خواهید شد که فیلدهای آبجکت نهایی:

// result....
"player": {
        "name": "Pogba",
        "kit": [
            {
            "shirtSize": "large",
            "shoeSize": "medium"             }
         ]

    }

با فیلدهای کوئری تطابق دارند:

//query.... has matching fields with the result
player(id : "Pogba") {
        name,
        kit {
            shirtSize,
            bootSize
        }
    }

اما بدون آرگومان:

(id : "Pogba")

ما نمی‌توانیم به طور مستقیم برای فیلد player مشابه با آرگومان‌های متفاوت کوئری کنیم. به عبارتی ما نمی‌توانیم چنین کاری را انجام دهیم:

{
    player(id : "Pogba") {
        name,
        kit {
            shirtSize,
            bootSize
        }
    } 
    player(id : "Lukaku") {
        name,
        kit {
            shirtSize,
            bootSize
        }
    }

}

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

{
  player1: player(id: "Pogba") {
    name,
    kit {
        shirtSize,
        shoeSize
    }
  }
  player2: player(id: "Lukaku") {
    name,
    kit {
        shirtSize,
        shoeSize
    }
  }
}

در اینجا، دو فیلد player با هم در تداخل می‌افتند، اما از آنجایی که ما می‌توانیم نام‌های مستعار player1 و player2 را به آن‌ها بدهیم، می‌توانیم به این صورت هر دو نتیجه را در یک درخواست به دست بیاوریم:

{
  "data": {
    "player1": {
      "name": "Luke Skywalker",
        "kit": [
            {
            "shirtSize": "large",
            "shoeSize": "medium" 
            }
         ]
    },
    "player2": {
      "name": "Lukaku",
        "kit": [
            {
            "shirtSize": "extralarge",
            "shoeSize": "large" 
            } 
        ]
    }
  }
}

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

سینتکس عملیات

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

سینتکس عملیات اساسا از دو چیز تشکیل می‌شود:

  • operation type که می‌تواند یک کوئری، جهش یا اشتراک باشد. این مورد برای توصیف نوع عملیاتی که می‌خواهید انجام دهید استفاده می‌شود.
  • operation name که می‌تواند هر چیزی باشد که به شما در ارتباط با عملیاتی که می‌خواهید اجرا کنید، کمک خواهد کرد.

حال ما می‌توانیم مثال پیشین خود را بازنویسی کنیم، و نام و نوع عملیات را به این صورت اضافه کنیم:

query PlayerDetails{
    player(id : "Pogba") {
        name,
        kit {
            shirtSize,
            bootSize
        }
    }
}

در اینجا query نوع عملیات، و PlaterDetails نام عملیات است.

متغیرها

تا به اینجا، ما تمام آرگومان‌ها را مستقیما به رشته کوئری خود منتقل می‌کردیم. در اکثر موارد، آرگومان‌هایی که ما منتقل می‌کنیم دینامیک هستند. برای مثال فرض کنید کلاینت بازیکن می‌خواهد جزئیات او از یک فرم ورودی متن یا منوی کشویی بیایند. به این صورت، آرگومانی که ما به رشته کوئری منتقل می‌کنیم باید دینامیک باشد و برای انجام این کار باید از متغیرها استفاده کنیم. شاید بپرسید که متغیرها چه هستند؟ متغیرها اساسا برای فاکتورگیری مقادیر دینامیک از کوئری‌ها و منتقل کردن آن‌ها به صورت مجزا استفاده می‌شوند.

با در نظر گرفتن مثال آخر خود، اگر ما بخواهیم بازیکن را به گونه‌ای دینامیک کنیم که جزئیات بازیکن انتخاب شده برگردانده شوند، باید مقدار id او را در یک متغیر ذخیره کنیم و به این صورت آن را به نام عملیات و آرگومان کوئری منتقل کنیم:

query PlayerDetails ($id: String){
    player (id : $id) {
        name,
        kit {
            shirtSize,
            bootSize
        }
    }
}

در اینجا، $title: String تعریف متغیر بوده و title هم نام متغیر است. این نام پیشوند $ را به همراه type دارد، که در این مورد String است. این یعنی ما می‌توانیم از داخل کردن رشته‌ها به صورت دستی برای ساخت کوئری‌های دینامیک خودداری کنیم، و این عالی است.

تکه‌ها (fragmentها)

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

    name,
        kit {
            shirtSize,
            bootSize
        }

برای این که کوئری ما موثرتر باشد، می‌توانیم به این صورت این تکه منطق به اشتراک گذاشته شده را در قالب تکه‌های با قابلیت استفاده مجدد بر روی فیلد player استخراج کنیم:

{
  player1: player(id: "Pogba") {     ...playerKit
  }
  player2: player(id: "Lukaku") {
    ...playerKit
  }
}

fragment playerKit on player {
    name,
    kit {
        shirtSize,
        shoeSize
    }
}

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

دستور العمل‌ها (directiveها)

دستور العمل‌های GraphQL راهی برای ما فراهم می‌کنند که به سرور بگوییم یک فیلد خاص را در هنگام پاسخ دادن به کوئری ما، include یا skip کند. اساسا دو دستور العمل داخلی در GraphQL وجود دارند که به ما در رسیدن به این هدف کمک می‌کنند:

۱. @skip برای نادیده گرفتن یک فیلد خاص، وقتی که مقدار منتقل شده به آن، برابر با true است.

۲. @include برای شامل کردن یک فیلد خاص، وقتی که مقدار منتقل شده به آن، برابر با true است.

بیایید یک دستور العمل Boolean را اضافه کنیم و آن را با استفاده از دستور العمل @skip، بر روی سرور نادیده بگیریم:

query PlayerDetails ($playerShirtDirective: Boolean!){
    player(id: "Pogba") {
        name,
        kit {
            shirtSize @skip(if: $playerShirtDirective)
            bootSize
        }
    }
}

کار بعدی که انجام می‌دهیم، ساخت دستور العمل playerShirtDirective در متغیرهای کوئری ما، و برابر قرار دادن آن با true است:

// متغیرهای کوئری
{
  "itemIdDirective": true
}

حال این کد، محموله مورد نظر را بدون shirtSize بر خواهد گرداند:

"player": {
        "name": "Pogba",
        "kit": [
            {
            "shoeSize": "medium"             }
         ]

    }

ما می‌توانیم این موقیت را با استفاده از دستور العمل @include برعکس کنیم. این دستور العمل، برعکس دستور العمل @skip عمل می‌کند. شما می‌توانید از آن برای برعکس کردن این عمل نادیده گرفتن بر روی سرور استفاده کنیم.

نتیجه گیری

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

منبع

مقالات پیشنهادی

۵ راه برای آسان کردن فرایند ورود

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

بهترین رویکردها برای طراحی تجربه‌کاربری شخصی‌سازی شده

شخصی‌سازی به این معناست که نیازها و موضوعات جذب کننده کاربران را درک کنید و آن‌ها را بدون اینکه کاربران خواسته باشند در اختیارشان بگذارید. این کار باع...

5 نکته تجربه کاربری برای طراحان گرافیک

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

هر آنچه که باید درباره طراحی UX (تجربه کاربری) و GDPR بدانید

اینترنت جایی است که ما مقدار زیادی از زمان خود را در آن صرف می کنیم. چه در حال کار کردن، مطالعه یا ارتباط با دیگران. و اگر در زندگی واقعی قوانین و مقر...