Acesta e primul din patru articole despre Azure Service Bus. Aici acoperim fundamentele: când treci de la HTTP sincron la mesaje și cum alegi între Queue și Topic. În părțile următoare: producători și consumatori în .NET, dead-letter queue cu retry policies și Outbox pattern.
Problema cu apelurile HTTP sincrone
Scenariul clasic: un client plasează o comandă. Serviciul de comenzi apelează serviciul de plăți, apoi pe cel de stocuri, apoi pe cel de notificări — toate prin HTTP, toate sincron, toate în același request.
// Anti-pattern: lant de apeluri sincrone in acelasi request
public async Task<IActionResult> PlaceOrder(OrderRequest request)
{
var order = await _orderRepository.CreateAsync(request);
await _paymentClient.ChargeAsync(order); // 1. daca pica?
await _inventoryClient.ReserveAsync(order); // 2. daca pica dupa plata?
await _notificationClient.SendEmailAsync(order); // 3. chiar trebuie sa astepte user-ul?
return Ok(order);
}
Ce e în neregulă aici:
- Cuplare temporală — toate cele trei servicii trebuie să fie disponibile în același moment. Serviciul de notificări e picat 2 minute? Nicio comandă nu se poate plasa.
- Latența se adună — user-ul așteaptă suma latențelor tuturor apelurilor, inclusiv pentru operații care nu-l privesc (email-ul poate pleca și peste 5 secunde).
- Eșecuri parțiale greu de gestionat — plata a reușit, dar rezervarea de stoc a eșuat. Acum ce faci? Refund? Retry? În ce ordine?
- Scalare cuplată — un vârf de comenzi lovește simultan toate serviciile din lanț, chiar dacă doar unul e bottleneck-ul.
Ce schimbă messaging-ul asincron
Cu mesaje, serviciul de comenzi publică un eveniment (OrderPlaced) și răspunde imediat. Celelalte servicii consumă evenimentul în ritmul lor:
// Cu messaging: publici evenimentul si raspunzi imediat
public async Task<IActionResult> PlaceOrder(OrderRequest request)
{
var order = await _orderRepository.CreateAsync(request);
await _messagePublisher.PublishAsync(new OrderPlacedEvent(order));
return Accepted(order); // 202 -- procesarea continua asincron
}
Ce câștigi:
- Decuplare temporală — serviciul de notificări e picat? Mesajele îl așteaptă în coadă. Când revine, le procesează. Nicio comandă pierdută.
- Latență minimă pentru user — răspunzi după ce comanda e salvată, restul se întâmplă în fundal.
- Load leveling — un vârf de 1000 de comenzi devine o coadă pe care consumatorii o procesează în ritmul lor, nu un tsunami simultan pe toate serviciile.
- Retry natural — un mesaj eșuat se reprocesează automat, cu politici de retry configurabile (partea 3 din serie).
Când NU folosești mesaje
Echilibrul contează. Rămâi pe HTTP sincron când:
- Ai nevoie de răspuns imediat — o validare de card, o verificare de stoc în timp real pentru afișare. Request/response e modelul corect.
- Operația e o interogare — citirile sunt prin definiție sincrone; mesajele sunt pentru comenzi și evenimente, nu pentru query-uri.
- Fluxul e simplu și intern — două servicii care comunică rar nu justifică infrastructura de messaging.
Azure Service Bus — Queue vs. Topic/Subscription
Service Bus oferă două modele de livrare, iar alegerea între ele e prima decizie de design.
Queue — un producător, un consumator (competing consumers)
Mesajul intră în coadă și e procesat de exact un consumator. Dacă rulezi 5 instanțe ale consumatorului, ele concurează pentru mesaje — fiecare mesaj ajunge la o singură instanță. Modelul natural pentru distribuirea de lucru:
Producator --> [ Queue: orders ] --> Consumator (instanta 1, 2 sau 3)
Use case tipic: procesare comenzi, generare rapoarte, orice „work item” care trebuie executat o singură dată.
Topic/Subscription — un producător, mai mulți consumatori (pub/sub)
Mesajul e publicat pe un topic, iar fiecare subscription primește propria copie. Trei servicii interesate de OrderPlaced? Trei subscriptions, fiecare cu copia lui, procesate independent:
+--> [ Sub: payments ] --> Serviciu plati
Producator --> [ Topic ] -----+--> [ Sub: inventory ] --> Serviciu stocuri
+--> [ Sub: notifications ] --> Serviciu notificari
Fiecare subscription se comportă ca o coadă proprie: dacă serviciul de stocuri e picat, mesajele lui se acumulează în subscription-ul lui, fără să afecteze plățile sau notificările.
Filtre pe subscription
Un subscription poate primi doar un subset al mesajelor, prin filtre SQL pe proprietăți:
# Subscription care primeste doar comenzile mari
az servicebus topic subscription rule create \
--resource-group my-rg \
--namespace-name my-servicebus \
--topic-name orders \
--subscription-name high-value-orders \
--name HighValueFilter \
--filter-sql-expression "TotalAmount > 1000"
Cum alegi
| Criteriu | Queue | Topic/Subscription |
|---|---|---|
| Câți consumă un mesaj | Exact unul | Fiecare subscription primește o copie |
| Model mental | Distribuire de lucru | Difuzare de evenimente |
| Adăugarea unui nou consumator | Concurează cu ceilalți | Subscription nou, independent |
| Exemplu | „Procesează această comandă” | „S-a plasat o comandă” (cine e interesat reacționează) |
Regula practică: dacă mesajul e o comandă („fă X”), folosește Queue. Dacă e un eveniment („s-a întâmplat Y”), folosește Topic — nu știi azi câte servicii vor fi interesate mâine.
Crearea resurselor
# Namespace (Standard tier -- necesar pentru topics)
az servicebus namespace create \
--name my-servicebus \
--resource-group my-rg \
--location westeurope \
--sku Standard
# Queue
az servicebus queue create \
--name order-processing \
--namespace-name my-servicebus \
--resource-group my-rg
# Topic + subscriptions
az servicebus topic create \
--name order-events \
--namespace-name my-servicebus \
--resource-group my-rg
az servicebus topic subscription create \
--name inventory \
--topic-name order-events \
--namespace-name my-servicebus \
--resource-group my-rg
Notă de tier: Basic suportă doar queues. Standard adaugă topics. Premium aduce izolare de resurse, latență predictibilă și mesaje până la 100 MB — pentru producție cu volum serios, Premium merită evaluat.
Ce urmează
În partea a doua scriem cod: producători și consumatori în .NET cu Azure.Messaging.ServiceBus, autentificare prin Managed Identity, ServiceBusProcessor pentru consum robust și setările care contează (prefetch, concurrency, lock duration).
Întrebări? Scrie-mi la contact@ludoprogramming.com.