تصميم قاعدة بيانات لمتجر إلكتروني باستخدام SQLite
يبدأ كثير من المتاجر الإلكترونية الصغيرة بجدول واحد يضم المنتج واسم العميل والطلب في الصف نفسه. تعمل هذه الطريقة في تجربة قصيرة، لكنها تجعل تحديث البيانات صعباً وتكرر المعلومات وتزيد احتمال الخطأ. الحل هو تقسيم البيانات إلى جداول ترتبط بعلاقات واضحة. في هذا المقال نصمم قاعدة بيانات SQLite لمتجر بسيط، ونحدد الجداول الأساسية، ثم ننشئها وننفذ استعلاماً يعرض تفاصيل الطلب. لن نغطي كل ما يحتاجه متجر تجاري، لكن المثال يوضح كيف تفكر قبل كتابة أول استعلام، ولماذا يعد التصميم الجيد جزءاً من البرمجة وليس مهمة منفصلة عن الكود.
الفكرة الأساسية
يتكون النموذج المقترح من جداول customers وproducts وorders وorder_items. يحتفظ العميل ببياناته مرة واحدة، ويحتفظ المنتج بسعره الحالي، بينما يمثل order السجل العام للطلب. أما order_items فيربط الطلب بالمنتجات ويخزن الكمية والسعر وقت الشراء. تخزين السعر في عنصر الطلب مهم لأن سعر المنتج قد يتغير لاحقاً، ولا ينبغي أن تتغير قيمة طلب قديم عند قراءة التقرير. المفتاح الأساسي يميز كل صف، والمفتاح الخارجي يربط جدولاً بآخر مع الحفاظ على علاقة مفهومة.
مثال برمجي عملي
import sqlite3
connection = sqlite3.connect("shop.db")
connection.execute("PRAGMA foreign_keys = ON")
connection.executescript("""
CREATE TABLE IF NOT EXISTS customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price_cents INTEGER NOT NULL CHECK (price_cents >= 0)
);
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_id INTEGER NOT NULL,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
CREATE TABLE IF NOT EXISTS order_items (
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0),
price_cents INTEGER NOT NULL CHECK (price_cents >= 0),
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
""")
connection.commit()
connection.close()
شرح المثال
يفتح الكود اتصالاً بملف SQLite، ثم يفعل التحقق من المفاتيح الخارجية قبل إنشاء الجداول. استخدمنا السعر بوحدة السنت داخل عدد صحيح بدلاً من float لتجنب مشكلات التقريب في العمليات المالية. يمنع قيد UNIQUE تكرار البريد، بينما يمنع CHECK إدخال سعر سالب أو كمية غير صالحة. المفتاح المركب في order_items يمنع تكرار المنتج نفسه داخل الطلب. في تطبيق فعلي قد تسمح بتكرار المنتج بسطرين لأسباب مختلفة، لكن هذا القيد مناسب للنموذج البسيط.
قبل إنشاء الجداول اكتب على ورقة أو مخطط صغير ما الذي تريد تخزينه وما الذي يتكرر. اسأل عن العلاقة: هل للعميل طلبات كثيرة؟ هل يضم الطلب أكثر من منتج؟ الإجابة تحدد المفاتيح الخارجية. بعد إنشاء المخطط، أضف بيانات تجريبية قليلة واكتب استعلامات القراءة التي سيحتاجها المتجر. إذا وجدت نفسك تكرر الاسم أو البريد في كل طلب، فهذه إشارة إلى أن الجدول يحتاج علاقة لا نسخة جديدة من النص.
طريقة العمل قبل كتابة الكود
قبل فتح محرر النصوص، اكتب النتيجة التي تريد الوصول إليها وحدد المدخلات والمخرجات. يساعد هذا التمرين على كشف الحالات الغامضة مبكراً، مثل قيمة ناقصة أو قائمة فارغة أو طلب لا يصل إلى الخادم. لا تحتاج إلى مخطط كبير للمثال التعليمي، لكنك تحتاج إلى أسماء واضحة وخطوات يمكن اختبارها واحدة بعد أخرى. عندما تتغير الفكرة أثناء الكتابة، عدل التصميم قبل إضافة شروط متفرعة يصعب تتبعها.
قسّم المشكلة إلى أجزاء صغيرة، واجعل كل جزء مسؤولاً عن قرار واحد قدر الإمكان. يمكن أن تكون الأجزاء دوال، أو مكونات، أو طبقات منفصلة بحسب التقنية. لا يعني التقسيم إنشاء ملفات كثيرة، بل يعني أن تعرف أين تبحث عندما يحدث الخطأ. سجّل الافتراضات المهمة بجملة قصيرة، مثل أن القائمة مرتبة أو أن السعر غير سالب، ثم تحقق منها في المكان المناسب بدلاً من الاعتماد على الذاكرة.
اختبار المثال في حالات مختلفة
لا تختبر المسار الطبيعي فقط. جرّب مدخلاً فارغاً، وقيمة أكبر من المتوقع، وعنصراً غير موجود، وطلباً يصل من دون البيانات المطلوبة. الحالات الحدية تكشف غالباً مشكلات الفهارس والأنواع وترتيب التنفيذ. اكتب النتيجة المتوقعة قبل تشغيل الكود، ثم قارنها بالنتيجة الفعلية. إذا كان الاختبار يمر بالصدفة، أضف شرطاً أو اختباراً أوضح، ولا تكتفِ بطباعة قيمة تبدو منطقية.
- تحقق من المدخلات قبل استخدامها في الحساب أو التخزين.
- أعد رسالة مفهومة ورمزاً مناسباً عند وقوع الخطأ.
- اجعل الاختبارات قابلة للإعادة من دون اعتماد على شبكة أو وقت متغير.
- راجع أثر التغيير على الأجزاء التي تستدعي الدالة أو المكون.
ملاحظات تتعلق بجودة الكود
تظهر جودة الحل في التفاصيل الصغيرة: اسم يشرح الغرض، ودالة لا تجمع مهاماً بعيدة، ورسالة خطأ لا تترك القارئ في حيرة. لا تحاول اختصار كل سطر، فالكود المقروء أفضل من تعبير قصير يحتاج إلى شرح طويل. وفي الوقت نفسه، لا تكرر القاعدة نفسها في أماكن كثيرة؛ انقلها إلى موضع واحد عندما يكون ذلك أوضح. راجع الملف بعد أن يعمل، لأن أول نسخة تركز عادةً على الوصول إلى النتيجة أكثر من قابلية الصيانة.
احتفظ بالإعدادات التي تختلف بين جهاز وآخر خارج الكود، ولا تضع كلمات مرور أو مفاتيح خاصة في المستودع. استخدم سجلات مناسبة أثناء التطوير، ثم راجع ما ينبغي حجبه في بيئة التشغيل. إذا تعامل البرنامج مع بيانات المستخدم، فافصل بين ما يحتاجه التطبيق وما يمكن الاحتفاظ به. هذه الممارسات لا تخص لغة واحدة، بل تقلل المشكلات عندما يكبر المشروع أو يعمل عليه أكثر من شخص.
متى تعرف أن الحل يحتاج إلى تطوير؟
يحتاج المثال التعليمي إلى طبقات إضافية عندما يدخل في نظام حقيقي: قاعدة بيانات، مستخدمون متعددون، مراقبة، اختبارات، وصلاحيات. لا تضف هذه الأجزاء قبل معرفة المشكلة التي تحلها، لكن لا تنقل الكود التجريبي إلى الإنتاج كما هو. راقب حجم البيانات، وعدد الطلبات، ومصدر المدخلات، وما إذا كان الفشل يجب أن يعيد العملية أو يوقفها. اكتب قرارك في مستند صغير أو تعليق يشرح السبب، حتى لا يضطر الفريق إلى تخمينه لاحقاً.
إذا وجدت أن الخطأ يتكرر في أكثر من مكان، فابحث عن قاعدة مشتركة. وإذا أصبح التعديل في ملف صغير يؤثر في ملفات كثيرة، فراجع حدود المسؤوليات. لا توجد بنية واحدة صحيحة لكل مشروع، لكن توجد أسئلة تساعدك على اختيار بنية مناسبة: من يملك البيانات؟ من يغيرها؟ ماذا يحدث عند الفشل؟ وكيف يمكن اختبار الجزء من دون تشغيل النظام كله؟
أخطاء ينبغي تجنبها
من الأخطاء حفظ المبلغ كرقم عشري من دون سبب واضح، أو حذف جدول المنتج رغم وجود طلبات قديمة تعتمد عليه. ينبغي أن تفكر في دورة حياة السجل، فالحذف الفيزيائي ليس دائماً أفضل خيار. لا تترك foreign_keys معطلاً في SQLite، وإلا قد تقبل بيانات تشير إلى صف غير موجود. كما لا تضع كل الأعمدة في جدول واحد بحجة أن المتجر صغير، لأن تكلفة إعادة التنظيم لاحقاً قد تكون أكبر من وقت التصميم الأول.
خطوة تالية مناسبة
بعد إتمام النموذج، أضف فهارس للمفاتيح التي تستخدمها في البحث، واكتب استعلاماً يحسب إجمالي الطلب من quantity وprice_cents. جرّب معاملة واحدة تنشئ الطلب وعناصره، ثم أعد كل شيء إذا فشلت إضافة عنصر. عندما تتجاوز قاعدة البيانات حجم المشروع المحلي، ستحتاج إلى نظام أكثر ملاءمة للتشغيل المتعدد، لكن المفاهيم الأساسية للعلاقات والقيود ستبقى نفسها.
مراجعة النموذج قبل اعتماد الجداول
قبل تنفيذ المخطط، اكتب ثلاث أو أربع عمليات سيجريها المتجر فعلاً: إضافة منتج، إنشاء طلب، عرض تاريخ مشتريات عميل، وتعديل المخزون. إذا وجدت أن عملية مهمة تحتاج إلى تكرار القيمة نفسها في أكثر من موضع، اسأل إن كان هناك جدول ناقص أو علاقة لم تُرسم بعد. اختبر أيضاً ما يحدث عند حذف عميل يملك طلبات سابقة، وهل ينبغي منع الحذف أم الاحتفاظ بالسجل مع حالة غير نشطة. هذه الأسئلة لا تحتاج إلى أداة متقدمة، لكنها تكشف قرارات تؤثر في التطبيق لسنوات. التصميم الجيد لا يمنع كل تغيير مستقبلي، لكنه يجعل التغيير مقصوداً وقابلاً للتتبع.
الخلاصة
بعد إتمام النموذج، أضف فهارس للمفاتيح التي تستخدمها في البحث، واكتب استعلاماً يحسب إجمالي الطلب من quantity وprice_cents. جرّب معاملة واحدة تنشئ الطلب وعناصره، ثم أعد كل شيء إذا فشلت إضافة عنصر. عندما تتجاوز قاعدة البيانات حجم المشروع المحلي، ستحتاج إلى نظام أكثر ملاءمة للتشغيل المتعدد، لكن المفاهيم الأساسية للعلاقات والقيود ستبقى نفسها. يوضح هذا الموضوع كيف يتحول مفهوم نظري إلى خطوات يمكن تشغيلها وفحصها.
ابدأ بتطبيق المثال على ملف صغير، ثم غيّر مدخلاً واحداً وراقب النتيجة. بعد ذلك أضف حالة فشل واكتب اختباراً لها، ثم انقل الفكرة إلى مشروعك الحقيقي بحذر. عندما تفهم سبب كل خطوة، ستستطيع تغيير الأدوات أو اللغة من دون فقدان المفهوم. البرمجة تتحسن بالمحاولات القصيرة والمراجعة المستمرة، لا بنسخ كود طويل من دون معرفة ما الذي يحميه أو ما الذي قد يكسره.