تتطلب الأنظمة الموزعة الحديثة فرض عقود صارمة. بينما لـ REST مكانه، يقدم JSON-RPC بديلاً خفيفاً وفعالاً للتواصل بين الخدمات، خاصة في هندسة الخدمات المصغرة. ومع ذلك، يمكن أن تكون استدعاءات RPC الخام خطيرة دون التحقق من الصحة. يستكشف هذا المنشور كيفية بناء خادم JSON-RPC قوي وعالي الأداء في Go، مع دمج التحقق الصارم من صحة مخطط JSON لضمان سلامة البيانات والأمان.
لماذا JSON-RPC والتحقق من صحة المخطط؟
JSON-RPC هو بروتوكول لإجراء الإجراءات البعيدة (RPC) غير متصل وخفيف الوزن، يشبه JSON-RPC 1.0 ولكن بدون الكائنات المتداخلة. يستخدم طريقة POST، مما يسمح للعميل بنقل المعلمات المشفرة بصيغة JSON في جسم الطلب. الميزة الرئيسية هي بساطته وكفاءته. ومع ذلك، فإن الجانب السلبي لـ "كل شيء مسموح به" في أجسام الطلبات هو احتمال تسبب البيانات غير الصحيحة في حدوث أخطاء وقت التشغيل أو أخطاء منطقية.
يعمل دمج التحقق من صحة مخطط JSON كحاجز حماية. من خلال التحقق من صحة حمولة JSON الواردة مقابل مخطط مسبق التعريف قبل المعالجة، نضمن ما يلي:
- وجود الحقول المطلوبة.
- تطابق أنواع البيانات مع التوقعات.
- فرض القيود (مثل طول السلسلة أو نطاقات الأرقام).
اختيار المكتبات المناسبة
لهذه التنفيذ، سنستخدم gorilla/rpc لإطار عمل RPC، لأنه مستخدم على نطاق واسع ومستقر. للتحقق من الصحة، يُعد go-playground/validator المعيار الصناعي. ومع ذلك، للامتثال الصارم لمخطط JSON (RFC 7159)، سنستخدم github.com/xeipuuv/gojsonschema أو بديلاً حديثاً مثل github.com/santhosh-tekuri/jsonschema. من أجل الأداء العالي، سنستفيد من حزمة jsonschema التي تقوم بترجمة المخططات للتحقق السريع.
تنفيذ الخادم
لنقم ببناء خادم يقبل طلب إنشاء مستخدم. سنعرّف مخطط JSON يحدد هيكل بيانات المستخدم.
أولاً، تأكد من وجود التبعيات اللازمة:
go get github.com/gorilla/rpc/v2
go get github.com/gorilla/rpc/v2/json2
go get github.com/santhosh-tekuri/jsonschema/v3
الآن، دعنا نعرّف المعالج. المفتاح للأداء العالي هنا هو ترجمة مخطط JSON مرة واحدة عند بدء التشغيل، بدلاً من ترجمته في كل طلب.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gorilla/rpc/v2"
"github.com/gorilla/rpc/v2/json2"
"github.com/santhosh-tekuri/jsonschema/v3"
)
// UserRequest يمثل معلمات JSON-RPC الواردة
type UserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
// مخطط JSON للتحقق من الصحة
const userSchema = `{
"type": "object",
"properties": {
"name": {"type": "string", "minLength": 1},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 0, "maximum": 120}
},
"required": ["name", "email", "age"],
"additionalProperties": false
}`
func main() {
// ترجمة المخطط مرة واحدة للأداء
compiler := jsonschema.NewCompiler()
if err := compiler.AddResource("schema.json", strings.NewReader(userSchema)); err != nil {
log.Fatal(err)
}
schema, err := compiler.Compile("schema.json")
if err != nil {
log.Fatal(err)
}
// إنشاء خادم RPC
server := rpc.NewServer()
server.RegisterCodec(json2.NewCodec(), "application/json")
server.RegisterService(new(UserService), "")
// تعريف الخدمة
type UserService struct{}
// معالجة الطريقة
userService := &UserService{}
// غلاف مخصص للمعالج للتحقق من الصحة
http.Handle("/rpc", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var params json.RawMessage
// في السيناريو الحقيقي، قم بتحليل غلاف JSON-RPC لاستخراج المعلمات
// هذا مثال مبسط يركز على منطق التحقق من الصحة
// افترض أن لدينا حمولة JSON خام في الجسم
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// التحقق من الصحة مقابل المخطط
var loadedSchema jsonschema.Loader
loadedSchema = jsonschema.NewGoLoader(map[string]interface{}{}) // تحميل مبسط
// ملاحظة: في الإنتاج، استخدم gojsonschema لتسهيل تعيين هيكل Go أو التحقق الصارم من JSON
// هنا نوضح مفهوم فرض المخطط
service := new(UserService)
server.ServeHTTP(w, r)
}))
log.Println("Server listening on :8080")
http.ListenAndServe(":8080", nil)
}
أفضل الممارسات للأداء
لتعظيم الإنتاجية، ضع في اعتبارك التحسينات التالية:
- ترجمة المخططات مرة واحدة: يمكن أن تكون ترجمة مخطط JSON كثيفة الاستخدام للمعالج. قم دائماً بترجمة المخططات أثناء تهيئة الخادم.
- استخدم json.RawMessage: عند تحليل طلبات RPC، استخدم
json.RawMessageلتجنب التعيين/فك التعيين غير الضروريين قبل التحقق من الصحة. - تجميع الاتصالات: إذا كانت عملاء RPC الخاصين بك خدمات داخلية، فاستخدم تجميع اتصالات HTTP/2 لتقليل عبء مصافحة البروتوكول.
- مجموعات التخزين المؤقت: للأنظمة ذات الإنتاجية العالية للغاية، فكر في استخدام
sync.Poolلمخازن التخزين المؤقت للطلبات/الردود لتقليل ضغط إدارة الذاكرة (GC).
الخاتمة
إن بناء خادم JSON-RPC في Go أمر مباشر، ولكن إضافة التحقق من صحة مخطط JSON ترفعه من مشروع تجريبي إلى خدمة جاهزة للإنتاج. من خلال التحقق من المدخلات عند الحدود، تحمي منطق عملك من الحالات غير الصالحة وتقلل من الحمل المعرفي على معالجاتك. مع الانتباه الدقيق لترجمة المخططات وإدارة الذاكرة، يمكنك تحقيق كل من الصرامة والأداء العالي.