Kotlin + Spring Boot 4
OkHttp + Failsafe clients for flaky providers, polyglot persistence (Postgres + Liquibase, Redis cache-aside, Mongo), fire-and-forget coroutine writes, a config-driven rules engine, and Testcontainers + MockK + Datadog — built around a real vehicle-valuation service.
- Core: DI, Coroutines & Layering
- Step 1
Controller → Service → Repository
Every request through a vehicle valuation flows through three layers with three separate jobs — and mixing them up is how a codebase rots.
Read lesson - Step 2
Dependency Injection in Spring Boot 4
How VehicleValuationService gets its repository and provider clients without ever constructing them itself — and why Boot 4 finds them faster than ever.
Read lesson - Step 3
Suspend All the Way Down
One blocking call hiding inside a suspend function chain is enough to stall every other request sharing its thread — here's how to make sure it never happens.
Read lesson - Step 4
Wiring It Together: The Vehicle Valuation Service
Follow one VIN from HTTP request to database row and back, and watch constructor injection and suspend functions do their jobs at every hop.
Read lesson - Step 5
Configuration & Profiles in Boot 4
The same VehicleValuationService code should point at a sandbox Black Book endpoint in dev and the real one in prod — without a single line of code changing.
Read lesson - Calling Flaky External APIs Safely
- Step 6
OkHttp Fundamentals
Before you can call Black Book, Carfax, or S&P VIS safely, you need an HTTP client that reuses connections, times out predictably, and lets you hook into every request.
Read lesson - Step 7
Modeling Third-Party Clients: Black Book, Carfax, and S&P VIS
One shared contract, three provider-specific implementations, each isolated enough that a Carfax quirk can never break a Black Book call.
Read lesson - Step 8
Failsafe Retries
Not every failed call to Black Book means Black Book is down — building a retry policy that knows the difference is what keeps one blip from becoming a wrong answer.
Read lesson - Step 9
Failsafe Circuit Breakers
When a provider is really down, the kindest thing a client can do is stop calling it — and serve a cached answer instead of a slow failure.
Read lesson - Polyglot Persistence
- Step 10
Choosing a Store: Relational vs Cache vs Document
Our vehicle-valuation service writes to three different databases on purpose — this lesson is about the decision, not the tour.
Read lesson - Step 11
Postgres for Rule Tables & Liquibase Migrations
The rules that decide guardrails, boosts and no-buys live in Postgres because they are exactly the kind of data relational databases were built to protect.
Read lesson - Step 12
Redis Cache-Aside
Three provider calls per valuation is expensive to repeat, tolerable to serve slightly stale, and exactly what Redis's cache-aside pattern was built to absorb.
Read lesson - Step 13
MongoDB for Event Logs: The Snowflake → Mongo Story
The audit trail used to land in Snowflake a day late through a batch job; moving it to MongoDB traded some analytical horsepower for a queryable log the moment an event happens.
Read lesson - Step 14
Picking the Right Store for the Right Job
Four questions — consistency, read/write ratio, schema stability, query pattern — are enough to point almost any new table at the store it actually belongs in.
Read lesson - Async Off the Request Thread
- Step 15
Fire-and-Forget with Coroutines
launch fires background work and returns a Job; async returns a Deferred you must await, and the Mongo write needs the first, not the second.
Read lesson - Step 16
Isolating Failures with SupervisorJob
A child coroutine's crash cancels its siblings and parent by default; SupervisorJob breaks that wire so one failed audit write can't take down anything else.
Read lesson - Step 17
Applying It: Writing to Mongo Off the Request Thread
VehicleValuationService gets its own application-scoped, SupervisorJob-backed CoroutineScope as a bean — never GlobalScope — so the Mongo write survives the request and failures stay contained.
Read lesson - Rules & Decision Engines
- Step 18
Why Hardcoded if Chains Don't Scale
Every guardrail the business asks for lands as one more `if` in `valuate()` — until the function is 200 lines nobody dares to touch.
Read lesson - Step 19
Modeling a Rule
One interface, one sealed hierarchy of outcomes, and a priority number — that's the entire vocabulary the rest of the decision engine is built on.
Read lesson - Step 20
Loading Rules from CSV and a Database Table
The same columns power a rule whether it's parsed from a bundled file at startup or read live from Postgres — the trick is one factory function neither source is allowed to bypass.
Read lesson - Step 21
Evaluating Rules: The Engine Walkthrough
Sort once, evaluate every rule in priority order, let a hard stop win immediately, then cap the boosts — four small steps turn a pile of configured rules into one defensible price.
Read lesson - Testing & Observability
- Step 22
Testcontainers for Postgres & Mongo
A mocked repository lets a broken migration or a duplicate key sail straight through your test suite — a real Postgres and Mongo, booted just for the test, will not.
Read lesson - Step 23
MockK for the Provider Clients
The last lesson argued for real infrastructure in your tests — this one argues for the opposite when the infrastructure belongs to someone else.
Read lesson - Step 24
Datadog Custom Metrics via Micrometer
Tests prove the code is correct once, in CI — metrics prove it is still correct right now, in production, one dashboard away from a log search.
Read lesson - Step 25
Rate-Limit Logging
A retry storm hitting a rate limiter can generate more log lines per second than a human will ever read — the fix is to log less, not to log nothing.
Read lesson - Step 26
Bringing It Together: One Request, Traced
Twenty-five lessons built one piece each — this last one follows a single VIN through every one of them, end to end, in one request.
Read lesson