Project documentation

Order Platform User Guide

Set up the application, exercise the complete order workflow and understand the architectural and quality constraints that keep this modular monolith credible.

Java 21 Spring Boot 4.1 Spring Modulith 2.1 PostgreSQL 17

Overview

Spring Modulith Order Platform is a production-oriented order management backend. It is deployed as one Spring Boot application while keeping customers, pricing, orders, payments and notifications as explicit business modules.

Spring Modulith verifies allowed module dependencies and named public interfaces. Inside each module, a pragmatic ports-and-adapters approach keeps domain rules independent, lets application services orchestrate use cases and confines JPA and Spring Data details to infrastructure adapters.

Scope: this guide covers operation and architecture. The generated OpenAPI documentation remains the authoritative REST contract.

Prerequisites

The Maven wrapper is versioned with the repository, so a separate Maven installation is not required.

Java
JDK 21 available through JAVA_HOME and the command line.
Containers
Docker Engine or Docker Desktop with Docker Compose v2.
Maven
./mvnw on Unix-like systems or .\mvnw.cmd on Windows.
Port 5432
PostgreSQL host port; configurable through DB_PORT.
Port 8080
Default HTTP port for the Spring Boot application.

Local setup

  1. Start PostgreSQL

    Launch the PostgreSQL 17 service and wait for its health check.

    docker compose up -d
  2. Verify the project

    Generate the API contract, compile, run unit and PostgreSQL integration tests, verify module boundaries and assemble all documentation under target/pages.

    ./mvnw clean verify
  3. Run the application

    Start the verified application against the Compose database. The API listens on port 8080.

    ./mvnw spring-boot:run

On Windows PowerShell, replace ./mvnw with .\mvnw.cmd. The local database name, username and password are all order_platform.

API workflow

The realistic happy path crosses every business module while preserving the distinction between synchronous validation and event-driven follow-up work.

  1. Create customer Registers an active customer and returns its UUID.
  2. Request quote Prices a basket from the seeded catalog.
  3. Create order Validates the customer and products, then submits the order.
  4. Authorize payment Matches the order total and authorizes its pending payment.
  5. Inspect notifications Lists intents created from order and payment events.

The seed catalog includes SKU-COFFEE-MUG at 14.99 EUR, SKU-NOTEBOOK at 19.99 EUR, SKU-DESK-LAMP at 49.99 EUR and SKU-BACKPACK at 89.00 EUR.

API examples

These requests match the field names, paths and payloads in the source OpenAPI contract. Run them after the application starts. The examples use POSIX line continuation. In PowerShell, invoke curl.exe and run each request on one line or replace each trailing backslash with a backtick.

1. Create a customer

A successful request returns 201 Created. Keep the response id for order creation.

curl -i -X POST http://localhost:8080/customers \
  -H "Content-Type: application/json" \
  -d '{
    "email": "ada.lovelace@example.com",
    "fullName": "Ada Lovelace"
  }'

2. Quote the basket

This seeded basket returns a total of 49.97 EUR.

curl -i -X POST http://localhost:8080/pricing/quote \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "productCode": "SKU-COFFEE-MUG", "quantity": 2 },
      { "productCode": "SKU-NOTEBOOK", "quantity": 1 }
    ]
  }'

3. Create the order

Replace <customer-id> with the UUID returned by the first request. A successful request returns 201 Created and an order id.

curl -i -X POST http://localhost:8080/orders \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "<customer-id>",
    "items": [
      { "productCode": "SKU-COFFEE-MUG", "quantity": 2 },
      { "productCode": "SKU-NOTEBOOK", "quantity": 1 }
    ]
  }'

4. Authorize payment

Replace <order-id> with the submitted order UUID. The amount and currency must match the order total.

curl -i -X POST http://localhost:8080/payments/authorize \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "<order-id>",
    "amount": {
      "amount": 49.97,
      "currency": "EUR"
    }
  }'

5. Inspect notifications

The list contains records for the committed order and authorized payment.

curl -i http://localhost:8080/notifications

Validation, missing resources, duplicate customers and business-rule failures use RFC 7807 application/problem+json responses.

Architecture

The modules are direct packages below com.example.orderplatform. Each module uses only the api, application, domain and infrastructure packages it needs.

Customers Owns customer identity and exposes lookup through its named API.
Pricing Owns catalog prices and produces validated basket quotes.
Orders Calls customer and pricing APIs synchronously before submission.
Payments Consumes OrderCreatedEvent and authorizes the pending amount.
Notifications Consumes order and payment events to record notification intents.

Dependency boundaries

  • Named api interfaces are the only synchronous cross-module surface.
  • Domain events handle meaningful post-commit collaboration without a message broker.
  • Domain packages remain independent of Spring, JPA, generated API types and infrastructure.
  • Application services orchestrate use cases through outbound ports where persistence is a real technical boundary.
  • JPA entities, Spring Data repositories and persistence adapters remain private to infrastructure.

Testing and quality

./mvnw clean verify is the project quality gate and the only Maven build executed by CI.

  • Domain unit tests cover business rules and value objects without framework infrastructure.
  • Application service tests verify use-case orchestration, conflicts and module API collaboration.
  • Integration tests use Testcontainers with PostgreSQL for REST behavior, Flyway migrations, persistence and event consumers.
  • Architecture tests run ApplicationModules.verify() and ArchUnit rules for module and package boundaries.
  • JaCoCo generates coverage for non-generated production code.
  • Javadoc and OpenAPI are generated and checked before Pages assembly.
  • Artifact checks fail the build when a required Pages entry point or shared site asset is missing.

H2 is intentionally excluded. Using PostgreSQL in local development and integration tests keeps migration and persistence behavior aligned.

Generated documentation

Maven assembles the complete static site under target/pages. GitHub Pages publishes that directory without running the Spring Boot application or PostgreSQL.

target/pages/
|-- index.html
|-- assets/
|-- user-guide/index.html
|-- openapi/index.html
|-- openapi/openapi.json
|-- javadoc/index.html
`-- jacoco/index.html

Everything under target is generated output and must remain outside Git.

Troubleshooting

Docker or PostgreSQL is not running
Start Docker, run docker compose up -d, then inspect docker compose ps and docker compose logs postgres.
Port 5432 is occupied
Use DB_PORT=15432 docker compose up -d on Unix or set $env:DB_PORT="15432" first in PowerShell. Point spring.datasource.url to the selected host port.
Port 8080 is occupied
Set SERVER_PORT=8081 before starting Spring Boot, or stop the process already bound to port 8080.
Testcontainers cannot start Docker
Confirm the Docker daemon is reachable by the current user and that docker info succeeds before rerunning Maven.
Flyway reports a validation error
Do not rewrite an applied migration. Restore the original migration and add the next versioned migration for schema changes.
Generated documentation is missing
Run ./mvnw clean verify. Do not create or edit files manually under target/pages.
The Maven wrapper is not executable
On Unix-like systems run chmod +x mvnw, then rerun the wrapper command.
Back to top