Local-first microservices platform with 4 domain services (user, order, payment, notification) plus React frontend. Single GraphQL entry plus gRPC, Consul discovery, Kafka events, Resilience4j, PostgreSQL per service, Jaeger tracing with OTel, 4 relationship types.
- JDK 17 (
java -versionmust report 17.x, setJAVA_HOME=$HOME/jdk17or/usr/lib/jvm/java-17-openjdk-amd64if needed, never lower project Java version) - Docker and Docker Compose (tested with Docker 24+ and Compose v2)
- Maven wrapper (
./mvnw) per service, ormake build - Linters: Java uses Spotless (google-java-format 1.17) + Checkstyle 10.18 + SpotBugs 4.8 + PMD 7.3, frontend uses ESLint 8 + Prettier 3
- Optional:
grpcurlfor gRPC smoke,jqfor JSON
java -version
javac -version
docker --version
docker compose versionFrom clean clone:
git clone <repo> && cd springboot-dev
make start
make ps
make logsmake start does docker compose up --build -d for all infra plus services with healthchecks and depends.
| Component | HTTP | gRPC | Host Exposed | Notes |
|---|---|---|---|---|
| api-gateway | 8080 | 6560 | yes (gateway) | Single entry for GraphQL and gRPC |
| user-service | 8081 | 6565 | dev only | GraphQL at /graphql |
| order-service | 8082 | 6566 | dev only | GraphQL at /graphql |
| payment-service | 8083 | 6567 | dev only | GraphQL at /graphql |
| notification-service | 8084 | 6568 | dev only | GraphQL at /graphql, streaming |
| consul | 8500 | 8600 | no (internal) | UI at http://localhost:8500 |
| kafka | 9092 | 29092 internal | no | |
| zookeeper | 2181 | - | no | |
| postgres-user | 5432 | - | dev only | userdb |
| postgres-order | 5433 | - | dev only | orderdb |
| postgres-payment | 5434 | - | dev only | paymentdb |
| postgres-notification | 5435 | - | dev only | notificationdb |
| jaeger | 16686 | 14250, 4317 | yes 16686 | UI at http://localhost:16686 |
| otel-collector | - | 4317/4318 | yes 4317 | OTLP endpoint http://otel-collector:4317 |
| frontend | 5173 | - | yes | Vite dev or Nginx prod |
In production, only 8080, 6560, 16686, 5173 are host exposed. Services keep ports internal only via platform-net. For local dev, services expose 8081-8084 and 6565-6568 for debugging via profile application.yml vs docker compose. Use docker-compose.prod.yml overlay that removes service ports if needed.
graph TB
frontend[Frontend 5173 React Vite] --> gateway[API Gateway 8080/6560<br/>Spring Cloud Gateway + gRPC]
gateway -->|lb://| user[User Service 8081/6565<br/>GraphQL + gRPC]
gateway -->|lb://| order[Order Service 8082/6566<br/>GraphQL + gRPC]
gateway -->|lb://| payment[Payment Service 8083/6567<br/>GraphQL + gRPC]
gateway -->|lb://| notif[Notification Service 8084/6568<br/>GraphQL + gRPC streaming]
user -->|gRPC validate| order
order -->|gRPC validate| user
payment -->|gRPC fetch order| order
user -->|publish UserCreated| kafka[Kafka 9092<br/>user.events order.events payment.events]
order -->|publish OrderCreated| kafka
payment -->|publish PaymentCompleted| kafka
kafka --> notif
user --> pgU[(Postgres userdb 5432)]
order --> pgO[(Postgres orderdb 5433)]
payment --> pgP[(Postgres paymentdb 5434)]
notif --> pgN[(Postgres notificationdb 5435)]
gateway -.->|discovery| consul[Consul 8500]
user -.-> consul
order -.-> consul
payment -.-> consul
notif -.-> consul
gateway -->|OTLP 4317| otel[OTel Collector 4317]
user -->|OTLP| otel
order -->|OTLP| otel
payment -->|OTLP| otel
notif -->|OTLP| otel
otel --> jaeger[Jaeger 16686]
- api-gateway: Spring Cloud Gateway, GraphQL aggregation, gRPC proxy on 6560, Resilience4j circuit breaker per route with fallback
/fallback/*, RateLimiter and Bulkhead, correlationX-Request-Id, Jaeger W3C, CORS for 5173 - user-service: JPA entities
User,UserProfile,Role, GraphQL queriesuser usersand mutationscreateUser updateUser, gRPCUserService6565, Kafkauser.events, FlywayV1__init.sql - order-service:
Order,OrderItem,Product, GraphQLorder ordersByUser createOrder cancelOrder, gRPC 6566, validates user via gRPCuser-service:6565with@CircuitBreaker userServiceand fallback empty, Kafkaorder.events - payment-service:
Payment,Transaction, GraphQLpayment paymentsByOrder initiatePayment refundPayment, gRPC 6567, validates order via gRPCorder-service:6566with breakerorderServicefallback empty, Kafkapayment.events - notification-service:
Notification,NotificationTemplate,UserNotificationwith streamingStreamNotifications, GraphQL 6568, template fetch viaTemplateServicewith breakertemplateServicecached fallback, Kafka consumers for all events
See knowledge-base/wiki/concepts/data-model.md for full ER diagram.
- 1:1
users1:1user_profilesvia UNIQUE FKuser_profiles.user_id -> users.id - N:N
usersN:Nrolesviauser_rolesjoin table - 1:N / N:1
orders1:Norder_items,order_itemsN:1orders - N:N
ordersN:Nproductsviaorder_product_tagsand viaorder_items - 1:1 logical
orders1:1paymentsviapayments.order_id UNIQUE - 1:N / N:1
payments1:Ntransactions - N:N
userslogical N:Nnotificationsviauser_notificationsinbox - N:1
notifications->notification_templatesvia code
Each service owns its PostgreSQL 15 DB (userdb, orderdb, paymentdb, notificationdb). Cross-service references are UUID columns without FK constraints, validated via gRPC and eventual via Kafka.
docker compose config # lint compose
java -version # 17.x
make lint # Spotless, Checkstyle, SpotBugs, PMD
make lint-frontend # ESLint + Prettier
make test # per-service ./mvnw test
curl http://localhost:8500/v1/catalog/services
curl http://localhost:8080/actuator/gateway/routes
curl http://localhost:8081/actuator/health
curl -X POST http://localhost:8080/graphql -H "Content-Type: application/json" -d '{"query":"{ users { id } }"}'
grpcurl -plaintext localhost:6565 list
grpcurl -plaintext localhost:6565 grpc.health.v1.Health/Check
grpcurl -plaintext localhost:6560 list
docker exec kafka kafka-topics.sh --list --bootstrap-server localhost:9092
curl http://localhost:16686/api/traces?service=user-service | jq
make logs | grep traceIdCircuit breaker trip: stop user-service, create order via order-service GraphQL, verify fallback returns and breaker opens at /actuator/circuitbreakers, logs show CircuitBreaker 'userService' is OPEN, after restart breaker goes HALF_OPEN then CLOSED. Gateway fallback returns 503 with Retry-After at /fallback/*.
Jaeger: http://localhost:16686 shows trace gateway -> user-service gRPC -> order-service -> payment-service, traceId matches log pattern %X{traceId} %X{spanId} %X{requestId}.
See top of Makefile for list. Common:
make start- build and start all containers detachedmake stop- stop containersmake restart- stop and start againmake logs- follow all logs,make logs-user-servicefor onemake ps- list containersmake clean- down with volumes and orphansmake build- build imagesmake test- run testsmake lint- run Java linters (Spotless check, Checkstyle, SpotBugs, PMD) on all servicesmake lint-fix- auto-format Java via Spotlessmake lint-frontend- run ESLint and Prettier on frontendmake kafka-topics consul-ui grpc-health grpc-list jaeger-ui traces health
java -versionnot 17: installopenjdk-17-jdk, setJAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64docker compose upfails healthcheck: rundocker compose ps, checkdocker logs consul, ensure 8500 free- Port already in use 8080 or 5432:
lsof -i :8080thenmake stopor kill process grpcurlnot found: install viago install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest- Kafka topics not created: check
kafka-initlogs, scriptscripts/init-topics.sh, ensureKAFKA_AUTO_CREATE_TOPICS_ENABLE=false - Tracing missing: check
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317env,docker logs otel-collector, Jaeger UIhttp://localhost:16686, logs patterntraceId - Circuit breaker not opening: threshold is 10 calls with 50 percent failure, make at least 5 calls, check
/actuator/circuitbreakersand/actuator/healthhascircuitBreakers: UP/DOWN - Frontend blank: check
http://localhost:5173andVITE_API_URL=http://localhost:8080, CORS allows 5173,docker logs frontend - DB connection: check
postgres-*healthpg_isready,docker exec postgres-user psql -U platform -d userdb -c "\\dt" - Consul not showing services: wait 15s health interval, check each
/actuator/healthincludes db, kafka, consul, grpc
- Plans:
plans/00-overview.mdandplans/phases/ - Knowledge base:
knowledge-base/README.mdwithwiki/concepts/(data-model, kafka-topics, graphql-federation, grpc-contracts, tracing, consul-architecture, resilience) andwiki/adrs/
springboot-dev/
├── api-gateway/
├── user-service/
├── order-service/
├── payment-service/
├── notification-service/
├── frontend/
├── knowledge-base/
│ ├── raw/
│ ├── wiki/
│ ├── outputs/
│ └── tools/
├── plans/
├── scripts/
├── docker-compose.yml
├── otel-collector-config.yaml
├── Makefile
└── .env.example
All 6 phases green. One command git clone ... && make start boots gateway 8080 plus gRPC 6560-6568, Consul discovery 8500, Kafka events, Resilience4j on both paths, PostgreSQL per service, Jaeger tracing with OTel, 4 relationship types, React frontend, empty knowledge-base ready.