بتذكرها زي كأنه امبارح. كنا في شركة ناشئة في قطاع التكنولوجيا المالية (Fintech)، والنمو كان صاروخي. كل يوم مستخدمين جداد، معاملات بالآلاف، والمستثمرين مبسوطين. لكن خلف الكواليس، في قسم العمليات والمحاسبة، كانت الكارثة بتتحضر على نار هادية.
في ليلة من الليالي، حوالي الساعة 2 بعد منتصف الليل، رن تلفوني. على الخط كان المدير المالي، صوته كله توتر: “أبو عمر، الحقنا! في مبالغ ضخمة مش عارفين وين راحت. الأرصدة في نظامنا ما بتطابق كشوفات البنك بالمرة. إحنا حرفيًا بنخسر مصاري ومش عارفين ليش!”.
نزلت عالشركة فورًا. المكتب كان مسرح جريمة تقني: أكواب قهوة فاضية في كل مكان، شاشات مليانة جداول إكسل، ووجوه عليها كل علامات الإرهاق واليأس. قضينا الليلة كلها وإحنا بنعمل تسويات يدوية، بنقارن سجل سجل، وبنحاول نلاقي “الأرصدة اللي تبخرت”. اكتشفنا إنه مع كل عملية دفع فاشلة، أو استرداد جزئي، أو رسوم خفية من بوابات الدفع، كان نظامنا البسيط بيفقد أثر الأموال. كانت الأرصدة تتآكل شوي شوي، مثل تسريب مي بطيء في خزان ضخم. في هذيك الليلة، أدركت إنه نجاحنا السريع بنى قلعة من رمل، وإن الأساس البرمجي المالي تبعنا كان هشًا لدرجة خطيرة.
لماذا كان نظامنا الأول كارثة تنتظر الحدوث؟
بكل بساطة، كنا نستخدم ما يسمى بـ “دفتر الأستاذ ذي القيد الواحد” (Single-Entry Ledger). هذا هو الأسلوب اللي بيستخدمه صاحب الدكان في دفتره القديم: “دخل 100 شيكل”، “خرج 50 شيكل”. في عالم البرمجة، كان هذا يترجم إلى جدول بسيط في قاعدة البيانات اسمه wallets أو balances.
لما مستخدم يحول فلوس لآخر، الكود كان بيعمل إشي زي هيك:
-- تخيل هذا الكود داخل دالة برمجية
UPDATE wallets SET balance = balance - 100 WHERE user_id = 'user_A';
UPDATE wallets SET balance = balance + 100 WHERE user_id = 'user_B';
يبدو بسيطًا ومنطقيًا، صح؟ لكن ماذا لو حدث خطأ في النظام بين تنفيذ السطر الأول والثاني؟ ماذا لو تعطل السيرفر؟ النتيجة: 100 شيكل اختفت من حساب الأول ولم تصل للثاني. لقد تبخرت في الهواء الرقمي. هذه هي المشكلة الأساسية: العمليات المالية ليست ذرية (Atomic). وهذا هو المدخل إلى جحيم التسويات اليدوية.
أعراض المرض:
- تضارب الأرصدة (Balance Drift): الأرصدة في نظامنا لا تتطابق أبدًا مع الواقع.
- صعوبة التدقيق (Audit Hell): عندما يسأل قسم المحاسبة “لماذا رصيد هذا العميل 50.5 وليس 50؟”، لا توجد إجابة سهلة. لا يوجد سجل واضح يوضح رحلة كل قرش.
- العمليات المعقدة المستحيلة: كيف تتعامل مع استرداد جزئي؟ أو تقسيم دفعة بين عدة أطراف؟ أو رسوم المعاملات؟ النظام البسيط ينهار تمامًا.
الحل السحري القديم: دفتر الأستاذ المزدوج (Double-Entry Ledger)
الحل لم يكن اختراعًا جديدًا أو تقنية معقدة. الحل كان مبدأ محاسبيًا اخترعه التجار الإيطاليون في القرن الخامس عشر، واسمه “دفتر الأستاذ المزدوج”. الفكرة عبقرية في بساطتها:
لكل عملية مالية، يوجد طرفان (أو أكثر). لا يمكن للمال أن يظهر من العدم أو يختفي في الهواء. يجب أن يأتي من مكان ما ويذهب إلى مكان آخر. لكل “دين” (Debit)، يجب أن يكون هناك “دائن” (Credit) مساوٍ له في القيمة.
المعادلة المحاسبية الأساسية هي: الأصول = الخصوم + حقوق الملكية. في نظامنا، هذا يعني أن مجموع كل الحركات يجب أن يساوي صفرًا دائمًا. إذا لم يكن المجموع صفرًا، فهناك خطأ ما في مكان ما.
تطبيق المبدأ برمجيًا
قررنا إعادة بناء النظام المالي من الصفر. بدلًا من جدول واحد للأرصدة، قمنا ببناء هيكل يعكس المبدأ المحاسبي. هذا هو التصميم المبسط الذي وصلنا إليه:
1. جدول الحسابات (Accounts)
هذا الجدول يمثل كل “الأماكن” التي يمكن أن تتواجد فيها الأموال. ليس فقط حسابات المستخدمين، بل كل شيء!
id: معرف فريد للحساب.name: اسم الحساب (مثلاً: “محفظة أحمد”، “حساب إيرادات الشركة”، “حساب رسوم بوابة الدفع”).type: نوع الحساب (مثلاً: ASSET, LIABILITY, EQUITY, REVENUE, EXPENSE).user_id(اختياري): لربط الحساب بمستخدم معين.
2. جدول القيود (Entries)
هذا هو قلب النظام. كل صف في هذا الجدول يمثل حركة واحدة (إما دين أو دائن) على حساب واحد. هذا الجدول غير قابل للتعديل (Immutable). لا نحذف أو نعدل أي قيد، بل نضيف قيودًا جديدة لتصحيح الأخطاء.
id: معرف فريد.transaction_id: يربط كل القيود التي تنتمي لنفس العملية المالية.account_id: الحساب الذي تأثر.amount: المبلغ. نستخدم أعدادًا صحيحة (integers) ونخزن أصغر وحدة (مثلاً: هللات أو سنتات) لتجنب مشاكل الفاصلة العائمة. المبلغ يكون موجبًا للدين (Debit) وسالبًا للدائن (Credit).created_at: تاريخ ووقت القيد.
3. جدول المعاملات (Transactions)
هذا الجدول يجمع القيود معًا. كل عملية مالية في العالم الحقيقي (تحويل، شراء، إلخ) هي “معاملة” واحدة في هذا الجدول.
id: معرف فريد للمعاملة.description: وصف للعملية.created_at: تاريخ ووقت المعاملة.
مثال عملي: “أحمد” يحول 100 ريال إلى “فاطمة”
في النظام القديم، كانت عمليتا UPDATE. في النظام الجديد، العملية تبدو هكذا:
نحن نقوم بإنشاء “معاملة” واحدة، و “قيدين” مرتبطين بها داخل كتلة معاملة قاعدة بيانات (Database Transaction) لضمان أن تتم كلها معًا أو لا تتم على الإطلاق (Atomicity).
BEGIN;
-- 1. إنشاء معاملة جديدة لوصف العملية
INSERT INTO transactions (description) VALUES ('تحويل من أحمد إلى فاطمة') RETURNING id INTO new_transaction_id;
-- 2. إنشاء قيد الدين (Debit): خصم 100 من حساب أحمد
-- المبلغ موجب لأنه دين (زيادة في أصول الطرف الآخر، أو نقص في أصول هذا الطرف)
INSERT INTO entries (transaction_id, account_id, amount) VALUES (new_transaction_id, 'account_ahmad', 10000); -- 100.00 ريال
-- 3. إنشاء قيد الدائن (Credit): إضافة 100 إلى حساب فاطمة
-- المبلغ سالب لأنه دائن
INSERT INTO entries (transaction_id, account_id, amount) VALUES (new_transaction_id, 'account_fatima', -10000); -- 100.00 ريال
COMMIT;
لاحظ جمال هذا التصميم:
- الذرية مضمونة: إما أن يتم تنفيذ كل القيود بنجاح أو يفشل كل شيء ويتم التراجع (Rollback). لا يمكن للمال أن يختفي.
- التدقيق أصبح سهلًا: لمعرفة رصيد أحمد، كل ما نحتاجه هو:
SELECT SUM(amount) FROM entries WHERE account_id = 'account_ahmad';. ولمعرفة تاريخ حركاته، نعرض له كل القيود المرتبطة بحسابه. - النظام متوازن دائمًا: لو جمعنا حقل
amountفي جدولentriesبأكمله، يجب أن يكون المجموع صفرًا دائمًا. هذا فحص صحة (Sanity Check) قوي جدًا يمكن تشغيله بشكل دوري.
نصيحة من أبو عمر 💡
لا تخزن الرصيد الحالي في جدول الحسابات (
accounts)! حساب الرصيد في كل مرة من جدول القيود (entries) قد يبدو بطيئًا، ولكنه الطريقة الأكثر دقة وأمانًا. عندما يصبح الأداء مشكلة، يمكنك استخدام تقنيات مثل الـ Materialized Views أو التخزين المؤقت (Caching) المحسوب بعناية، ولكن يجب أن يكون “مصدر الحقيقة” الوحيد هو جدول القيود غير القابل للتعديل.
ما بعد الأساسيات: الارتقاء بالنظام
بعد تطبيق الأساس، أصبحنا قادرين على التعامل مع سيناريوهات معقدة بسهولة:
مثال: عملية شراء مع رسوم بوابة دفع (2.5%)
لنفترض أن أحمد اشترى منتجًا بـ 100 ريال من تاجر. بوابة الدفع تأخذ عمولة 2.5 ريال.
المعاملة الآن ستحتوي على 4 قيود:
- دين (Debit) على حساب أحمد: 100 ريال (نقص في أصوله).
- دائن (Credit) لحساب التاجر: 97.5 ريال (زيادة في أصوله).
- دائن (Credit) لحساب إيرادات رسوم بوابة الدفع: 2.5 ريال (زيادة في حقوق ملكيتنا).
- دائن (Credit) لحساب “الأموال المعلقة” لدى بوابة الدفع: -100 ريال، ثم دين (Debit) بـ 100 عند وصول المال لحسابنا البنكي. هذا يمثل حركة الأموال في العالم الحقيقي.
المجموع الكلي لهذه القيود = 100 – 97.5 – 2.5 = 0. النظام ما زال متوازنًا! أصبحنا قادرين على تتبع كل قرش يمر عبر نظامنا بدقة متناهية.
الخلاصة: لا تعيد اختراع العجلة (خاصة لو كانت العجلة ذهبية)
الدرس الذي تعلمناه من تلك الليالي الطويلة والمُرهقة كان قاسيًا ولكنه ثمين: في عالم التكنولوجيا المالية، الثقة هي عملتك الأساسية. والموثوقية تأتي من أسس برمجية صلبة ومبادئ مجربة عبر الزمن.
قد يبدو دفتر الأستاذ المزدوج معقدًا في البداية مقارنةً بجدول أرصدة بسيط، ولكنه استثمار لا يقدر بثمن في مستقبل نظامك. إنه ينقلك من عالم الفوضى والتخمين إلى عالم من الدقة والوضوح والسكينة. لقد أنقذنا من جحيم التسويات اليدوية، وسمح لنا بالنوم ليلًا ونحن مطمئنون أن أرصدتنا لن تتبخر في الهواء مرة أخرى.
نصيحتي لكل مطور يعمل على نظام فيه حركة أموال: لا تبدأ بدونه. ابدأ صح، ابدأ بدفتر الأستاذ المزدوج. ستشكر نفسك لاحقًا. 🙏