Cloud

Concevoir un backend d'application de livraison qui passe à l'échelle sur Azure et AWS

Machines à états des commandes, services événementiels, outbox transactionnel et les briques cloud que nous utilisons pour garder les plateformes de livraison rapides aux heures de pointe.

Les plateformes de livraison ont un profil de trafic brutal : presque rien à 4 h du matin, puis un mur de commandes à 12 h et à 19 h. Le backend doit absorber ces pics sans perdre une seule commande, garder coursiers, marchands et clients synchronisés, et rester assez simple pour qu'une petite équipe puisse l'exploiter. Voici la forme que nous utilisons.

Commencez par la machine à états de la commande

Chaque bug de livraison que nous avons débogué était, à la racine, une transition d'état illégale. Nous rendons donc les transitions explicites et testables unitairement avant d'écrire le moindre contrôleur.

C#
// Order state machine — explicit, testable transitions
public enum OrderStatus { Placed, Accepted, Preparing, ReadyForPickup, PickedUp, Delivered, Cancelled }

static readonly Dictionary<OrderStatus, OrderStatus[]> Allowed = new()
{
    [OrderStatus.Placed]         = new[] { OrderStatus.Accepted, OrderStatus.Cancelled },
    [OrderStatus.Accepted]       = new[] { OrderStatus.Preparing, OrderStatus.Cancelled },
    [OrderStatus.Preparing]      = new[] { OrderStatus.ReadyForPickup },
    [OrderStatus.ReadyForPickup] = new[] { OrderStatus.PickedUp },
    [OrderStatus.PickedUp]       = new[] { OrderStatus.Delivered },
};

public void Transition(OrderStatus next)
{
    if (!Allowed.TryGetValue(Status, out var ok) || !ok.Contains(next))
        throw new InvalidOperationException($"{Status} -> {next} is not allowed");
    Status = next;
    _events.Add(new OrderStatusChanged(Id, next, DateTime.UtcNow));
}

Chaque transition émet un événement de domaine. Ces événements pilotent tout le reste — notifications, affectation des coursiers, analytique — sans que le service de commandes sache qui écoute.

Publier les événements de façon fiable : l'outbox

Écrire en base puis publier sur un bus de messages est le problème classique de la double écriture : si la seconde étape échoue, vous avez une commande payée que personne ne livre. Le pattern outbox transactionnel le résout en écrivant l'événement dans la même transaction que la commande.

C#
// Transactional outbox — publish events reliably
await using var tx = await db.Database.BeginTransactionAsync();
db.Orders.Update(order);
db.OutboxMessages.Add(new OutboxMessage("order.status-changed", JsonSerializer.Serialize(evt)));
await db.SaveChangesAsync();
await tx.CommitAsync();
// A background worker (Hangfire / hosted service) drains the outbox to Azure Service Bus or Amazon SQS.

Des services, pas un monolithe — mais pas trop

Nous découpons généralement un backend de livraison en cinq services déployables : Commandes, Dispatch (appariement des coursiers et itinéraires), Catalogue (marchands et menus), Paiements et Notifications. Chacun possède ses données. Un découpage plus fin ajoute des sauts réseau et des coûts d'exploitation sans bénéfice réel pour une équipe de moins de vingt ingénieurs.

Correspondance des briques sur Azure et AWS

BesoinMicrosoft AzureAmazon Web Services
ConteneursAzure Kubernetes Service / Container AppsAmazon EKS / ECS Fargate
Bus de messagesAzure Service BusAmazon SQS + SNS
Base relationnelleAzure SQL / Azure Database for MySQLAmazon RDS / Aurora
Cache & géospatialAzure Cache for RedisAmazon ElastiCache
Fichiers (preuve de livraison)Blob StorageAmazon S3
ObservabilitéApplication InsightsCloudWatch + X-Ray

Le code applicatif est identique sur les deux — .NET avec des abstractions au-dessus du bus et du stockage — et c'est précisément pourquoi nous recommandons de cantonner les appels SDK propres au cloud aux bordures du système.

Absorber le pic de midi

  • Mettez à l'échelle sur la profondeur de file, pas sur le CPU. Les workers de dispatch s'ajustent au nombre de commandes non affectées.
  • Mettez le catalogue en cache. Les menus changent rarement et sont lus en permanence ; Redis avec des TTL courts supprime 80 % de la charge de la base.
  • Limitez le débit de la tablette marchand. Un restaurant qui rafraîchit chaque seconde pendant le coup de feu est un DDoS auto-infligé.
  • Préchauffez avant le pic. Une montée en charge planifiée à 11 h 30 et 18 h 30 coûte moins cher qu'une montée réactive à 12 h 05.

ETA en direct et itinéraires

Les positions des coursiers transitent par des ensembles géospatiaux Redis ; les ETA proviennent de l'API Routes de Google Maps Platform et sont mis en cache par segment. Nous regroupons les appels — une requête pour plusieurs coursiers candidats — ce qui garde la facture cartographique prévisible.

Conseil d'exploitation : attribuez à chaque commande un identifiant de corrélation qui voyage dans les journaux, les files et les notifications push. Quand un client appelle, le support retrouve toute l'histoire en quelques secondes.

En résumé

Un état explicite, des événements fiables, une poignée de services bien délimités et une mise à l'échelle pilotée par les files : c'est la recette derrière chaque plateforme de livraison que nous avons livrée. Le fournisseur cloud compte moins que la discipline.

IG
Rédigé par l'équipe d'ingénierie Ishtar Gate

Nos ingénieurs écrivent sur ce qu'ils construisent chaque jour — systèmes temps réel, applications mobiles, plateformes cloud et les compromis qui se cachent derrière. Une question sur votre propre projet ? Parlons-en.

Tous les articles

Une idée ? Construisons-la ensemble.

Parlez-nous de votre produit, de votre calendrier et de vos objectifs. Sous deux jours ouvrés, nous revenons vers vous avec une proposition claire, une esquisse d’architecture et des conseils honnêtes.

E-mailinfo@ishtar-gate.com Manchester, Royaume-Uni+44 7503 321169 Bagdad, Irak+964 770 677 1307