انواع API ها - REST، GraphQL، gRPC: کدومش رو واقعاً باید انتخاب کنی؟
یه روز یکی از بچههای تیم اومد گفت «بیا کل بکاند رو GraphQL کنیم، REST قدیمیه». دو هفته بعد، همون آدم داشت با N+1 query کلنجار میرفت و آرزو میکرد کاش همون REST قدیمی رو نگه داشته بودیم. این داستان رو احتمالاً قبلاً جایی شنیدی، چون تقریباً هر تیمی یه نسخه از آن را تجربه کرده.
واقعیت این است که هیچکدام از این سهتا «بهتر» نیستند؛ هرکدام برای یک نوع درد طراحی شدهاند.
REST: همون که همه میشناسن
REST رو همه بهخاطر سادگیاش دوست دارن. منبعمحور است: هر URL یک resource را نشان میدهد و متدهای HTTP (GET، POST، PUT، DELETE) کاری که میخواهی روی آن انجام دهی را مشخص میکنند. کتابخانهها همهجا هستند، کش کردن با HTTP caching معمولی کار میکند، و هر کسی که یک بار با API کار کرده، REST را بدون توضیح زیاد میفهمد.
مشکلش از
همانجایی شروع میشود که اپلیکیشنها پیچیدهتر میشوند. فرض کن صفحهی پروفایل کاربر را داری که هم اطلاعات کاربر، هم پستهای اخیرش، هم تعداد فالوورها را نیاز دارد. در REST کلاسیک، این میشود سه (یا بیشتر) درخواست جدا، یا یک endpoint سفارشی که فقط برای همین صفحه ساختهای و جای دیگری استفاده نمیشود. این پدیده اسم دارد: over-fetching وقتی دادهی اضافه میگیری، و under-fetching وقتی کم میگیری و باید درخواست دوم بزنی.
GET /users/42
GET /users/42/posts?limit=5
GET /users/42/followers/count
برای یک اپ ساده مشکلی نیست. برای یک اپ موبایل با شبکهی ضعیف که هر round-trip اضافه چند صد میلیثانیه هزینه دارد، همین چیزی است که کاربران را اذیت میکند.
GraphQL: یک endpoint، دقیقاً همون چیزی که خواستی
GraphQL همین مشکل را حل میکند با این ایده که فقط یک endpoint داشته باشی، و کلاینت دقیقاً مشخص کند چه فیلدهایی را میخواهد:
query {
user(id: 42) {
name
posts(limit: 5) { title }
followersCount
}
یک درخواست، دقیقاً دادهای که لازم داری، نه کمتر نه بیشتر. تیمهای فرانتاند معمولاً عاشق این میشوند چون دیگر برای هر تغییر کوچک در UI نباید از بکاند بخواهند یک endpoint جدید بسازد.
اما این آزادی قیمتی دارد. همان مثال N+1 که اول گفتم: اگر برای هر post بخواهی اطلاعات نویسندهاش را هم بگیری، resolver پیشفرض میتواند بهازای هر پست یک کوئری جدا به دیتابیس بزند. ده پست یعنی ده کوئری اضافه. راهحلش وجود دارد (مثل DataLoader برای batch کردن)، ولی این یعنی یک لایهی پیچیدگی که در REST اصلاً مجبور نبودی به آن فکر کنی. کش کردن هم سرراست نیست؛ چون همهچیز از یک endpoint با متد POST میآید، کشهای HTTP معمولی که برای REST جا افتادهاند، اینجا کار نمیکنند و باید کش را خودت در سطح اپلیکیشن مدیریت کنی.
gRPC: وقتی سرعت بین سرویسها مهمتر از خوانایی است
gRPC داستان متفاوتی دارد. این یکی اصلاً برای مرورگر طراحی نشده (هرچند gRPC-Web هم هست)؛ هدف اصلیاش ارتباط بین سرویسها در یک سیستم میکروسرویس است. بهجای JSON متنی، از Protocol Buffers استفاده میکند که باینری و فشرده است، و روی HTTP/2 کار میکند که چندین استریم را روی یک کانکشن همزمان میبرد.
نتیجهاش سرعت است؛ هم در حجم داده، هم در latency. قیمتش این است که دیگر نمیتوانی با مرورگر یا curl ساده پاسخ را بخوانی؛ باید فایل .proto را داشته باشی و کد را generate کنی. برای ارتباط بین دو سرویس داخلی که هیچکدام انسان نیست که مستقیم پاسخ را بخواند، این مشکلی نیست. برای یک API عمومی که توسعهدهندههای بیرونی باید با آن کار کنند، احتمالاً تجربهی بدتری میسازد.
پس کدوم رو انتخاب کنیم؟
اگر یک API عمومی یا نسبتاً ساده میسازی که کلاینتهای مختلف (وب، موبایل، شاید حتی توسعهدهندههای بیرونی) با آن کار میکنند و نیازهای دادهایشان شبیه هم است، REST هنوز انتخاب امنتری است. کسی که برای اولینبار با APIات روبهرو میشود، سریعتر میفهمدش.
اگر چند کلاینت با نیازهای دادهای خیلی متفاوت داری (مثلاً اپ موبایل که میخواهد کمترین داده ممکن را بگیرد، و داشبورد ادمین که میخواهد همهچیز را یکجا ببیند)، GraphQL این مشکل را با هزینهی پیچیدگی بیشتر در بکاند حل میکند. فقط حواست به N+1 باشد.
و اگر صحبت از ارتباط داخلی بین سرویسهاست، جایی که سرعت مهمتر از قابلخواندنبودن است، gRPC معمولاً برنده است.
نکتهی آخر: هیچ قانونی نمیگوید باید فقط یکی را انتخاب کنی. خیلی از سیستمهای واقعی، REST را برای API عمومی نگه میدارند و gRPC را برای ارتباط داخلی بین سرویسها به کار میبرند. انتخاب درست همیشه یکی نیست؛ گاهی سهتاست، هر کدام جایی که واقعاً مزیت دارد.