اتصالاتنا الداخلية كانت ثرثرة لا تنتهي: كيف أنقذنا gRPC من جحيم الـ JSON والتأخير في خدماتنا المصغرة؟

أذكر تلك الليلة جيداً، كانت الساعة قد تجاوزت الثانية صباحاً. أجلس في مكتبي، وفنجان الميرمية الساخن بجانبي، وأمامي شاشات المراقبة تصرخ باللون الأحمر. النظام بطيء، طلبات المستخدمين تستغرق وقتاً طويلاً، والشكاوى بدأت تتوالى. المشكلة لم تكن في خدمة واحدة بعينها، بل كانت شيئاً أعمق، شيئاً أشبه بالهمس الخبيث الذي ينتشر في كل مكان.

بعد ساعات من التنقيب والتحليل مع الفريق، وجدنا الجاني. لم يكن خطأً برمجياً في منطق العمل، بل كان “ثرثرة” لا تنتهي بين خدماتنا المصغرة (Microservices). كانت الخدمات تتحدث مع بعضها باستخدام واجهات REST API التقليدية، وترسل وتستقبل بيانات بصيغة JSON. كل طلب، كل استجابة، كانت عبارة عن نص طويل، مليء بالحقول المكررة، مما يستهلك عرض النطاق الترددي (Bandwidth) ويثقل كاهل المعالجات (CPUs) في عملية التحليل والتفسير. كانت خدماتنا كأنها في سوق شعبي، الكل يصرخ والضوضاء تملأ المكان، ولا أحد يسمع الآخر بوضوح. وقتها، قلت للفريق: “يا جماعة، المشكلة مش في شو بنحكي، المشكلة في كيف بنحكي. لازم نلاقي لغة تانية، لغة أسرع وأوضح”. ومن هنا بدأت رحلتنا مع gRPC.

لماذا كانت اتصالاتنا “ثرثرة”؟ مشكلة الـ REST و الـ JSON

لكي نفهم حجم المشكلة التي كنا نواجهها، دعونا نفصّل أسباب هذه “الثرثرة” التي كانت تسببها بنية REST/JSON في بيئة الخدمات المصغة عالية الأداء.

الحجم الكبير (The Bloat)

صيغة JSON هي صيغة نصية مقروءة للبشر، وهذا رائع للتطوير والdebugging. لكن في بيئة الإنتاج، حيث تُتبادل ملايين الطلبات في الدقيقة، تصبح هذه الميزة عبئاً. كل طلب يحمل معه أسماء الحقول كنصوص. تخيل أن لديك خدمة ترجع بيانات مستخدم:


{
  "user_id": 12345,
  "user_name": "AbuOmar",
  "user_email": "abu.omar.dev@example.com",
  "last_login_timestamp": "2023-10-27T10:00:00Z"
}

كلمات مثل “user_id” و “user_name” تتكرر في كل استجابة. اضرب هذا في مليون طلب، وستجد أنك تستهلك ميغابايتات من البيانات فقط في تكرار أسماء الحقول! هذا ما أسميه “الدهون الزائدة” في البيانات.

التحليل والتحويل (Parsing & Serialization)

المشكلة الثانية هي أن تحليل (Parsing) نص JSON وتحويله إلى كائن في الذاكرة (Object) هو عملية مكلفة نسبياً للمعالج. وكذلك العكس، تحويل الكائن إلى نص JSON (Serialization). عندما تكون لديك خدمة تتلقى آلاف الطلبات في الثانية، فإن هذه العملية الصغيرة تتراكم لتصبح عنق زجاجة حقيقي يستهلك دورات المعالج الثمينة التي كان من الممكن استخدامها في تنفيذ منطق العمل الفعلي. “مش بس الحجم، كمان المعالج بتعب وهو يفكفك بالـ JSON”.

غياب العقود الصارمة (Lack of Strict Contracts)

مع REST/JSON، العقد (Contract) بين الخدمة المستهلكة (Client) والخدمة المُقدِّمة (Server) غالباً ما يكون غير صارم. يمكن لمطور أن يغير اسم حقل من userName إلى user_name في الخدمة، وكل الخدمات الأخرى التي تعتمد على الاسم القديم ستتعطل فجأة في بيئة الإنتاج. هذا يؤدي إلى أخطاء وقت التشغيل (Runtime errors) التي يصعب اكتشافها إلا بعد فوات الأوان. “اليوم بنبعت إشي، بكرا حدا بغيره والدنيا بتخرب، وبدك ساعتين لتعرف مين غير وليش”.

المنقذ gRPC: لقاءنا الأول وتجربتنا

في خضم بحثنا عن حل، ظهر اسم gRPC. هو إطار عمل مفتوح المصدر وعالي الأداء من تطوير جوجل، مخصص للاتصال بين العمليات عن بعد (Remote Procedure Call). لم يكن مجرد تحسين، بل كان نقلة نوعية في طريقة تفكيرنا في الاتصالات الداخلية.

القلب النابض: Protocol Buffers (Protobuf)

السر الأول في قوة gRPC يكمن في استخدامه لـ Protocol Buffers (أو Protobuf اختصاراً). Protobuf هي آلية من جوجل لـ “تسلسل” البيانات (Serializing) بشكل ثنائي (Binary). بدلاً من إرسال نصوص مقروءة، يتم تحويل البيانات إلى صيغة ثنائية مدمجة وفعالة للغاية.

لنعد إلى مثال المستخدم. في Protobuf، نعرّف “العقد” أولاً في ملف .proto:


syntax = "proto3";

package users;

// تعريف الخدمة والإجراء الذي تقدمه
service UserService {
  rpc GetUser(GetUserRequest) returns (User);
}

// رسالة الطلب
message GetUserRequest {
  int32 user_id = 1;
}

// رسالة الاستجابة (بيانات المستخدم)
message User {
  int32 id = 2;
  string username = 3;
  string email = 4;
}

لاحظ أننا لا نرسل أسماء الحقول (id, username) في كل مرة. نرسل فقط الأرقام التعريفية (1, 2, 3, 4)، والبيانات نفسها بصيغة ثنائية. هذا يقلل حجم البيانات بشكل هائل.

نصيحة من أبو عمر: ملف الـ .proto هو مصدر الحقيقة الأوحد (Single Source of Truth). حافظ عليه في مستودع كود مركزي، واعتبره العقد المقدس بين فرق العمل. أي تغيير يتم فيه يجب أن يمر بمراجعة دقيقة.

الأجمل من ذلك، أن Protobuf يقوم بتوليد كود الخادم والعميل (Server & Client Stubs) تلقائياً بلغات برمجة متعددة. هذا يضمن أن كلا الطرفين يتحدثان نفس اللغة تماماً، مما يقضي على أخطاء عدم تطابق الحقول.

السرعة الفائقة: HTTP/2

السر الثاني هو أن gRPC مبني على بروتوكول HTTP/2، بينما معظم واجهات REST التقليدية تستخدم HTTP/1.1. هذا يمنح gRPC قدرات خارقة:

  • Multiplexing: إمكانية إرسال عدة طلبات واستجابات في نفس الوقت عبر اتصال TCP واحد، مما يزيل مشكلة “head-of-line blocking” التي تعاني منها HTTP/1.1. “مش زي طابور الخبز، كل واحد بستنى دوره. هون الكل بفوت مع بعض بنفس الخط بدون ما يستنوا بعض”.
  • Streaming: يدعم gRPC تدفق البيانات في كلا الاتجاهين (bidirectional streaming)، مما يسمح بإنشاء سيناريوهات اتصال معقدة وفعالة، مثل الدردشة الحية أو إرسال بيانات القياس عن بعد (telemetry) بشكل مستمر.
  • Header Compression: يقوم بضغط ترويسات الطلبات (Headers) لتقليل حجم البيانات المرسلة بشكل أكبر.

كيف طبقنا gRPC عملياً؟ (مع مثال بسيط)

قد يبدو الأمر معقداً، لكنه في الواقع منظم جداً. لنرَ الخطوات العملية لإنشاء خدمة gRPC بسيطة بلغة Go.

الخطوة الأولى: تعريف الخدمة بملف .proto

نبدأ دائماً بملف التعريف، كما رأينا في المثال السابق (user.proto).

الخطوة الثانية: توليد الكود

باستخدام مترجم Protobuf (protoc)، نقوم بتوليد الكود اللازم للغة التي نستخدمها. على سبيل المثال، في Go، نستخدم الأمر التالي:


# أمر لتوليد كود Go من ملف .proto
protoc --go_out=. --go_opt=paths=source_relative 
    --go-grpc_out=. --go-grpc_opt=paths=source_relative 
    path/to/your/user.proto

سينتج عن هذا الأمر ملفات Go تحتوي على الواجهات (Interfaces) وهياكل البيانات (Structs) اللازمة.

الخطوة الثالثة: كتابة منطق الخادم (Server Logic)

الآن، كل ما علينا فعله هو تطبيق الواجهة التي تم توليدها. الكود سيجبرنا على تطبيق دالة GetUser.


// server.go
package main

import (
    "context"
    pb "path/to/your/generated/code" // استيراد الكود المولد
)

// تعريف هيكل الخادم
type server struct {
    pb.UnimplementedUserServiceServer
}

// تطبيق دالة GetUser
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    // هنا تكتب منطق العمل الحقيقي
    // مثلاً، البحث في قاعدة البيانات عن المستخدم بالـ ID المطلوب
    // req.GetUserId()

    // إرجاع بيانات مستخدم وهمية للتوضيح
    return &pb.User{Id: req.UserId, Username: "AbuOmar", Email: "abu.omar.dev@example.com"}, nil
}

// ... باقي كود تشغيل الخادم

الخطوة الرابعة: استدعاء الخدمة من العميل (Client Call)

الخدمة الأخرى (العميل) ستستخدم أيضاً الكود المولد للاتصال بالخادم بطريقة سهلة وآمنة من ناحية الأنواع (type-safe).


// client.go
package main

import (
    "context"
    "log"
    pb "path/to/your/generated/code"
    "google.golang.org/grpc"
)

func main() {
    // إنشاء اتصال مع الخادم
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("لم نتمكن من الاتصال: %v", err)
    }
    defer conn.Close()

    // إنشاء عميل للخدمة
    c := pb.NewUserServiceClient(conn)

    // استدعاء الدالة عن بعد وكأنها دالة محلية
    r, err := c.GetUser(context.Background(), &pb.GetUserRequest{UserId: 123})
    if err != nil {
        log.Fatalf("فشل استدعاء الدالة: %v", err)
    }

    log.Printf("المستخدم: %s", r.GetUsername())
}

لاحظ كيف أن استدعاء c.GetUser(...) يبدو وكأنه استدعاء لدالة محلية عادية. gRPC يتكفل بكل التعقيدات الشبكية في الخلفية.

نصائح من قلب الميدان

  • ابدأ صغيراً: لا تحاول تحويل نظامك بالكامل دفعة واحدة. اختر خدمتين بينهما اتصال كثيف وغير حرجتين، وقم بتحويل الاتصال بينهما إلى gRPC. قِس الأداء، تعلم من التجربة، ثم توسع. “ما تهد الدار القديمة قبل ما تبني غرفة بالجديدة”.
  • التعامل مع الأخطاء: gRPC لديه نظام موحد للأخطاء. تعلم كيفية استخدام أكواد الحالة (status codes) مثل NOT_FOUND أو INVALID_ARGUMENT لتوفير معلومات دقيقة عن سبب فشل الطلب.
  • متى لا تستخدم gRPC؟: رغم حبي له، gRPC ليس الحل لكل شيء. بالنسبة للواجهات العامة الموجهة للمتصفحات (Public-facing APIs for browsers)، لا يزال REST/JSON هو الخيار الأسهل والأكثر توافقية. gRPC يتألق في الاتصالات الداخلية بين الخدمات (service-to-service communication).
  • استخدم Deadlines: من أهم الميزات في gRPC هي القدرة على تحديد مهلة زمنية (Deadline) للطلب. هذا يمنع الطلبات من البقاء عالقة إلى الأبد ويحمي نظامك من الأعطال المتسلسلة (cascading failures).

الخلاصة: من الثرثرة إلى الحوار الهادف 🚀

الانتقال إلى gRPC كان بمثابة نقل خدماتنا من سوق صاخب إلى مكتبة هادئة ومنظمة. الاتصالات أصبحت أسرع بعشرات المرات، استهلاك الموارد قل بشكل ملحوظ، والأهم من ذلك، أصبح لدينا “عقود” برمجية صارمة قللت من الأخطاء غير المتوقعة بشكل كبير.

الرحلة لم تكن خالية من التحديات، فتعلم أي تقنية جديدة يتطلب وقتاً وجهداً. لكن العائد كان يستحق كل دقيقة. إذا كنت تعاني من بطء وتكاليف الاتصالات بين خدماتك المصغرة، فأنصحك بشدة أن تعطي gRPC فرصة.

لما خدماتك تصير تحكي مع بعضها بسرعة وكفاءة، زي كأنه فريق عمل متفاهم، وقتها بتعرف إنك ماشي صح. جربوا gRPC، ومش رح تندموا. ويلا، شدوا حيلكم يا شباب! 💪

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

تجربة المستخدم والابداع البصري

المستخدمون لا يقرؤون، بل يمسحون ضوئيًا: كيف تستغل ‘النماذج البصرية F و Z’ لتصميم واجهات لا تقاوم؟

بصفتي أبو عمر، أشارككم قصة من الميدان وكيف اكتشفت أن المستخدمين لا يقرؤون كلمة بكلمة. سنتعمق في كيفية استغلال النماذج البصرية F و Z لتصميم...

25 أبريل، 2026 قراءة المزيد
برمجة وقواعد بيانات

جداولنا كانت إما بطيئة أو معقدة: كيف أنقذنا “اللا تطبيع المحسوب” من جحيم الـ JOINs التي لا تنتهي؟

هل تعاني من استعلامات SQL بطيئة بسبب كثرة الـ JOINs؟ في هذه المقالة، أشارككم قصة حقيقية حول كيف تخلينا عن التطبيع الكامل لقواعد البيانات واستخدمنا...

25 أبريل، 2026 قراءة المزيد
الحوسبة السحابية

كانت بنيتنا التحتية قصراً من رمال: كيف أنقذنا ‘Infrastructure as Code’ من جحيم التغييرات اليدوية؟

في هذه المقالة، يشارك أبو عمر، مطور برمجيات فلسطيني، قصة واقعية عن كارثة كادت أن تدمر مشروعاً بسبب التعديلات اليدوية على البنية التحتية. نستكشف معاً...

25 أبريل، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كانت سيرتي الذاتية تصرخ في فراغ: كيف أنقذتني المساهمات في المشاريع المفتوحة المصدر من جحيم ‘الرفض التلقائي’؟

أنا أبو عمر، وأروي لكم حكايتي مع رسائل الرفض التلقائية وكيف تحولت من مبرمج بسيرة ذاتية باهتة إلى مطور يملك سجلاً حافلاً بالإنجازات بفضل المشاريع...

25 أبريل، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

قاعدة بياناتنا كانت تستغيث: كيف أنقذتنا استراتيجيات التخزين المؤقت (Caching) من جحيم الحمل الزائد؟

أشارككم قصة حقيقية عن يوم كادت فيه قاعدة بياناتنا أن تنهار تحت ضغط القراءة الهائل، وكيف كانت استراتيجيات التخزين المؤقت (Caching)، وتحديداً Redis، هي طوق...

25 أبريل، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

كانت عمليات الاحتيال تسبقنا بخطوة: كيف أنقذتنا ‘نماج تعلم الآلة الرسومية’ (Graph ML) من جحيم الشبكات الاحتيالية؟

في هذه المقالة، أشارككم قصة حقيقية من قلب المعركة ضد المحتالين في عالم التكنولوجيا المالية. سأشرح كيف فشلت الطرق التقليدية في كشف شبكات الاحتيال المنظمة،...

25 أبريل، 2026 قراءة المزيد
البودكاست