Database Engineering

بناء سجل تاريخي غير قابل للتغيير: تطبيق نمط مصادر الأحداث لتدقيق العمليات في قواعد البيانات العلائقية

في هندسة البرمجيات الحديثة، تعد سلامة البيانات وإمكانية تتبعها متطلبات لا تقبل المساومة. بينما يتعامل العديد من المطورين مع سجلات التدقيق كخطوة ثانوية - مجرد إضافة صفوف إلى جدول منفصل `audit_log` عند حدوث التغييرات - فإن نهجاً أكثر متانة هو مصادر الأحداث (Event Sourcing). من خلال معالجة تغييرات الحالة كأحداث غير قابلة للتغيير، تحصل على سجل كامل وقابل لإعادة التشغيل لتطور تطبيقك.

يستكشف هذا المقال كيفية تنفيذ نمط مصادر الأحداث المصمم خصيصاً لتدقيق العمليات داخل أنظمة قواعد البيانات العلائقية التقليدية (مثل PostgreSQL أو MySQL)، مستفيداً من ميزات SQL لضمان عدم قابلية التغيير للأحداث وأداء عالٍ.

المفهوم الأساسي: الأحداث مقابل الحالة

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

يوفر هذا النهج عدة مزايا:

  • المساءلة الكاملة: تعرف بالضبط ما الذي تغير، ومن الذي غيّره، ومتى.
  • إمكانية إعادة التشغيل: يمكنك إعادة بناء حالة أي سجل في أي نقطة زمنية.
  • الامتثال: يلبي المتطلبات التنظيمية الصارمة لخطية البيانات (مثل GDPR، HIPAA).

تصميم المخطط للأحداث غير القابلة للتغيير

أساس هذا النمط هو جدول مخصص `audit_events`. على عكس الجداول القياسية، يجب أن يفرض هذا الجدول عدم قابلية التغيير. في PostgreSQL الحديثة، يمكننا تحقيق ذلك بأناقة باستخدام `WITHOUT OVERWRITE` أو عن طريق تقييد صلاحيات `DELETE` و `UPDATE` تماماً.

إليك تعريف مخطط قوي:

CREATE TABLE audit_events (
    event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    aggregate_id UUID NOT NULL,       -- معرف الكائن الذي يتم تعديله (مثل user_id)
    event_type VARCHAR(50) NOT NULL,  -- مثال: 'USER_CREATED', 'EMAIL_UPDATED'
    payload JSONB NOT NULL,           -- بيانات التغييرات
    metadata JSONB,                   -- سياق إضافي مثل عنوان IP، وكيل المستخدم
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT chk_audit_no_delete CHECK (true) -- يُفرض عبر الصلاحيات
);

-- فهارس للاسترجاع السريع
CREATE INDEX idx_audit_aggregate ON audit_events(aggregate_id, created_at DESC);
CREATE INDEX idx_audit_type ON audit_events(event_type);

فرض عدم قابلية التغيير باستخدام صلاحيات قاعدة البيانات

يمكن تجاوز الفحوصات على مستوى الكود. لضمان عدم قابلية التغيير الحقيقية، يجب تقييد عمليات `DELETE` و `UPDATE` المباشرة على جدول `audit_events` على مستوى قاعدة البيانات.

-- سحب صلاحيات الكتابة المباشرة من مستخدمين التطبيق
REVOKE DELETE, UPDATE ON audit_events FROM app_user;

-- إنشاء دالة لإدراج الأحداث بأمان
CREATE OR REPLACE FUNCTION insert_audit_event(
    p_aggregate_id UUID,
    p_event_type VARCHAR,
    p_payload JSONB,
    p_metadata JSONB
) RETURNS VOID AS $$
BEGIN
    INSERT INTO audit_events (aggregate_id, event_type, payload, metadata)
    VALUES (p_aggregate_id, p_event_type, p_payload, p_metadata);
END;
$$ LANGUAGE plpgsql;

إعادة بناء الحالة: نموذج القراءة

أحد التحديات في مصادر الأحداث هو قراءة البيانات. بما أننا نخزن الأحداث فقط، يجب علينا حساب الحالة الحالية. بينما يمكن القيام بذلك في كود التطبيق، يمكن لـ SQL التعامل مع هذا بكفاءة باستخدام دوال النوافذ (Window Functions).

للحصول على أحدث إصدار من ملف تعريف المستخدم، يمكنك استخدام التعبير المشترك للجدول (CTE) لجلب أحدث حدث لكل كائن مجمع:

WITH latest_events AS (
    SELECT 
        aggregate_id,
        event_type,
        payload,
        created_at,
        ROW_NUMBER() OVER (PARTITION BY aggregate_id ORDER BY created_at DESC) as rn
    FROM audit_events
    WHERE aggregate_id = 'user-123-uuid'
)
SELECT payload, created_at
FROM latest_events
WHERE rn = 1;

التطبيق العملي على مستوى التطبيق

في كود التطبيق الخاص بك، يجب أن تغلف منطق الأعمال الذي يعدل البيانات داخل معاملة (Transaction). عندما يقوم المستخدم بتحديث ملف تعريفه، لا تقوم فقط بتحديث جدول `users`؛ بل تستدعي أيضاً إجراء `insert_audit_event` المخزن.

على سبيل المثال، في خدمة Python أو Node.js:

  • بدء المعاملة
  • تنفيذ منطق الأعمال (تحديث جدول `users`)
  • حفظ الحدث (استدعاء الإجراء المخزن)
  • إتمام المعاملة

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

الخاتمة

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

Share: