التجارة متعددة القنوات: هندسة Checkout موحد عبر خمسة موردين
كيف تبني checkout موحد عبر موردين متعددين. مزامنة ثنائية الاتجاه، بروكسي توفر بالوقت الفعلي، تحديد نطاق القنوات، توجيه الطلبات، ومعالجة الأخطاء لما الموردين يفشلون بنص الـ checkout.
Multi-Channel مش "ربط بأمازون"
أغلب مقالات Multi-Channel Commerce بتوصف كيف تربط متجرك بـ APIs السوق. هاد syndication، مش هندسة. Multi-Channel Commerce الحقيقي يعني: موردين متعددين بـ APIs مختلفة، نماذج تسعير مختلفة، صيغ توفر مختلفة، وتدفقات حجز مختلفة، كلها بتنعرض للعميل كـ checkout واحد سلس.
بنينا منصة تجميع موردين بتوحد الـ checkout عبر 5 APIs لموردين. سلة واحدة، خمس موردين، طلب واحد. هالمقال بيغطي الهندسة يلي بتخلي هالشي يشتغل. للتقييم الأوسع لمنصات التجارة، شوف دليل Vendure للإنتاج ودليل منصات التجارة الإلكترونية.
مشكلة الـ Checkout الموحد
كل مورد عنده API خاصته، صيغة بيانات خاصته، نموذج توفر خاصته، وتدفق حجز خاصته:
| الجانب | مورد A | مورد B | مورد C |
|---|---|---|---|
| نمط الـ API | REST v2 | GraphQL | SOAP/XML |
| التسعير | لكل شخص، ديناميكي | سعر ثابت، شرائح | لكل مجموعة، متفاوض عليه |
| التوفر | API بالوقت الفعلي | ملف مزامنة يومي | Webhook عند التغيير |
| الحجز | خطوتين (حجز + تأكيد) | خطوة واحدة (حجز فوري) | ثلاث خطوات (عرض سعر + حجز + تأكيد) |
| الإلغاء | مجاني لغاية 24 ساعة | غير قابل للاسترداد | سياسة استرداد جزئي |
| صيغة الـ ID | UUID | رقمي | بادئة أبجدية رقمية |
الـ checkout الموحد لازم ينورمل كل هاد ورا interface واحد. العميل بيضيف منتجات من موردين مختلفين لسلة واحدة وبيدفع مرة وحدة. النظام بيوجه كل منتج للمورد الصح، بيعالج تدفق الحجز لكل مورد، وبيعرض تأكيد واحد للعميل.
Supplier Adapter Pattern
كل مورد بياخد adapter بينفذ interface مشترك:
interface SupplierAdapter {
search(query: SearchQuery): Promise<Product[]>;
checkAvailability(productId: string, date: string, persons: PersonConfig[]): Promise<AvailabilityResult>;
reserve(params: ReservationParams): Promise<Reservation>;
confirm(reservationId: string, bookerInfo: BookerInfo): Promise<BookingConfirmation>;
cancel(bookingId: string): Promise<CancellationResult>;
}
// كل مورد بينفذ الـ interface بطريقة مختلفة
class SupplierAAdapter implements SupplierAdapter {
async checkAvailability(productId: string, date: string, persons: PersonConfig[]) {
// مورد A: استدعاء API بالوقت الفعلي
const response = await this.httpClient.get(`/v2/availability/${productId}`, {
params: { date, adults: persons.filter(p => p.type === 'adult').length },
});
return this.normalizeAvailability(response.data);
}
}
class SupplierBAdapter implements SupplierAdapter {
async checkAvailability(productId: string, date: string, persons: PersonConfig[]) {
// مورد B: استعلام GraphQL
const { data } = await this.graphqlClient.query({
query: AVAILABILITY_QUERY,
variables: { productId, date },
});
return this.normalizeAvailability(data.availability);
}
}
الـ adapter بينورمل استجابة كل مورد لصيغة مشتركة. سيرفس الـ checkout بيشتغل بالصيغة المشتركة، ولا مرة بالبنى الخاصة بالمورد.
بروكسي التوفر بالوقت الفعلي
بعض الموردين بيقدموا تسعير بالوقت الفعلي بيتغير كل دقيقة (تسعير ديناميكي، مخزون محدود). نتائج البحث بتعرض سعر مخزن بالـ cache، بس وقت الـ checkout النظام لازم يتأكد من السعر الحالي.
async function getAvailabilityWithFallback(
productId: string, date: string, persons: PersonConfig[]
): Promise<AvailabilityResult> {
const supplier = getSupplierForProduct(productId);
if (supplier.supportsRealTimeAvailability) {
try {
// سعر مباشر من API المورد
const live = await supplier.adapter.checkAvailability(productId, date, persons);
await cache.set(`avail:${productId}:${date}`, live, { ttl: 300 });
return live;
} catch (error) {
// API المورد معطل: رجع السعر المخزن مع تحذير
const cached = await cache.get(`avail:${productId}:${date}`);
if (cached) {
return { ...cached, stale: true, warning: 'Price may have changed' };
}
throw new AvailabilityError('Cannot determine availability');
}
}
// مورد بتوفر دفعات: رجع من الفهرس
return searchIndex.getAvailability(productId, date);
}
الـ proxy pattern: جرب مباشر، ارجع للـ cache، ارجع للفهرس. دايماً خبر العميل إذا السعر ممكن يكون قديم.
المزامنة الثنائية: منع الحلقات اللامتناهية
لما نظامين بيزامنوا بيانات بالاتجاهين (نظام التجارة ونظام العمليات)، كل تحديث من نظام A بيطلق مزامنة لنظام B، يلي بدوره بيطلق مزامنة راجعة لـ A.
// تتبع المصدر بيمنع الحلقات اللامتناهية
interface SyncMessage {
entityId: string;
entityType: string;
data: any;
source: string; // "commerce" | "operations" | "import"
correlationId: string;
}
async function handleSync(message: SyncMessage) {
// تجاهل الرسائل يلي مصدرها هالنظام
if (message.source === THIS_SYSTEM_ID) {
return; // ACK وتخطي
}
// عالج المزامنة
await updateEntity(message.entityId, message.data);
// انشر التحديث بعلامة المصدر تبعنا
await publishSync({
...message,
source: THIS_SYSTEM_ID, // علمها عشان النظام الثاني يتجاهلها
});
}
كل رسالة بتحمل حقل source. لما نظام بيستقبل رسالة من نفسه (عبر النظام الثاني)، بيتجاهلها. الحلقة بتنكسر عند أول round-trip.
لمزيد من أنماط الـ Event-Driven شاملة الـ deduplication والـ dead letter handling، شوف دليل هندسة الـ Event-Driven.
Channel Scoping: الظهور مقابل النشر مقابل التوفر
ثلاث مفاهيم لازم تبقى منفصلة:
| المفهوم | السؤال | مثال |
|---|---|---|
| الظهور | شو الموردين يلي هالقناة بتشوفهم؟ | الموقع الألماني بيشوف موردين A، B، C. الـ Partner API بيشوف بس مورد A. |
| النشر | هالمنتج منشور ونشط؟ | المنتج موجود بس مش منشور (مسودة). |
| التوفر | هالمنتج ممكن ينحجز هلق؟ | المنتج منشور بس نفذ ليوم السبت. |
// القناة بتحدد الظهور
const channel = await channelStore.get(tenantId, channelId);
const visibleSupplierIds = channel.supplierIds;
// فلتر المنتجات حسب ظهور القناة
const products = await searchIndex.search({
query: userQuery,
filters: {
supplier_id: { $in: visibleSupplierIds }, // تحديد نطاق القناة
status: 'active', // النشر
},
});
// التوفر بيتفحص وقت الـ checkout (concern منفصل)
منتج ممكن يكون ظاهر (بقائمة موردين القناة)، منشور (حالة = نشط)، بس مش متوفر (نفذ). كل واحد فلتر مختلف بمرحلة مختلفة من التدفق.
توجيه الطلبات
لما طلب فيه منتجات من موردين متعددين، كل منتج لازم يتوجه للمورد الصح للتنفيذ:
async function processMultiSupplierOrder(order: Order): Promise<OrderResult> {
// تجميع المنتجات حسب المورد
const itemsBySupplier = groupBy(order.items, item => item.supplierId);
// معالجة منتجات كل مورد بشكل مستقل
const results = await Promise.allSettled(
Object.entries(itemsBySupplier).map(async ([supplierId, items]) => {
const adapter = getAdapter(supplierId);
// حجز
const reservation = await adapter.reserve({
items,
booker: order.bookerInfo,
expiresIn: 900, // 15 دقيقة حجز
});
// تأكيد
return adapter.confirm(reservation.id, order.bookerInfo);
})
);
// معالجة النتائج المختلطة
const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
if (failed.length > 0 && successful.length > 0) {
// نجاح جزئي: بعض الموردين انحجزوا، وبعضهم فشلوا
// نلغي الحجوزات الناجحة؟ أو نأكد الطلب الجزئي؟
// هاي قرار تجاري، مش تقني.
return handlePartialSuccess(order, successful, failed);
}
return { status: failed.length === 0 ? 'COMPLETED' : 'FAILED', results };
}
مشكلة النجاح الجزئي
شو بيصير لما مورد A بيأكد بس مورد B بيفشل؟ الخيارات:
| الاستراتيجية | السلوك | الأفضل لـ |
|---|---|---|
| الكل أو لا شي | إلغاء مورد A إذا B فشل | طلبات عالية القيمة، فعاليات |
| تنفيذ جزئي | تأكيد A، إعلام العميل عن B | منتجات قابلة للاستبدال |
| إعادة محاولة ثم إلغاء | إعادة محاولة B 3 مرات، إلغاء A إذا B فشل نهائياً | توازن بين UX والموثوقية |
الاستراتيجية الصح بتعتمد على البزنس. لتذاكر الفعاليات (مش قابلة للاستبدال)، الكل أو لا شي عادةً هو الصح. للمنتجات القابلة للاستبدال، التنفيذ الجزئي أفضل.
معالجة الأخطاء: لما الموردين يفشلوا بنص الـ Checkout
| نقطة الفشل | شو صار | الاستجابة |
|---|---|---|
| خلال البحث | timeout بـ API المورد | عرض نتائج من الموردين الثانيين |
| خلال فحص التوفر | سعر قديم | عرض السعر المخزن مع تحذير |
| خلال الحجز | المخزون نفذ | حذف المنتج من السلة، اقتراح بدائل |
| خلال التأكيد | الدفع مرفوض من المورد | إلغاء الحجز، إعادة محاولة الدفع، أو استرداد |
| بعد التأكيد | المورد بعث إلغاء | إعلام العميل، عرض إعادة حجز أو استرداد |
| خلال الإلغاء | API المورد معطل | وضع الإلغاء بالطابور لإعادة المحاولة |
كل خطأ لازم يتعالج بشكل صريح. "صار خطأ ما" ما بتنقبل أبداً كرسالة خطأ للعميل بمعاملة تجارية.
أخطاء شائعة
-
افتراض إنو كل الموردين عندهم نفس تدفق الحجز. تدفقات الخطوتين، الخطوة الواحدة، والثلاث خطوات كلها موجودة. الـ adapter لازم ينورملها.
-
تجاهل تغيرات الأسعار بين البحث والـ checkout. التسعير الديناميكي يعني إنو سعر الـ checkout ممكن يختلف عن سعر البحث. لازم تتحقق وقت الـ checkout.
-
ما في تتبع مصدر بالمزامنة الثنائية. بدونه، كل تحديث بيلف بشكل لا نهائي بين النظامين.
-
معاملة ظهور القناة كـ UI concern. تحديد نطاق القناة لازم ينفرض على مستوى الـ query، مش بس بالـ frontend. مستهلكي الـ API بيشوفوا نفس الفلاتر.
-
ما في استراتيجية لحالات الفشل الجزئي. طلبات الموردين المتعددين رح تفشل جزئياً. حدد سياسة البزنس قبل ما يصير هالشي بالإنتاج.
-
استدعاءات متزامنة للموردين خلال الـ checkout. إذا مورد واحد بطيء، كل الـ checkout بيستنى. عالج الموردين بالتوازي مع timeouts فردية.
النقاط الأساسية
-
الـ Supplier Adapters بتنورمل التنوع. كل مورد بياخد adapter بينفذ interface مشترك. منطق البزنس ما بيلمس صيغ خاصة بالمورد أبداً.
-
التوفر بالوقت الفعلي هو proxy pattern. جرب مباشر، ارجع للـ cache، ارجع للفهرس. دايماً بلغ العميل عن حداثة البيانات.
-
تتبع المصدر بيمنع حلقات المزامنة. كل رسالة بتحمل مصدرها. الأنظمة بتتجاهل الرسائل يلي من نفسها.
-
الظهور، النشر، والتوفر ثلاث concerns منفصلة. القناة بتحدد الظهور. حالة المنتج بتحدد النشر. المخزون بيحدد التوفر. لا تخلطهم.
-
الفشل الجزئي قرار تجاري. الكل أو لا شي مقابل التنفيذ الجزئي بيعتمد على نوع المنتج. حدد السياسة قبل ما تبني الكود.
منبني أنظمة multi-channel commerce كجزء من ممارستنا بـ التجارة الإلكترونية والبرمجيات المخصصة. إذا بتحتاج مساعدة بهندسة تكامل الموردين، تواصل مع فريقنا أو اطلب عرض سعر.
المواضيع المغطاة
أدلة ذات صلة
البنية المدفوعة بالأحداث عملياً: شو اللي فعلاً يخرب
أنماط بنية مدفوعة بالأحداث من الإنتاج الفعلي. عواصف الأحداث، حلقات المزامنة الثنائية، dead letters، مخازن الـ idempotency، والاختيار بين Kafka وRabbitMQ وBullMQ وSymfony Messenger.
اقرأ الدليلالدليل الشامل لأنظمة الذكاء الاصطناعي الوكيلي
دليل تقني لأنظمة الذكاء الاصطناعي الوكيلي في بيئات الأعمال. تعرف على البنية والقدرات والتطبيقات العملية للوكلاء المستقلين.
اقرأ الدليلالتجارة الوكيلية: كيف تخلي وكلاء الذكاء الاصطناعي يشترون بأمان
كيف تصمم تجارة وكيلية محكومة. محركات السياسات، بوابات الموافقة البشرية، إيصالات HMAC، الـ idempotency، عزل المستأجرين، وبروتوكول الدفع الوكيلي الكامل.
اقرأ الدليلجاهز لبناء أنظمة ذكاء اصطناعي جاهزة للإنتاج؟
فريقنا متخصص في بناء أنظمة ذكاء اصطناعي جاهزة للإنتاج. خلينا نحكي كيف نقدر نساعد.
ابدأ محادثة