DSL على Go: تحويل كودك إلى لغة جديدة
تخيل أنك تكتب نظام معالجة الطلبات. كل يوم يطلب المدراء: "أضف خصم 10% لجميع العملاء من موسكو، ولكن فقط إذا كان مبلغ الطلب أكبر من 5000". تذهب إلى الكود، تعدل if-else، تختبر، تنشر. بعد أسبوع - طلب جديد: "خصم 15% للعملاء VIP، إذا كان عدد المنتجات أكثر من ثلاثة". مألوف؟
ماذا لو كان بإمكان المدراء كتابة هذه القواعد بأنفسهم؟ دون مشاركتك. دون خطر تعطيل الإنتاج. لهذا بالضبط توجد اللغات المخصصة للمجال (DSL) - لغات مدمجة مصممة خصيصًا لمهمة معينة. و Go، ببساطته وأدائه العالي، مثالي لإنشاء مثل هذه الـ DSL.
في هذه المقالة، سنشرح كيفية إنشاء DSL على Go - من الحلول المضمنة البسيطة إلى المحللات الخارجية الكاملة. ستتعلم كيفية أتمتة المهام الروتينية وإعطاء المستخدمين أداة يمكنهم فهمها دون برمجة.
ما هو DSL ولماذا هو مطلوب في Go؟
DSL (لغة مخصصة للمجال) هي لغة برمجة تم إنشاؤها لمجال ضيق. على عكس اللغات العامة (Go, Python, Java)، تحل DSL مهمة واحدة، ولكنها تفعل ذلك بأناقة.
أمثلة من الحياة الواقعية:
- SQL - DSL للعمل مع قواعد البيانات.
- Regex - DSL للبحث عن الأنماط في النص.
- HTML - DSL لترميز صفحات الويب.
في Go، تُستخدم DSL من أجل:
- التكوين - بدلاً من YAML/JSON يمكنك كتابة المنطق بلغتك الخاصة.
- قواعد الأعمال - إعطاء المحللين القدرة على تغيير المنطق دون مبرمجين.
- الاختبار - وصف السيناريوهات بشكل قابل للقراءة.
- معالجة البيانات - إنشاء خطوط أنابيب للتحويلات.
لم يتم اختيار Go عشوائيًا: مترجمه سريع، بناء الجملة موجز، والأدوات المدمجة (مثل text/scanner أو go/ast) تبسط التحليل. لكن الأهم - تسمح Go بإنشاء كل من DSL الداخلية (المضمنة في بناء جملة Go نفسه) و DSL الخارجية (ببناء جملة ومحلل خاصين بها).
DSL الداخلية على Go: استخدام بناء جملة اللغة
DSL الداخلية هي مكتبات تبدو كلغة منفصلة، ولكنها تستخدم بناء جملة Go. لا تتطلب كتابة محلل: فقط تكتب دوال "تلتصق" في سلاسل استدعاءات.
مثال: DSL لقواعد الخصم
لنفترض أننا نريد أن تبدو القواعد هكذا:
discount := Rule("خصم VIP"). When(City("موسكو")). When(OrderSum > 5000). Apply(Discount(10))كيف ننفذ هذا في Go؟ نستخدم نمط Builder والدوال عالية المستوى:
package main
import "fmt"
// Rule - هيكل القاعدةtype Rule struct { Name string Checks []func(order map[string]interface{}) bool Discount int}
// When يضيف شرطًاfunc (r *Rule) When(check func(order map[string]interface{}) bool) *Rule { r.Checks = append(r.Checks, check) return r}
// Apply يحدد الخصمfunc (r *Rule) Apply(discount int) *Rule { r.Discount = discount return r}
// Evaluate يتحقق مما إذا كان الطلب مناسبًاfunc (r *Rule) Evaluate(order map[string]interface{}) bool { for _, check := range r.Checks { if !check(order) { return false } } return true}
// دوال الشرط المساعدةfunc City(city string) func(map[string]interface{}) bool { return func(order map[string]interface{}) bool { return order["city"] == city }}
func OrderSum(order map[string]interface{}) int { return order["sum"].(int)}
// إعادة تحميل العامل > عبر الإغلاقfunc GreaterThan(a func(map[string]interface{}) int, b