المعمارية

معمارية منصة فورية لطلب السيارات بـ .NET وSignalR وFirebase وFlutter

كيف صممنا تدفقات التوزيع والتتبع المباشر والدفع في منصة طلب سيارات لفنلندا — والمفاضلة بين SignalR وFirebase للبيانات الفورية.

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

التطبيقات الأربعة

  • تطبيق الراكب (Flutter) — الحجز، التتبع المباشر، الدفع، التقييم.
  • تطبيق السائق (Flutter) — التوفر، عروض الرحلات، الملاحة، الأرباح.
  • لوحة الإدارة (Angular) — خريطة الأسطول الحية، التعرفة، العروض، النزاعات، التقارير.
  • واجهة النظام الخلفي (ASP.NET Core) — مصدر الحقيقة للرحلات والمستخدمين والتسعير والمدفوعات.

اختيار Flutter لتطبيقي الهاتف سمح لفريق واحد بمشاركة رموز التصميم وكود الشبكة والتحليلات بين الراكب والسائق، مع تسليم تطبيقات بإحساس أصلي على iOS وAndroid. أما Angular، فأنواعه القوية وبنيته المنظمة جعلته خيارًا طبيعيًا للوحة إدارة كثيفة البيانات يصونها مطورو .NET.

الفورية: SignalR أم Firebase؟

المساران الساخنان هما تحديثات موقع السائق (آلاف الكتابات الصغيرة في الدقيقة) وأحداث حالة الرحلة (قليلة لكنها حرجة). نستخدم التقنيتين معًا عن قصد:

الجانبSignalR (ASP.NET Core)Firebase Realtime Database
زمن الاستجابةمنخفض جدًا، WebSocket دائممنخفض، مُدار عالميًا
العمل دون اتصال / إعادة الاتصالمعالجة يدويةتخزين محلي ومزامنة مدمجان
البث لعدد كبير من الركابمجموعات، يحتاج Redis عند التوسعأصلي ويتوسع تلقائيًا
قواعد العملتعمل داخل كود المجال لديكقواعد أمان فقط
نموذج التكلفةحوسبة تشغّلها أصلًاحسب الاتصالات / النطاق الترددي

قاعدتنا: كل ما يغيّر المال أو حالة الرحلة يمر عبر الواجهة على SignalR؛ أما تدفق المواقع عالي التكرار ومنخفض القيمة فيُنسخ إلى Firebase ليشترك فيه تطبيق الراكب بتكلفة زهيدة ويواصل العمل في الأنفاق والمناطق ضعيفة التغطية.

C# · ASP.NET Core
// ASP.NET Core — driver location hub
public class TrackingHub : Hub
{
    private readonly ITripService _trips;
    public TrackingHub(ITripService trips) => _trips = trips;

    public async Task UpdateLocation(Guid tripId, double lat, double lng, double heading)
    {
        await _trips.RecordLocationAsync(tripId, lat, lng, heading);
        await Clients.Group($"trip:{tripId}")
                     .SendAsync("driverMoved", new { lat, lng, heading, at = DateTime.UtcNow });
    }

    public Task WatchTrip(Guid tripId) =>
        Groups.AddToGroupAsync(Context.ConnectionId, $"trip:{tripId}");
}

مطابقة السائقين مع الركاب

التوزيع استعلام جغرافي مكاني يتبعه سير عمل للعروض. نخزّن آخر موقع معروف للسائق في عمود مكاني ونستعلم عن أقرب السائقين المتاحين ضمن نصف قطر معين، ثم نرتبهم حسب وقت الوصول من Google Maps Distance Matrix API لا بالمسافة المستقيمة — فبحيرة أو خط سكة حديد يصنعان فرقًا كبيرًا في هلسنكي.

SQL
-- Nearest available drivers (PostGIS / SQL Server spatial)
SELECT TOP (10) d.Id, d.Location.STDistance(@pickup) AS Metres
FROM   Drivers d
WHERE  d.Status = 'Available'
  AND  d.Location.STDistance(@pickup) < 4000
ORDER  BY Metres;

العرض نفسه آلة حالة صغيرة: Offered → Accepted | Declined | TimedOut. مهمة مؤجلة في Hangfire تفرض المهلة، فلا يعطّل سائق فقد الاتصال طابور الانتظار أبدًا.

خريطة الراكب الحية

في Flutter، تشترك شاشة الراكب ببساطة في تدفق المواقع وتعيد رسم العلامة. ولأن التدفق يأتي من Firebase، فهو ينجو من انتقال التطبيق إلى الخلفية ومن الاتصالات المتقطعة دون كود إضافي.

Dart · Flutter
// Flutter rider app — live driver marker
StreamBuilder<DriverPosition>(
  stream: trackingService.positions(tripId),
  builder: (context, snap) {
    if (!snap.hasData) return const Center(child: CircularProgressIndicator());
    final p = snap.data!;
    return GoogleMap(
      initialCameraPosition: CameraPosition(target: LatLng(p.lat, p.lng), zoom: 15),
      markers: { Marker(markerId: const MarkerId('driver'), position: LatLng(p.lat, p.lng), rotation: p.heading) },
    );
  },
);

المدفوعات والرسائل والمهام الخلفية

  • Stripe يتولى البطاقات وApple Pay وGoogle Pay. نحجز مبلغًا تقديريًا عند الحجز ونقتطع الأجرة النهائية عند الوصول.
  • Twilio يرسل رموز OTP للتحقق من الهاتف وإشعارات الرحلة حين لا تصل الإشعارات الفورية.
  • Hangfire يشغّل المؤقتات والتحويلات الأسبوعية للسائقين ورسائل الإيصالات والتقارير الليلية — وكلها مرئية في لوحته.
درس تعلمناه: ضع مفاتيح idempotency على كل عملية دفع وكل انتقال حالة منذ اليوم الأول. شبكات الهاتف تعيد المحاولة؛ وتدفق أموالك يجب ألا يفعل.

مبنية لأوروبا

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

الخلاصة

  1. افصل الحالة (الواجهة + SignalR) عن القياسات (Firebase).
  2. رتّب السائقين حسب وقت الوصول الحقيقي لا المسافة.
  3. اجعل كل انتقال idempotent ومحكومًا بمهلة.
  4. شارك الكود بين تطبيقي الراكب والسائق عبر حزم Flutter لا بالنسخ واللصق.

تخطط لمنتج طلب سيارات أو تاكسي أو إدارة أسطول؟ تحدث مع فريقنا — لقد حللنا معظم الأجزاء الصعبة بالفعل.

IG
كتبه فريق الهندسة في بوابة عشتار

يكتب مهندسونا عمّا يبنونه كل يوم — الأنظمة الفورية، تطبيقات الهواتف، المنصات السحابية، والمفاضلات التي تقف خلفها. هل لديك سؤال عن مشروعك؟ يسعدنا الحديث معك.

جميع المقالات

لديك فكرة؟ لنبنِها معًا.

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

راسلناinfo@ishtar-gate.com مانشستر، المملكة المتحدة+44 7503 321169 بغداد، العراق+964 770 677 1307