Add cqrs module: CQRS in Spring Boot Without a Framework

Separate write/read DataSources, a JPA command side, and two interchangeable
projection listeners (sync and @Async) demonstrating the real latency-versus-
freshness trade-off CQRS forces. Includes a reflection-based proof that the
query side has no dependency on the write side, and a real failure/fix
transcript for the -parameters compiler flag this standalone reactor doesn't
inherit from spring-boot-starter-parent.
This commit is contained in:
2026-10-03 20:59:55 +00:00
parent d815a37f2e
commit b4a623b889
27 changed files with 1086 additions and 0 deletions
@@ -0,0 +1,51 @@
package com.ankurm.cqrsdemo;
import com.ankurm.cqrsdemo.command.OrderCommandController.LineRequest;
import com.ankurm.cqrsdemo.command.OrderCommandController.PlaceOrderRequest;
import com.ankurm.cqrsdemo.command.OrderCommandController.PlaceOrderResponse;
import com.ankurm.cqrsdemo.query.OrderSummary;
import org.junit.jupiter.api.Test;
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate;
import org.springframework.boot.resttestclient.TestRestTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.ResponseEntity;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Default profile: no {@code async-projection}, so {@link com.ankurm.cqrsdemo.read.SyncOrderSummaryProjection}
* is active. The projection's simulated write delay (300ms, see {@code application.properties})
* runs on the same thread that is about to answer the command's HTTP request, so it shows up
* directly in how long {@code POST /orders} takes -- and because of that, the read model is
* already correct the instant the command returns, with no waiting required.
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestRestTemplate
class SyncProfileLatencyTest {
@org.springframework.beans.factory.annotation.Autowired
private TestRestTemplate rest;
@Test
void placingAnOrderTakesAtLeastTheSimulatedProjectionDelayAndTheReadModelIsImmediatelyConsistent() {
PlaceOrderRequest request = new PlaceOrderRequest("Priya",
List.of(new LineRequest("USB-C-CABLE", 2, 1299), new LineRequest("USB-C-HUB", 1, 4999)));
long start = System.currentTimeMillis();
ResponseEntity<PlaceOrderResponse> placed = rest.postForEntity("/orders", request, PlaceOrderResponse.class);
long elapsedMs = System.currentTimeMillis() - start;
String orderId = placed.getBody().orderId();
System.out.println("POST /orders (sync profile) took " + elapsedMs + " ms, orderId=" + orderId);
assertThat(elapsedMs).isGreaterThanOrEqualTo(300L);
ResponseEntity<OrderSummary> summary = rest.getForEntity("/order-summaries/" + orderId, OrderSummary.class);
System.out.println("Immediately after POST returned, GET /order-summaries/" + orderId
+ " -> status=" + summary.getStatusCode() + ", body=" + summary.getBody());
assertThat(summary.getBody()).isNotNull();
assertThat(summary.getBody().totalCents()).isEqualTo(2 * 1299 + 4999);
assertThat(summary.getBody().status()).isEqualTo("PLACED");
}
}