Rolle im AblaufPrüft die Fehlerfälle und beweist, dass die sichere Variante keine halbfertigen Zustände zurücklässt.
Damit ist der zentrale Pfad abgeschlossen; der Test-/Runner-Code und die vollständige Dateiliste darunter zeigen die übrigen Varianten.
package com.example.txdeepdive;
import com.example.txdeepdive.adapters.*;
import com.example.txdeepdive.application.*;
import com.example.txdeepdive.domain.*;
import com.example.txdeepdive.ports.*;
import com.example.txdeepdive.shared.*;
import com.example.txdeepdive.tx.*;
import java.util.List;
public final class Run4C1TestRunner {
public static void main(String[] args) {
badDualWritePublishesEventAlthoughOrderRolledBack();
safeOutboxRollsBackOrderAndOutboxTogether();
successfulSafeUseCaseCommitsOrderInventoryAndOutbox();
System.out.println("RUN4C1_TESTS_OK");
}
static Fixtures fixtures() {
TransactionManager tx = new TransactionManager();
InMemoryDatabase db = new InMemoryDatabase();
ProductId product = new ProductId("P-42");
db.putStock(product, 5);
OrderRepository orders = new InMemoryOrderRepository(db, tx);
OutboxRepository outbox = new InMemoryOutboxRepository(db, tx);
InventoryPort inventory = new TransactionalInventoryAdapter(db, tx);
InMemoryEventBroker broker = new InMemoryEventBroker();
return new Fixtures(tx, db, product, orders, outbox, inventory, broker);
}
static PlaceOrderCommand command(ProductId product, String orderId) {
return new PlaceOrderCommand("idem-" + orderId, OrderId.of(orderId), new CustomerId("C-42"), List.of(new OrderLine(product, 2, Money.eur("12.50"))));
}
static void badDualWritePublishesEventAlthoughOrderRolledBack() {
Fixtures f = fixtures();
BadDualWriteOrderService bad = new BadDualWriteOrderService(f.tx, f.orders, f.broker);
try { bad.placeOrderAndCrashBeforeCommit(command(f.product, "O-BAD")); throw new AssertionError("expected failure"); }
catch (DualWriteFailure expected) { }
assertEquals(0, f.orders.count(), "order must be rolled back");
assertEquals(1, f.broker.publishedCount(), "event was already published and cannot be rolled back");
}
static void safeOutboxRollsBackOrderAndOutboxTogether() {
Fixtures f = fixtures();
SafeOrderApplicationService safe = new SafeOrderApplicationService(f.tx, f.orders, f.inventory, f.outbox);
try { safe.placeOrderAndCrashBeforeCommit(command(f.product, "O-SAFE-FAIL")); throw new AssertionError("expected failure"); }
catch (RuntimeException expected) { }
assertEquals(0, f.orders.count(), "order rolled back");
assertEquals(0, f.outbox.count(), "outbox rolled back with order");
assertEquals(0, f.broker.publishedCount(), "broker did not see uncommitted data");
}
static void successfulSafeUseCaseCommitsOrderInventoryAndOutbox() {
Fixtures f = fixtures();
SafeOrderApplicationService safe = new SafeOrderApplicationService(f.tx, f.orders, f.inventory, f.outbox);
Result<PlaceOrderResult, DomainError> result = safe.placeOrder(command(f.product, "O-OK"));
assertTrue(result.isOk(), "result should be ok");
assertEquals(1, f.orders.count(), "order committed");
assertEquals(1, f.outbox.count(), "outbox committed");
assertEquals(3, f.inventory.available(f.product), "inventory decremented after commit");
new OutboxPublisher(f.outbox, f.broker).publishPending();
assertEquals(1, f.broker.publishedCount(), "publisher sends after commit");
}
static void assertTrue(boolean value, String msg) { if (!value) throw new AssertionError(msg); }
static void assertEquals(int expected, int actual, String msg) { if (expected != actual) throw new AssertionError(msg + " expected=" + expected + " actual=" + actual); }
record Fixtures(TransactionManager tx, InMemoryDatabase db, ProductId product, OrderRepository orders, OutboxRepository outbox, InventoryPort inventory, InMemoryEventBroker broker) { }
}