Middle+
Kaip įgyvendintumėte žinutės siuntimą klientui, atsižvelgiant į galimybę atšaukti operaciją?
sobes.tech AI
Atsakymas iš AI
Siųsti pranešimą klientui su galimybe atšaukti transakciją Java kalba, aš naudociau Outbox modelį.
-
Duomenų bazės transakcija:
- Pranešimas saugomas specialioje "outbox" lentelėje toje pačioje transakcijoje kaip ir pagrindinė apdorojimo logika.
- Jei transakcija atšaukiama dėl verslo logikos klaidos, įrašas "outbox" lentelėje nėra įrašomas, tai užkerta kelią pranešimo siuntimui.
-
Outbox apdorotojas:
- Atskiras procesas (pvz., foninis demonas arba planuotojas) periodiškai tikrina "outbox" lentelę naujiems, nepasiųstiems pranešimams.
- Šis procesas gauna pranešimus iš duomenų bazės.
- Siunčia pranešimus klientui (pvz., per HTTP, žinučių eilę, WebSocket ir kt.).
- Sėkmingai išsiuntus, pranešimas pažymimas kaip išsiųstas arba ištrinamas.
-
Idempotentiškumas:
- Svarbu, kad kliento pusės apdorotojas būtų idempotentiškas, kad pakartotinis pranešimo siuntimas (pvz., dėl klaidų Outbox apdorotojuje) nesukeltų nepageidaujamų pasekmių.
Pavyzdinė "outbox" lentelės struktūra:
| Laukas | Tipas | Aprašymas |
|---|---|---|
| id | UUID | Unikalus identifikatorius |
| payload | JSON | Pranešimo turinys |
| type | VARCHAR | Pranešimo tipas (pvz., "order_created") |
| created_at | TIMESTAMP | Sukūrimo laikas |
| processed_at | TIMESTAMP | Apdorojimo laikas (NULL, jei neapdorotas) |
| status | VARCHAR | Būsena (pvz., "PENDING", "SENT", "FAILED") |
Kodo pavyzdys (psevdokodas):
// Verslo logika
public void processOrder(Order order) {
try (Transaction tx = connection.beginTransaction()) {
// Pagrindinė logika: užsakymo išsaugojimas
saveOrder(order, tx);
// Įrašas "outbox" lentelėje toje pačioje transakcijoje
OutboxEntry message = new OutboxEntry(UUID.randomUUID(), orderToJson(order), "order_processed", Instant.now(), null, "PENDING");
saveOutboxEntry(message, tx);
tx.commit(); // Jei čia įvyksta klaida, "outbox" įrašas nebus išsaugotas
} catch (Exception e) {
// Transakcija bus atšaukta, "outbox" įrašas nebus pridėtas
log.error("Error processing order", e);
}
}
// "Outbox" apdorojimo ciklas
public void processOutbox() {
List<OutboxEntry> pendingMessages = getPendingOutboxEntries(); // Gauti įrašus su būsena PENDING
for (OutboxEntry message : pendingMessages) {
try {
sendMessageToClient(message.getPayload(), message.getType()); // Siųsti pranešimą klientui
updateOutboxEntryStatus(message.getId(), "SENT", Instant.now());
} catch (Exception e) {
log.error("Error sending message", e);
updateOutboxEntryStatus(message.getId(), "FAILED", null); // Pažymėti kaip nepavykę arba pakartojimo logika
}
}
}
Taip, transakcijos įrašas "outbox" garantuoja, kad pranešimas bus išsiųstas klientui tik sėkmingai užbaigus pagrindinę verslo transakciją, užtikrinant duomenų nuoseklumą.