Un produit VTC paraît simple depuis le siège passager : toucher, attendre, rouler, payer. Derrière cette simplicité se cache l'un des systèmes temps réel les plus exigeants qu'une petite équipe puisse construire. Dans cet article, nous parcourons l'architecture que nous avons conçue pour une plateforme VTC destinée au marché finlandais — quatre applications, un backend, et beaucoup de décisions sur ce qui doit être instantané et ce qui doit seulement être fiable.
Les quatre applications
- Application passager (Flutter) — réservation, suivi en direct, paiements, notes.
- Application chauffeur (Flutter) — disponibilité, propositions de course, navigation, revenus.
- Administration web (Angular) — carte de flotte en direct, tarifs, promotions, litiges, rapports.
- API backend (ASP.NET Core) — la source de vérité pour les courses, les utilisateurs, la tarification et les paiements.
Choisir Flutter pour les deux applications mobiles a permis à une seule équipe de partager les design tokens, le code réseau et l'analytique entre passager et chauffeur, tout en livrant des applications au rendu natif sur iOS et Android. Le typage fort et la structure opinionated d'Angular en font un choix naturel pour une console d'administration riche en données, maintenue par des développeurs .NET.
Temps réel : SignalR ou Firebase ?
Les deux chemins critiques sont les mises à jour de position des chauffeurs (des milliers de petites écritures par minute) et les événements de statut de course (peu nombreux mais critiques). Nous utilisons les deux technologies, délibérément :
| Critère | SignalR (ASP.NET Core) | Firebase Realtime Database |
|---|---|---|
| Latence | Très faible, WebSocket persistant | Faible, géré mondialement |
| Hors ligne / reconnexion | Gestion manuelle | Cache local et synchronisation intégrés |
| Diffusion à de nombreux passagers | Groupes, backplane Redis à grande échelle | Natif, mise à l'échelle automatique |
| Règles métier | S'exécutent dans votre code de domaine | Règles de sécurité uniquement |
| Modèle de coût | Du calcul que vous exploitez déjà | Par connexion / bande passante |
Notre règle : tout ce qui modifie l'argent ou l'état d'une course passe par l'API via SignalR ; le flux de positions, à haute fréquence et faible valeur, est répliqué dans Firebase pour que l'application passager s'y abonne à moindre coût et continue de fonctionner dans les tunnels et les zones blanches.
// 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}");
}Apparier chauffeurs et passagers
Le dispatch est une requête géospatiale suivie d'un workflow de proposition. Nous stockons la dernière position connue du chauffeur dans une colonne spatiale, interrogeons les chauffeurs disponibles les plus proches dans un rayon donné, puis les classons par temps d'arrivée estimé via l'API Distance Matrix de Google Maps plutôt qu'à vol d'oiseau — un lac ou une voie ferrée fait une grande différence à Helsinki.
-- 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;La proposition elle-même est une petite machine à états : Offered → Accepted | Declined | TimedOut. Une tâche différée Hangfire applique le délai d'expiration, si bien qu'un chauffeur qui perd sa connexion ne bloque jamais la file.
La carte en direct du passager
Dans Flutter, l'écran passager s'abonne simplement à un flux de positions et redessine le marqueur. Comme le flux provient de Firebase, il survit à la mise en arrière-plan de l'application et aux connexions instables sans code supplémentaire.
// 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) },
);
},
);Paiements, SMS et tâches de fond
- Stripe gère les cartes, Apple Pay et Google Pay. Nous autorisons une estimation à la réservation et capturons le montant final à l'arrivée.
- Twilio envoie les codes OTP pour la vérification du téléphone et les notifications de course lorsque le push n'est pas délivré.
- Hangfire exécute les minuteurs, les reversements hebdomadaires aux chauffeurs, les e-mails de reçu et les rapports nocturnes — le tout visible dans son tableau de bord.
Conçu pour l'Europe
Servir la Finlande impliquait une tarification en euros avec ventilation de la TVA sur les reçus, une localisation en finnois et en anglais, et un traitement RGPD strict : l'historique de positions est pseudonymisé après 30 jours, et chaque export ou suppression de données personnelles est un simple appel d'API. L'hébergement dans une région européenne d'Azure simplifie la résidence des données.
À retenir
- Séparez l'état (API + SignalR) de la télémétrie (Firebase).
- Classez les chauffeurs par temps d'arrivée réel, pas par distance.
- Rendez chaque transition idempotente et bornée dans le temps.
- Partagez le code entre les applications passager et chauffeur via des packages Flutter, pas par copier-coller.
Vous préparez un produit VTC, taxi ou de gestion de flotte ? Parlez à notre équipe — nous avons déjà résolu la plupart des parties difficiles.