08 — Dev validation on your machine¶
How to prove the base architecture on your M1 laptop: 50 000 contracts, continuous lapse (no month-end), reporting over JSONB, and messaging from the outbox.
Prerequisites: Docker Desktop running, JDK 17+, k6 installed.
1. Start dev mode¶
cd backend/limen-server
export JAVA_HOME=~/.sdkman/candidates/java/21.0.4-amzn # or your JDK 17+
mvn quarkus:dev
Swagger: http://localhost:8080/api/swagger-ui
2. Prove continuous lapse (not month-end batch)¶
Activate with a lapse milestone a few seconds in the future:
curl -s -X POST http://localhost:8080/api/contracts/LMN-LAPSE-TEST/activations \
-H 'Content-Type: application/json' \
-d '{
"effectiveAt": "2025-11-01T00:00:00Z",
"lapseAt": "'$(date -u -v+5S +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '+5 seconds' +%Y-%m-%dT%H:%M:%SZ)'"
}' | jq .
Wait 10 seconds, then:
curl -s http://localhost:8080/api/contracts/LMN-LAPSE-TEST | jq .status
curl -s http://localhost:8080/api/milestones/statistics | jq .
Expect: LAPSED, processed count increased. No batch job was started — MilestoneDispatch polled due rows with SKIP LOCKED.
3. Seed 50 000 contracts (dev instance)¶
In a second terminal while quarkus:dev is running. Run in the foreground so k6 prints progress:
If you background it (&), watch it with:
Done when: load-status.sh shows finished successfully and book-summary reports ~50 000 contracts.
Takes ~2–3 minutes on an M1.
4. Reporting — CDC projections (phase two proof)¶
Now: /api/reporting/* reads from reporting_proj — physical tables fed asynchronously from the outbox stream. Reporting never touches contract, contract_version or ledger_entry.
Primary (write) → outbox → Kafka → ProjectionConsumer → reporting_proj
↑ ↓
same transaction /api/reporting/*
Production swaps application dispatch for Debezium on the WAL; the consumer and projection tables stay the same.
Prove it on your running dev instance¶
Restart quarkus:dev first (Flyway applies V1.2.0__cdc_projections.sql).
# One-shot: align projections with your existing 50k seed
curl -s -X POST http://localhost:8080/api/reporting/projection/backfill | jq .
# Compliance watermark — primary views vs projection store
curl -s http://localhost:8080/api/reporting/reconciliation | jq .
curl -s http://localhost:8080/api/reporting/projection/status | jq .
# Reporting now reads the projection store
curl -s http://localhost:8080/api/reporting/book-summary | jq .
curl -s http://localhost:8080/api/reporting/components | jq '.[0:3]'
Or run the full walkthrough:
Expect: reconciliation.matched: true, eventsProcessed increasing as you quote/activate/lapse, component rows updating without querying primary tables.
| Endpoint | Source | Purpose |
|---|---|---|
/reporting/components |
reporting_proj.component_row |
Actuarial / ops flatten |
/reporting/ledger-summary |
reporting_proj.ledger_bucket |
Finance / IFRS 17 actuals |
/reporting/reconciliation |
Compares views vs projections | Compliance watermark |
POST /reporting/projection/backfill |
Copies primary views → projections | Bulk seed bootstrap |
There is no contract_component table. The snapshot is truth; the projection is the reporting read-model contract.
4b. Reporting — what changed from phase one¶
Phase one queried SQL views on the primary database. That proved JSONB shape. Phase two proves isolation: finance and compliance queries do not compete with the write path.
curl -s http://localhost:8080/api/reporting/book-summary | jq .
curl -s http://localhost:8080/api/reporting/components | jq '.[0:3]'
curl -s http://localhost:8080/api/reporting/ledger-summary | jq '.[0:5]'
| Endpoint | Phase-one source | Now |
|---|---|---|
/reporting/components |
View over primary JSONB | CDC-fed reporting_proj |
/reporting/ledger-summary |
Aggregate on primary ledger | CDC-fed reporting_proj |
Production target remains ClickHouse / read replicas for scale; the pattern is proven here.
5. Messaging (outbox → channel)¶
Events are written to outbox_event in the same transaction as state changes. OutboxDispatch publishes to the limen-events channel (in-memory in dev, Kafka in prod).
Watch dev logs for:
Or query Postgres:
docker ps | grep postgres # find Dev Services container
docker exec -it <container> psql -U <user> -d <db> \
-c "select count(*) filter (where published_at is null) awaiting, count(*) published from outbox_event"
6. What each proof answers¶
| Question | How you know |
|---|---|
| Do we need month-end batch for lapse? | Milestone test — one row, continuous dispatch |
| Can we run 50k on a laptop? | load.sh seed + snapshot.sh |
| Is Redis worth it? | load.sh read — compare hot vs truth p50 |
| Can finance report without normalised components? | /reporting/components view over JSONB |
| Do events leave the transaction safely? | Outbox row + channel log + published_at set |
7. Automated suite¶
Includes ContinuousMilestoneTest, ReportingJsonbTest, and the original architecture tests.