كل الأعمال

دراسة حالة · سوق B2B

Msane — سلة واحدة ومصانع كثيرة

سوق تصنيع B2B تتحوّل فيه عملية إتمام الشراء الواحدة إلى عدة طلبات، ويجب أن يساوي مجموع الأجزاء الإجمالي بالضبط.

المشكلة

يربط Msane المشترين بالمصانع وتجار الجملة والمصممين. يملأ المشتري سلة واحدة بمنتجات من عدة مزوّدين ويدفع مرة واحدة. وخلف هذه الدفعة الواحدة:

  • كل مزوّد يحتاج إلى طلب خاص به،
  • كل طلب يحتاج إلى نصيبه من الكوبون والرسوم وضريبة القيمة المضافة،
  • المخزون يجب أن ينقص بأمان حين يُتمّ مشتريان الشراء في الوقت نفسه.

وفوق الكتالوج، يجري التفاوض على الطلبات المخصصة وطلبات التصميم في المحادثة، مع عروض أسعار وعينات وأقساط، ويتولّى مندوبو المصانع الطلبات نيابةً عن المصنع.

دوري

كنت مطوّر الـ Backend الوحيد وكتبت المشروع كله تقريبًا: المصادقة وتهيئة الحسابات الجديدة (onboarding)، والمنتجات، والسلة وإتمام الشراء، ودورات حياة الطلبات، والمرتجعات واسترداد المبالغ، والتقييمات، والمحافظ، والتسويات، والتقارير.

المعمارية

  1. Controller
  2. FormRequest
  3. DTO
  4. Service
  5. Repository
  6. Resource
مسار الطلب. الـ services تُرجع نتيجة، ولا ترمي exception لنتائج منطق العمل.

يوجد 48 interface للـ repositories. دوالّها مسمّاة بحسب ما يطلبه العمل، مثل الطلبات المعلّقة التي لم تُسنَد إلى المندوب بعد. وليست دوالّ عامة للإنشاء والقراءة والتحديث والحذف.

القرارات الأساسية

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

  1. تجميع بنود السلة حسب المزوّد

  2. أخذ lock على صفوف المنتجات بترتيب id تصاعدي

    عمليتا شراء تتقاطع منتجاتهما تأخذان الـ locks بالترتيب نفسه، فلا يمكن أن يحدث بينهما deadlock.

  3. حجز المخزون

  4. توزيع الكوبون والرسم الثابت على الطلبات

    بالتناسب، وبأصغر وحدة للعملة.

  5. تصحيح فرق التقريب

    تقريب كل نصيب قد يجعل المجموع يختلف بوحدة واحدة. يُسنَد الفرق بحيث تطابق الأنصبة الإجمالي بالضبط.

  6. إنشاء طلب لكل مزوّد مع تفصيله المالي

    يُخزَّن التفصيل المالي على هيئة snapshot، فلا تعيد تغييرات السعر أو ضريبة القيمة المضافة اللاحقة كتابة ما سبق.

مجموع الأجزاء يساوي دائمًا المبلغ الذي دفعه المشتري.

محفظة تستطيع حجز المال. المحفظة التي بدأت منها كانت تجمع وتطرح فقط. أعدت بناءها على هيئة ledger مع row locks وثلاث عمليات إضافية: الحجز، والإفراج، والسحب من المبلغ المحجوز. يمكن للتسوية أن تحجز رصيدًا أثناء مراجعتها. والسحوبات المجمّعة تأخذ lock واحدًا وتكتب صفًا واحدًا في الـ ledger لكل طلب.

مهل تلغي نفسها. العرض المقبول يجب دفعه خلال مهلة زمنية، ولخطوات أخرى مهل أيضًا. كل مهلة هي job مؤجّل يحمل الـ timestamp الذي أُنشئ من أجله. عند تشغيله يقارن هذا الـ timestamp بالطلب. فإذا كان الطلب قد انتقل إلى مرحلة أخرى، يخرج الـ job. ولا حاجة إلى شيء يبحث عن jobs في الـ queue ويحذفها.

أول من يقبل يفوز. يمكن أن يرى عدة مندوبين للمصانع الطلب المفتوح نفسه. القبول يأخذ row lock، فيحصل عليه واحد منهم فقط.

النتيجة

  • سُلّم المشروع في نحو سبعة أسابيع ونصف، وأنا مطوّر الـ Backend الوحيد.
  • 25 ملف feature tests فيها نحو 195 دالة اختبار، منها ثلاث مجموعات اختبار تمر بدورة حياة كاملة من البداية إلى النهاية: طلب مندوب، وطلب إرجاع، وطلب تصميم.
  • كل ميزة موثّقة مع وصف الـ API الخاص بها.

ما الذي تبقّى

صُمّم الدفع بالبطاقة خلف strategy interface، والدفع بالمحفظة يعمل من البداية إلى النهاية. ربط بوابة الدفع الفعلية كان الخطوة التالية حين غادرت المشروع. وكنت سأقسّم أيضًا أكبر اثنتين من services الطلبات، وقد تجاوزت كل منهما ألف سطر.

السيرة الذاتية

تحميل PDF
صفحة السيرة الذاتية 1صفحة السيرة الذاتية 2