REST API for managing Products, Orders, and Order Items with stock control, built as a backend technical challenge.
- Java 21
- Spring Boot 4.0.2 (Web MVC, Data JPA, Validation, Actuator)
- PostgreSQL 18 with Liquibase migrations
- MapStruct for compile-time DTO mapping
- Lombok for boilerplate reduction
- SpringDoc OpenAPI (Swagger UI)
- Testcontainers (PostgreSQL) for integration tests
- JUnit 5 + Mockito for unit tests
- Docker with multi-stage build and non-root runtime user
Standard Controller - Service - Repository layering. Controllers are thin delegates; business logic lives in the service layer and, where appropriate, in the domain entities themselves.
AbstractCrudController<C, U, R> and MappedCrudService<E, C, U, R> provide type-safe CRUD scaffolding. Concrete controllers and services inherit the full lifecycle (create, findAll,
findByCode, update, delete) and only override what they need.
All entities carry a JPA @Version field. However, Hibernate uses the version from its persistence context snapshot. To enforce true client-side optimistic locking, the service layer performs an explicit version comparison between the DTO and the loaded entity before applying the update.
Stock adjustments use @Lock(PESSIMISTIC_WRITE) via a dedicated findByIdForStockUpdate repository method. This acquires a row-level SELECT ... FOR UPDATElock, preventing concurrent transactions from reading stale stock quantities.
Entities are not anemic data holders. Product owns decreaseStock() and increaseStock() with built-in validation.
Internal numeric IDs are never exposed. Each entity has a database-generated UUID code column (@Generated + insertable = false) that serves as the public identifier in all API
paths and responses.
Order status transitions are validated server-side: PENDING -> PROCESSING | CANCELLED, PROCESSING -> COMPLETED | CANCELLED. Terminal states (COMPLETED, CANCELLED) cannot transition further. Cancellation triggers automatic stock restoration for all line items.
All endpoints return paginated results for list operations (via page, size, sort query params).
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/products |
List all products |
GET |
/api/products/{code} |
Get product by UUID code |
POST |
/api/products |
Create a new product |
PUT |
/api/products/{code} |
Update a product |
DELETE |
/api/products/{code} |
Delete a product |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/orders |
List all orders |
GET |
/api/orders/{code} |
Get order by UUID code |
POST |
/api/orders |
Create order with inline items |
PUT |
/api/orders/{code} |
Update order fields / status |
DELETE |
/api/orders/{code} |
Delete order (restores stock) |
GET |
/api/orders/{code}/order-items |
List items for a specific order |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/order-items |
List all order items |
GET |
/api/order-items/{code} |
Get order item by UUID code |
POST |
/api/order-items |
Add item to existing order |
PUT |
/api/order-items/{code} |
Update order item quantity |
DELETE |
/api/order-items/{code} |
Delete order item |
- Docker and Docker Compose
Create a .env file in the project root:
POSTGRES_DB=ecommerce
POSTGRES_USER=ecommerce
POSTGRES_PASSWORD=ecommercedocker compose up -dThis starts PostgreSQL 16 and the application container. Liquibase runs migrations automatically on startup.
| URL | Description |
|---|---|
http://localhost:8080/actuator/health |
Health check |
http://localhost:8080/swagger-ui.html |
Swagger UI |
http://localhost:8080/v3/api-docs |
OpenAPI JSON spec |
Docker must be running -- Testcontainers spins up a disposable PostgreSQL instance for each test run.
./mvnw test- Unit tests (
*Test.java): Service layer logic with Mockito mocks for repositories and dependencies. - Integration tests (
*IT.java): Full Spring Boot context with real PostgreSQL via Testcontainers.
Pre-built collection and environment files are provided in the postman/ directory.
- Open Postman.
- Import the collection:
postman/Ecommerce-API-Recruiter.postman_collection.json. - Import the environment:
postman/Ecommerce-API-Local.postman_environment.json. - Select the imported environment in the top-right dropdown.
- Confirm
baseUrlis set tohttp://localhost:8080. - Run requests in folder order:
00 - Health & Docs->01 - Products->02 - Orders & OrderItems.
Collection scripts automatically capture and propagate variables (codes, versions) between requests.