Sobes.tech
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į.

  1. 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.
  2. 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.
  3. 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ą.