Phase 08 — Distributed Systems, Applied Integration & Java for ML¶
This is the final vertical. Phase 07 taught you to ship one service; Phase 08 teaches you to make many services collaborate — reliably, asynchronously, and at scale — and to bolt an ML model onto that stack without pretending Java is Python. By the end you will have a microservice system on Kubernetes, an event pipeline on Kafka, and a Spring Boot service that serves an ONNX classifier. That is the M13 portfolio anchor.
Distributed systems are famous for failing in ways you couldn’t predict. This phase is deliberately blunt about the trade-offs: what actually works in enterprise Java, what looks good in slide decks and dies in production, and where the honest engineering judgement lives.
Timeline¶
Months 11–13 (Weeks 45–56, ~120–150 hours). This is a shorter phase by wall-clock but denser by concept. Do not skim.
Why This Phase Exists¶
Most Java engineers can build one Spring Boot service. Fewer can integrate five services without introducing bugs that only appear at 3 a.m. under load. Even fewer can serve an ML model from the JVM without either (a) shelling out to Python and calling it a day, or (b) reinventing Triton badly. This phase closes those two gaps, in that order.
What You’ll Learn¶
# |
Topic |
File |
Focus |
|---|---|---|---|
01 |
Microservices patterns |
Decomposition, API gateway, Resilience4j, correlation, idempotency |
|
02 |
Event-driven with Kafka |
Spring Kafka, delivery semantics, Streams, Debezium, alternatives |
|
03 |
Caching & distributed state |
Redis, cache-aside, Redisson locks, stampede prevention |
|
04 |
Java for ML/AI applications |
DJL, Tribuo, ONNX Runtime, Spring AI, LangChain4j |
|
05 |
Serving ML models from Java |
ONNX loading, Triton gRPC bridge, batching, drift monitoring |
|
06 |
Cloud-native Kubernetes |
Probes, HPA, sidecars, Helm, ArgoCD, Native trade-offs |
|
07 |
Case studies & war stories |
Netflix, LinkedIn, Twitter, ING, Uber Michelangelo |
|
— |
Projects & capstone |
ML categorizer, Kafka pipeline, capstone microservice system |
How to Work Through This Phase¶
Read files 01–03 first. These are the load-bearing distributed patterns.
Then 04 and 05 together — the Java-ML story only makes sense as a pair.
File 06 is a consolidation of Kubernetes-shaped ideas you’ve already touched in Phase 07/07.
File 07 is deliberately last. War stories only land when you’ve felt the pain in files 01–06.
Start Project (a) after finishing file 05. Start Project (b) after file 02. The capstone (c) needs everything.
Expect to reread files 01 and 02 at least twice. Distributed systems concepts don’t stick on first pass.
Dev Environment¶
Add to your Phase 07 setup:
# macOS / Linux
brew install kubectl helm kind
brew install --cask docker # or OrbStack; you already have this
brew install kafka # optional; docker-compose is fine
pip install torch onnx onnxruntime # for producing ONNX artefacts to serve from Java
You’ll also want:
kindorminikubefor a local Kubernetes cluster (kind is faster to boot)k9sfor a sane Kubernetes UI in the terminalsternfor tailing logs across podsA free Confluent Cloud or Aiven Kafka trial (optional; local Kafka in Docker Compose is fine)
An Ollama install (
brew install ollama) if you want to run local LLMs for the Spring AI / LangChain4j sections
Exit Criteria¶
You can call Phase 08 done when you can honestly tick every box:
Decompose a monolith spec into 3–5 services and defend the seams (DDD-lite bounded contexts, not “one service per table”)
Configure Resilience4j circuit breaker + retry + bulkhead + timeout on a Spring Boot client and explain each parameter
Explain Kafka producer delivery semantics (at-most-once, at-least-once, exactly-once) and which config flags produce each
Wire a Spring Kafka consumer with manual ack + retry topic + dead-letter topic
Pick between Kafka, Pulsar, RabbitMQ, and NATS for a given scenario with justification
Implement cache-aside with Spring Data Redis and defend against cache stampede
Load an ONNX model from a Spring Boot service and expose it as a REST endpoint with latency < 100ms p95
Explain when to serve a model from Java directly vs proxy to a Triton/TorchServe backend
Give the honest Spring AI vs LangChain4j verdict without cheerleading either
Deploy a multi-service system to a local Kubernetes cluster (kind/minikube) with Helm, probes, HPA, and observability
Ship the capstone: URL shortener extended into a microservice system with Kafka + Redis + JWT + Prometheus/Grafana on K8s
What Most People Get Wrong¶
Cargo-cult microservices. They split a small app into 12 services on day one, then spend 6 months building infra that a monolith wouldn’t have needed. A monolith with clean modules is usually the right answer until it isn’t.
“Exactly-once” fetishism. Real exactly-once in Kafka needs idempotent producers, transactional writes, and consumer-side idempotency. Most teams need at-least-once + idempotent handlers, which is simpler and enough.
Reinventing Triton. Loading ONNX in Java is fine for classical models. For LLMs and heavy transformers, put Triton/TorchServe behind a gRPC call and stop pretending the JVM is a research platform.
Ignoring failure modes until they happen. Circuit breakers, retries, and timeouts are configured last “when we have time.” They never do. Add them in the first commit.
Kubernetes because everyone else is. If you’re running two services with fewer than 100 QPS, ECS Fargate or Cloud Run will save you three months of yak-shaving.
Don’t be that engineer.
Previous phase: ../07_enterprise_spring_data/README.md · Next: 01_microservices_patterns.md