An e-commerce application built with Spring Modulith. The backend publishes REST and gRPC APIs, persists data per module, and coordinates domain events through RabbitMQ. A Next.js 14 (App Router) storefront lives in frontend-next/ and consumes the /api/** endpoints proxied by nginx when running under Docker Compose.
- common – shared utilities, exception handling, cache helpers, and Hazelcast session integration that are intentionally exported for other modules.
- catalog – product catalogue domain, Liquibase migrations for the
catalogschema, and theProductRestControllerREST surface. - orders – order lifecycle, cart REST endpoints, Hazelcast write-through cache, gRPC client/server adapters, and the
OrdersApifacade for other modules. - inventory – inventory projections that react to
OrderCreatedEvent, inventory cache configuration, and Liquibase migrations for theinventoryschema. - notifications – listens to published domain events and records notification intents (currently via logging; ready for future email integration).
- config – infrastructural wiring (Hazelcast bootstrap, OpenAPI, gRPC server, observability, health checks, and session/CORS configuration).
The legacy Thymeleaf “web” module has been fully retired. UI traffic is now served by
frontend-nextor via/api/**for API clients.
frontend-next/hosts a Next.js 14 App Router application that uses React Query and the generated TypeScript SDK infrontend-sdk/.- Docker Compose entrypoint:
http://localhostis served by thewebproxynginx container, forwarding/to Next.js and/api/**to the Spring Boot monolith. - Session state is stored in Hazelcast and exposed as the
BOOKSTORE_SESSIONcookie; when bypassing nginx ensure requests include credentials so the cookie is sent.
- Hazelcast provides distributed caching for catalog, orders, inventory data and serves as the Spring Session store. Management Center runs on port
38080. - OpenTelemetry traces/metrics/logs are exported over OTLP gRPC (4317) to the bundled HyperDX all-in-one appliance (
compose.ymlservicehyperdx). - RabbitMQ transports domain events and their dead-letter queues. Modulith events are also persisted to the
eventsschema for replay.
- Catalog exports
ProductApi, which the Orders module consumes for price validation. - Orders publishes
OrderCreatedEvent; Inventory and Notifications consume it to update stock and log notification intents. - Common helpers are marked as exported modules and are the only shared implementation allowed across boundaries.
- Cross-module interactions flow through exported APIs or domain events—direct repository access across modules is not permitted.
- Server:
GrpcServerConfigstarts an in-process gRPC server (default port9091) exposing Orders operations to external consumers. - Client:
OrdersGrpcClienttargetsbookstore.grpc.client.target; in Docker Compose it points toorders-service:9090so the monolith calls the extracted Orders service, while local development defaults to the in-process server. - Health checks, reflection, deadlines, and retry behaviour are fully configurable via
application.properties.
- Java 21+ (tested with Temurin via SDKMAN)
- Docker & Docker Compose (for the full stack)
- Node.js ≥ 18.17 with
pnpm(for frontend work) - Go Task runner (
brew install go-taskorgo install github.com/go-task/task/v3/cmd/task@latest) - Optional: k6 for load tests, Playwright for frontend E2E
Verify the toolchain:
java -version
docker info
docker compose version
task --version
pnpm --version# Build and start monolith, orders-service, postgres, rabbitmq, HyperDX, frontend-next, nginx proxy…
task start
# Stop everything
task stop
# Rebuild images and restart
task restartWhen the stack is up:
| Component | URL |
|---|---|
| Storefront (nginx → Next.js) | http://localhost |
| Next.js (direct) | http://localhost:3000 |
| Spring Boot API | http://localhost:8080 |
| Swagger UI | http://localhost:8080/swagger-ui.html |
| OpenAPI JSON | http://localhost:8080/api-docs |
| Actuator root | http://localhost:8080/actuator |
| Modulith info | http://localhost:8080/actuator/modulith |
| HyperDX UI | http://localhost:8081 |
| RabbitMQ console | http://localhost:15672 (guest/guest) |
| Hazelcast Management Center | http://localhost:38080 |
Provide PostgreSQL and RabbitMQ locally, then run:
export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/postgres
export SPRING_RABBITMQ_HOST=localhost
./mvnw spring-boot:runcd frontend-next
pnpm install
pnpm dev # serves on http://localhost:3000Enable the backend dev profile (SPRING_PROFILES_ACTIVE=dev) so CORS allows http://localhost:3000. Make sure frontend fetches include credentials: 'include' to forward the BOOKSTORE_SESSION cookie.
- Backend build:
./mvnw -ntp clean verify - Formatting:
./mvnw spotless:apply - Load tests:
k6 run k6.js(supports overridingBASE_URL) - Modulith documentation:
./mvnw testregeneratestarget/spring-modulith-docs/ - Frontend:
pnpm test(unit),pnpm test:e2e(Playwright afterpnpm build && pnpm start)
src/main/java/com/sivalabs/bookstore/
├── common/ # shared helpers, cache utilities, session support
├── catalog/ # product domain + REST API + Liquibase changelog
├── orders/ # order domain, cart REST, gRPC adapters, Hazelcast MapStore
├── inventory/ # inventory projections & cache configuration
├── notifications/ # domain event consumers
└── config/ # infrastructure wiring (Hazelcast, OTEL, gRPC, CORS, sessions)
src/main/resources/db/migration/ # Liquibase change sets (schemas: catalog, orders, inventory)
frontend-next/ # Next.js storefront
frontend-sdk/ # Generated TypeScript API client
- Liquibase change sets reside in
src/main/resources/db/migration/. - Schemas:
catalog,orders,inventory, pluseventsfor Modulith event persistence. - Apply locally with
./mvnw liquibase:update(uses Spring datasource properties).
- Port conflicts: adjust mappings in
compose.yml(notably 80, 3000, 8080, 4317, 15672, 38080). - Session issues: confirm
BOOKSTORE_SESSIONcookie is issued and requests include credentials; inspect/api/cartlogs for session reuse. - CORS failures in dev: ensure
SPRING_PROFILES_ACTIVE=devor overridecors.allowed-originsto your frontend origin. - Missing traces: verify the
hyperdxcontainer is healthy and OTLP env vars (OTLP_ENDPOINT,OTLP_GRPC_HEADERS_AUTHORIZATION) are configured. - Orders service offline: fall back to the in-process gRPC server by setting
bookstore.grpc.client.target=localhost:9091.
Refer to the markdown files under docs/ for historical context, API deep dives, and module-specific analyses.