Architecture

This project is a modular monolith: one deployable Spring Boot application with separately owned business modules. The design keeps operational complexity low while making module boundaries visible and testable.

Module Responsibilities

shared-kernel contains only small shared abstractions:

catalog owns product data and stock:

orders owns order placement and order lookup:

payment owns payment attempts:

ecommerce-app composes the runtime:

Dependency Direction

flowchart TD
    App[ecommerce-app] --> Catalog[catalog]
    App --> Orders[orders]
    App --> Payment[payment]
    Catalog --> Kernel[shared-kernel]
    Orders --> Kernel
    Payment --> Kernel
    Orders -->|StockReservationService| Catalog
    Payment -->|OrderPlacedEvent contract| Orders

Rules enforced with ArchUnit:

Event Flow

sequenceDiagram
    participant Client
    participant Orders
    participant Catalog
    participant EventPublisher
    participant Payment

    Client->>Orders: POST /api/orders
    Orders->>Orders: check Idempotency-Key
    Orders->>Catalog: reserveStock(productId, quantity)
    Catalog-->>Orders: stock reserved
    Orders->>Orders: persist order
    Orders->>EventPublisher: publish OrderPlacedEvent
    EventPublisher-->>Payment: after commit event listener
    Payment->>Payment: persist payment attempt
    Orders-->>Client: 201 Created

Payment listens after the order transaction commits. That means a failed order cannot trigger payment processing.

If a client retries order placement with the same Idempotency-Key and identical product and quantity, orders returns the existing aggregate and skips stock reservation and event publication. If the same key is reused for a different request, orders rejects it with a conflict response.

CQRS Light

The catalog module separates command and query paths without adding a second database:

This is intentionally pragmatic. It improves clarity and caching without introducing distributed read models or eventual consistency.

REST Mapping

REST DTOs are separated from application models. MapStruct generates the boundary mappers during Maven compilation:

This keeps mapping code explicit and type-checked without hand-written boilerplate.

API Documentation

springdoc-openapi scans the Spring MVC controllers at runtime and exposes:

OpenAPI metadata, matched paths, Swagger UI path, and API grouping are configured in ecommerce-app/src/main/resources/openapi.yaml.

The unified CI workflow uses the Maven generate-openapi profile to export the OpenAPI JSON into ecommerce-app/target/generated-docs/openapi.json, then publishes it to GitHub Pages.

The published Pages site exposes both a static Swagger UI at /openapi/ and the raw specification at /openapi/openapi.json.

Persistence

Flyway owns the PostgreSQL schema. Hibernate runs with:

spring:
  jpa:
    hibernate:
      ddl-auto: validate

The initial migration creates: