كنت في payever شهرين. فبراير ومارس 2020 — وربما تتذكر هذين الشهرين بالذات، لأنهما الشهران اللذان سبقا مباشرةً إغلاق الجائحة لكل شيء وتغيير خطط الجميع. جولة قصيرة، عمل محدد. هذه هي قصة ذلك العمل.
payever تبني بنية تحتية للتجارة الإلكترونية — منصة تتيح للتجار إدارة مبيعاتهم عبر قنوات متعددة من واجهة واحدة. المشكلة التي سُلِّمتُ إياها: microservices في الـbackend لتكامل Mobile.de وAmazon.de وeBay. ثلاث منصات. ثلاثة نماذج بيانات مختلفة كلياً. نموذج دومين داخلي موحّد يستطيع النظام العمل معه باتساق.
كم مهندساً قابلتَ يمكنه وصف API قوائم السيارات في Mobile.de من تجربة مباشرة؟ بالضبط.
ثلاث منصات، ثلاث طرق مختلفة لفهم التجارة
أهم ما يجب فهمه في تكامل المنصات: كل منصة ليست مجرد API مختلف. إنها نموذج مفاهيمي مختلف لمعنى التجارة ذاتها.
Amazon تفكّر بالكتالوج والمخزون بشكل منفصل. المنتج له ASIN — المعرّف الأساسي في Amazon لشيء موجود في العالم. قائمتك ليست المنتج — بل هي عرضك لبيع منتج تعرفه Amazon أصلاً. هذا التمييز له تأثيرات عميقة على كيفية تصميم التحديثات: حين تغيّر سعراً، أنت تحدّث عرضك. وحين تغيّر وصف المنتج، ربما تحاول تحديث السجل الأساسي لـAmazon، وهذا الأمر لـAmazon فيه رأي صريح. تدفق التحديث القائم على الـfeed — الـMWS آنذاك، الـSP-API الآن — غير متزامن ومبني على التأكيد. تُرسل feed، تحصل على submission ID، تستعلم عن النتائج. معالجة الأخطاء ليست HTTP status code بسيطاً؛ إنها مستند نتيجة feed مُهيكل يجب تحليله.
eBay أقرب إلى نموذج المزاد/القائمة المباشر. أنت تمتلك قائمتك بالكامل — لا طبقة disambiguation للكتالوج. يبدو هذا أبسط وهو كذلك من بعض النواحي؛ والمقايضة أن قائمتك جزيرة وأنت مسؤول عن كامل بياناتها. تتحول تعقيدات التكامل نحو تدفق الطلبات: نموذج الطلب في eBay له مفهومه الخاص لرسائل المشتري، التغذية الراجعة، حل النزاعات، وبدء الإرجاع — وكل ذلك يجب تعيينه على نموذج الطلب الداخلي للمنصة.
Mobile.de هي الأمتع في الوصف على العشاء، لأن لا أحد خارج ألمانيا تقريباً يعرفها، ولا أحد في مجال البرمجيات تقريباً عمل معها مباشرة. إنها السوق الألمانية المسيطرة للسيارات المستعملة. نموذج المخزون شديد التخصص الرأسي — قائمة السيارة تحمل الماركة، الموديل، السنة، الإصدار، عداد المسافات، VIN، تصنيف شامل لحالة السيارة، ومجموعة من السمات القابلة للبحث (نوع الوقود، ناقل الحركة، درجة الانبعاثات) التي تهم السوق الألمانية بطريقة لا تفهمها منصة التجارة الإلكترونية العامة أصلاً. الـAPI موثّق جيداً، لكن النموذج المفاهيمي غريب على أي مهندس عمل فقط في تكاملات التجزئة الأفقية.
مشكلة نموذج الدومين الموحّد
مهمة المنصة أن تمنح التاجر واجهة واحدة لإدارة كل هذا. وهذا يعني أن مهمة الـbackend أن يكون له نموذج دومين داخلي واحد يستطيع تمثيل القائمة على كل المنصات الثلاث بأمانة والقيام بتحديثات ذهاباً وإياباً بينها.
هنا يصبح الأمر صعباً فعلاً. “المنتج” في نموذجك الداخلي يجب أن يحمل من المعلومات ما يكفي لـ:
- تقديمه لـAmazon كإدخال feed مقابل ASIN معروف، أو تشغيل طلب إنشاء منتج جديد إن لم يكن ثمة ASIN
- إدراجه في eBay بامتلاك كامل لبيانات القائمة، بما فيها الفئة، وخصائص العنصر، وسياسة الشحن
- إدراجه في Mobile.de بحقول تصنيف المركبات الصحيحة التي لا Amazon ولا eBay تحتاجها أصلاً
النهج الساذج: schema عملاق يضم كل حقل من كل منصة. هذا خطأ. الحقول ليست مختلفة فقط — إنها مختلفة دلالياً. “الحالة” على eBay (جديد، مستعمل، قطع غيار أو لا يعمل، إلخ) ليست نفس مفهوم حالة السيارة على Mobile.de التي لها مفردات تقييم خاصة بها. نموذج موحّد يحاول تسطيح هذه الفروق ينتج schema يكذب.
النهج الصحيح: نموذج دومين أساسي يحمل الحقول الحقيقية العالمية (العنوان، السعر، عدد المخزون، الوسائط)، مع امتدادات خاصة بكل منصة مُحدَّدة النوع ومُخزَّنة جانباً السجل الأساسي. حين تنشر على منصة، تدمج السجل الأساسي مع الامتداد المناسب. حين تتلقى تحديثاً من منصة، تفككه مجدداً. نموذج المستند في MongoDB مناسب لهذا لأن حقول الامتداد تختلف بحسب المنصة وليس لها schema ثابت يصلح في جدول علاقي.
NestJS كطبقة الربط
كانت طبقة الخدمة NestJS — وهي TypeScript على Node.js بتنظيم موديولات مستوحى من Angular. لعمل التكامل كهذا، حدود الموديولات مفيدة فعلاً: موديول Amazon يمتلك صيغة الـwire الخاصة بـAmazon، موديول eBay يمتلك صيغة الـwire الخاصة بـeBay، وموديول المنتج الأساسي يمتلك نموذج الدومين. اتجاه الاعتماد واضح. لا شيء في موديول Amazon يعتمد على كيفية تمثيل eBay لقائمة ما.
RabbitMQ تولّى العمل غير المتزامن: feedات المنصات التي تحتاج وقتاً، إشعارات الطلبات الواردة من كل منصة، تحديثات المخزون التي يجب بثّها لكل القنوات النشطة حين يغيّر التاجر سعراً. توجيه الـexchange هنا كان مباشراً — exchange واحد لكل منصة للأحداث الواردة، وexchange واحد لتحديثات المنتج الداخلية يُبثّ لكل ناشري القنوات المُمكَّنة. الناشرون هم الموديولات التي تعرف كيف تترجم التحديث الداخلي إلى صيغة الـwire الخاصة بكل منصة.
ثمانية أسابيع وما تستحقه
شهران ليسا وقتاً كثيراً، ولن أدّعي أن التكامل كان مكتملاً. ما شحنّاه في تلك المدة كان مسار push الأساسي للمنتج ومزامنة الطلبات الواردة للمنصات الثلاث. تدفق الإرجاع، تكامل الرسائل، حلقة تسوية الأخطاء الكاملة — كانت في خارطة الطريق.
لكن شهرين يكفيان لتعلّم الدومين بعمق كافٍ لتكوين آراء مفيدة بشأنه. أعرف كيف يبدو تصنيف المركبات في Mobile.de من الداخل. أعرف كيف يبدو تدفق تأكيد الـfeed غير المتزامن في Amazon حين يفشل في التحقق من الصحة. أعرف كيف يمكن لمتطلبات item specifics الخاصة بالفئة في eBay أن تُحدد ما إذا كانت القائمة ستصل أم لا. هذه ليست مفاهيم معمارية مجردة — إنها أنماط الفشل المحددة التي كانت تظهر في السجلات وأنا أقرأها.
هذا هو نوع التنقيب في التكاملات الذي يحتاج معظم المهندسين أكثر من شهرين لتكوين آراء بشأنه، لأن معظمهم لا يعمل مع هذه الأنظمة الثلاثة في نفس الوقت. الجدول الزمني المضغوط أجبر على وضوح حول ما يهم وما هو تعقيد عرضي.
أضعت انضباط التكامل في Fulcrum: نفس التفكير في العقود المُحددة النوع بين الدومينات، ومخططات الأحداث الثابتة، والمحوّلات القابلة للتركيب للأنظمة الخارجية، ينطبق مباشرة على تنسيق الوكلاء. المشاكل متشابهة حتى لو كان الدومين مختلفاً. وعلى عكس العمل مع payever، الجدول الزمني لـFulcrum ليس شهرين.