בעיה: ה-API הסטנדרטי של REST ב-1C-Bitrix על קטלוג של 10,000 מוצרים מייצר 5,000+ בקשות כדי להביא נתונים מקוננים (מחירים, מלאי). אפליקציית מובייל טוענת 400 KB של שדות מיותרים. GraphQL פותר זאת עם בקשה אחת—הלקוח מתאר את השדות הדרושים ומקבל בדיוק את מה שביקש. אנו מיישמים GraphQL על Bitrix כבר כמה שנים; חיסכון בפועל בתעבורה מגיע ל-60%, וזמן התגובה יורד מ-3 שניות ל-200 אלפיות השנייה. עם נפח של 500,000 בקשות בחודש, החיסכון ב-CDN מסתכם ב-$20–50.
למה GraphQL עדיף על REST לקטלוגים מורכבים
נקודת הקצה של REST /api/products/123 מחזירה קבוצה קבועה של שדות. אפליקציית מובייל צריכה שם ומחיר—היא מקבלת 40 שדות. לקוח אחר צריך מלאי לפי מחסן—הוא מבצע בקשה שנייה. GraphQL מאפשר לכל לקוח לתאר את הצרכים שלו:
# Мобильное приложение query { product(id: 123) { name price { value currency } images { url } } } # Складской модуль query { product(id: 123) { name sku { id stock { warehouse quantity } } } } נקודת קצה אחת, בקשה אחת—נתונים שונים ללקוחות שונים. GraphQL עדיף על REST בתרחישים עם מספר צרכנים: פרונטאנד, אפליקציית מובייל, שירותים חיצוניים—כל אחד מקבל רק את הנתונים שלו.
הבעיה העיקרית של GraphQL היא שאילתות N+1. הלקוח מבקש רשימה של 20 מוצרים, כל אחד צריך מחירים—20 שאילתות נפרדות ל-# Мобильное приложение query { product(id: 123) { name price { value currency } images { url } } } # Складской модуль query { product(id: 123) { name sku { id stock { warehouse quantity } } } } . פתרון: DataLoader (תבנית אצווה). DataLoader צובר שאילתות בתוך ביצוע GraphQL אחד ומבצע שאילתת אצווה אחת:
class PriceDataLoader { private array $buffer = []; public function load(int $productId): Promise { $this->buffer[] = $productId; return new Promise(fn($resolve) => $resolve($productId)); } public function dispatch(): void { // Один запрос для всех накопленных ID $prices = \Bitrix\Catalog\PriceTable::getList([ 'filter' => ['PRODUCT_ID' => $this->buffer], ])->fetchAll(); // Распределяем результаты } } במקום 20 שאילתות—1. עבור נתונים מקוננים (מוצרים → SKU → מלאי) החיסכון הוא כפלי. על קטלוגים גדולים (50,000+ פריטים) זה מקטין את זמן התגובה ל-200 אלפיות השנייה.
איך ליישם GraphQL ב-Bitrix: מסכמה לנקודת קצה
Bitrix אינו תומך ב-GraphQL כברירת מחדל. היישום בנוי על גבי PHP הסטנדרטי של Bitrix באמצעות ספריית GraphQL-PHP—התקן דה-פקטו ל-PHP.
נקודת הכניסה היא בקר יחיד ב-b_catalog_price שמקבל בקשות POST עם גוף JSON (class PriceDataLoader { private array $buffer = []; public function load(int $productId): Promise { $this->buffer[] = $productId; return new Promise(fn($resolve) => $resolve($productId)); } public function dispatch(): void { // Один запрос для всех накопленных ID $prices = \Bitrix\Catalog\PriceTable::getList([ 'filter' => ['PRODUCT_ID' => $this->buffer], ])->fetchAll(); // Распределяем результаты } } ).
// /local/php_interface/api/graphql.php use GraphQL\GraphQL; use GraphQL\Type\Schema; $rawInput = file_get_contents('php://input'); $input = json_decode($rawInput, true); $schema = new Schema([ 'query' => QueryType::build(), 'mutation' => MutationType::build(), ]); $result = GraphQL::executeQuery($schema, $input['query'], null, null, $input['variables'] ?? null); header('Content-Type: application/json'); echo json_encode($result->toArray()); כל סוג GraphQL מתאים לישות Bitrix. דוגמה לקטלוג:
// ProductType ObjectType(['name' => 'Product', 'fields' => fn() => [ 'id' => ['type' => Type::int()], 'name' => ['type' => Type::string()], 'code' => ['type' => Type::string()], 'price' => [ 'type' => PriceType::get(), 'resolve' => fn($product) => PriceResolver::resolve($product['ID']), ], 'sku' => [ 'type' => Type::listOf(SkuType::get()), 'resolve' => fn($product) => SkuResolver::resolve($product['ID']), ], 'sections' => [ 'type' => Type::listOf(SectionType::get()), 'resolve' => fn($product) => SectionResolver::resolve($product['IBLOCK_SECTION_ID']), ], ]]); Resolvers הם פונקציות שמביאות נתונים לכל שדה. ה-resolver עבור /api/graphql ניגש ל-{ "query": "...", "variables": {...} }, עבור // /local/php_interface/api/graphql.php use GraphQL\GraphQL; use GraphQL\Type\Schema; $rawInput = file_get_contents('php://input'); $input = json_decode($rawInput, true); $schema = new Schema([ 'query' => QueryType::build(), 'mutation' => MutationType::build(), ]); $result = GraphQL::executeQuery($schema, $input['query'], null, null, $input['variables'] ?? null); header('Content-Type: application/json'); echo json_encode($result->toArray()); —בלוק המידע של SKU המשני, עבור // ProductType ObjectType(['name' => 'Product', 'fields' => fn() => [ 'id' => ['type' => Type::int()], 'name' => ['type' => Type::string()], 'code' => ['type' => Type::string()], 'price' => [ 'type' => PriceType::get(), 'resolve' => fn($product) => PriceResolver::resolve($product['ID']), ], 'sku' => [ 'type' => Type::listOf(SkuType::get()), 'resolve' => fn($product) => SkuResolver::resolve($product['ID']), ], 'sections' => [ 'type' => Type::listOf(SectionType::get()), 'resolve' => fn($product) => SectionResolver::resolve($product['IBLOCK_SECTION_ID']), ], ]]); —price. הניסיון מראה שסכמת טיפוסים מעוצבת היטב משתלמת בשלב הרחבת התכונות.
איך ליישם מוטציות והרשאות
מוטציות ב-GraphQL הן האנלוגיה ל-POST/PUT/DELETE ב-REST:
mutation { createOrder(input: { productId: 123, quantity: 2, deliveryAddress: "Москва, ул. Ленина, 1" }) { orderId status totalAmount } } ה-resolver של המוטציה קורא ל-b_catalog_price עם הפרמטרים הנדרשים—ה-API הסטנדרטי של D7 במודול sku. אנו ממליצים לאמת נתוני קלט דרך resolvers ולהחזיר שגיאות ברורות.
ההרשאות מיושמות בשתי רמות. רמת בקשה: middleware בודק JWT או סשן Bitrix לפני ביצוע שאילתת GraphQL. רמת שדה: שדה ספציפי נגיש רק למשתמשים מורשים. לדוגמה, השדה sections (מחיר עלות) גלוי רק למשתמשים עם תפקיד 'מנהל'. מיושם ב-resolver ללא בלוקי קוד נוספים—בדיקת הרשאה פשוטה.
איך לשמור במטמון GraphQL ולארגן מנויים
קשה יותר לשמור GraphQL במטמון מאשר REST: שאילתות הן ייחודיות לפי קבוצת שדות ומשתנים. גישות:
- מטמון ברמת resolver—הנפוץ ביותר: ה-resolver שומר את התוצאה של אצוות DataLoader ספציפית ב-Redis/Memcache. ה-TTL תלוי בתדירות עדכון הנתונים.
- Persisted Queries: הלקוח שולח hash של שאילתה רשומה מראש במקום הטקסט המלא שלה. זה מאפשר שמירה במטמון ברמת HTTP (CDN שומר בקשות GET עם ה-hash).
-
מטמון מתויג של Bitrix: רישום תגים בעת קריאת נתונים (
b_iblock_section), ביטול בעת שינוי.
לפרויקטים בעומס גבוה אנו משתמשים בשילוב של כל שלוש השיטות—זה נותן שיעור פגיעה במטמון של 90%.
GraphQL תומך במנויים—עדכונים בזמן אמת דרך WebSocket. כאשר הזמנה משתנה, כל המנויים מקבלים הודעה. עבור Bitrix זה מיושם דרך שרת WebSocket נפרד (Ratchet/Swoole) + Redis pub/sub. כאשר ישות Bitrix משתנה (דרך handler אירועים), אנו מפרסמים לערוץ Redis, ושרת ה-WebSocket מעביר לכל המנויים.
מה כלול בפיתוח שלנו ושלבי העבודה
אנו מספקים חבילה מלאה: עיצוב סכמה, יישום טיפוסים ו-resolvers, הגדרת DataLoader ומטמון, תיעוד בפורמט GraphQL SDL + Markdown, הדרכת צוות על GraphiQL, ותמיכה אחריות ל-30 יום לאחר הפריסה. אנו מעריכים את הפרויקט שלך ביום אחד. עלות הפרויקט נעה בין $2k–5k בהתאם למורכבות.
| שלב | תוכן | משך |
|---|---|---|
| עיצוב סכמה | טיפוסים, שאילתות, מוטציות, קשרים | שבוע אחד |
| תשתית בסיסית | נקודת קצה GraphQL, הרשאות | 3–5 ימים |
| יישום טיפוסים ו-resolvers | קטלוג, הזמנות, משתמשים | 2–4 שבועות |
| DataLoader (N+1) | אצווה לנתונים מקוננים | שבוע אחד |
| מטמון | מטמון Redis DataLoader + תגים | שבוע אחד |
| הרשאות שדה | בקרת גישה | 3–5 ימים |
| בדיקות | בדיקות יחידה ל-resolvers, בדיקות אינטגרציה | שבוע אחד |
השוואת גישות
| קריטריון | REST | GraphQL |
|---|---|---|
| עודף/חוסר נתונים | לעיתים קרובות | אין |
| מספר בקשות לנתונים מקוננים | N+1 | 1 |
| גמישות ללקוחות שונים | נמוכה | גבוהה |
| מורכבות שמירה במטמון | בינונית | גבוהה |
GraphQL על Bitrix הוא פתרון בוגר לפרויקטים עם מספר לקוחות ונתונים מקוננים מורכבים. עבור אתר פשוט עם פרונטאנד אחד, REST מספיק. קבלו ייעוץ—המהנדסים שלנו יעריכו את הפרויקט שלכם ביום אחד ויעזרו לכם לבחור את האפשרות הטובה ביותר. צרו קשר כדי לדון בפרויקט שלכם.







