Welcome to the Track Day Legends API! This is an elegant, high-performance, and robust REST API designed for petrolheads, track day enthusiasts, and amateur racing drivers. It manages a technical catalog of iconic sports cars (CarModel) and their mechanical variants or specifications (EngineSpec).
The project is built following strict Hexagonal Architecture (Ports and Adapters) and Domain-Driven Design (DDD) principles to ensure maximum maintainability, scalability, and testability:
- Pure Domain (
domain): Houses the core business entities (CarModelandEngineSpec) completely decoupled from Spring, JPA, Jackson, or other infrastructure frameworks. - Application (
application): Defines use cases (inbound portsinand outbound portsout) and implements business logic as pure Java POJOs. - Persistence Adapter (
adapter.outbound.persistence): Interacts with Spring Data JPA over an H2 in-memory database, utilizing explicit manual mappers to prevent leaking JPA entities into the domain. - Web Adapter (
adapter.inbound.web): Implements clean REST controllers, declarative request validation DTOs, manual mappers, and a centralizedGlobalExceptionHandler. - Infrastructure (
infrastructure): Configures manual Spring Beans viaBeanConfigurationand loads rich, realistic demo data withDemoDataLoader.
Design Note: Lombok and MapStruct are intentionally omitted in favor of explicit, standard, and highly readable Java 21 code.
Track-Day-Legends-API/
βββ pom.xml
βββ README.md
βββ src/
βββ main/
β βββ java/
β β βββ com/
β β βββ example/
β β βββ trackdaylegends/
β β βββ TrackDayLegendsApplication.java
β β βββ domain/
β β β βββ exception/ (Domain exceptions)
β β β βββ model/ (Pure models: CarModel, EngineSpec)
β β βββ application/
β β β βββ port/ (Inbound & Outbound ports)
β β β βββ usecase/ (Pure Java service/use case implementations)
β β βββ adapter/
β β β βββ inbound/web/ (REST controllers, DTOs, mappers, handlers)
β β β βββ outbound/persistence/ (JPA Entities, repositories, adapters)
β β βββ infrastructure/
β β βββ config/ (Manual Spring Bean wiring)
β β βββ bootstrap/ (Initial demo data loader)
β βββ resources/
β βββ application.properties
βββ test/ (Unit & Integration tests using JUnit 5 + Mockito)
- Java 21 or higher installed.
- Maven 3.8+ installed.
- Navigate to the root directory of the project:
cd Track-Day-Legends-API - Build the project and run the tests:
mvn clean test - Run the Spring Boot application:
mvn spring-boot:run
- The server will start on port
8080. - Access the H2 Database Web Console at:
- URL: http://localhost:8080/h2-console
- JDBC URL:
jdbc:h2:mem:trackdaydb - Username:
sa - Password: (leave empty)
| Method | Endpoint | Description | Optional Filter Parameters |
|---|---|---|---|
| GET | /api/car-models |
List all car models | brand, year, segment, active |
| GET | /api/car-models/{id} |
Retrieve a model with all its engine specs | |
| POST | /api/car-models |
Create a new car model | (JSON Payload) |
| PUT | /api/car-models/{id} |
Update an existing car model | (JSON Payload) |
| PATCH | /api/car-models/{id}/deactivate |
Deactivate a car model and its specs | |
| DELETE | /api/car-models/{id} |
Delete a car model |
| Method | Endpoint | Description | Optional Filter Parameters |
|---|---|---|---|
| GET | /api/engine-specs |
List all engine specifications | engineType, minHorsepower, maxZeroToHundred, drivetrain, active |
| GET | /api/engine-specs/{id} |
Retrieve an engine spec by ID | |
| POST | /api/car-models/{carModelId}/engine-specs |
Create an engine spec for a specific car model | (JSON Payload) |
| PUT | /api/engine-specs/{id} |
Update an engine specification | (JSON Payload) |
| PATCH | /api/engine-specs/{id}/deactivate |
Deactivate an engine specification | |
| DELETE | /api/engine-specs/{id} |
Delete an engine specification |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/engine-specs/search/fastest |
Retrieve top 5 fastest engine specs by 0-100 km/h acceleration |
| GET | /api/engine-specs/search/most-powerful |
Retrieve top 5 most powerful engine specs by horsepower |
| GET | /api/stats/summary |
Global database dashboard statistics (totals, averages, fastest 0-100, etc.) |
| GET | /api/health/demo |
Quick API diagnostic health check |
curl -X GET "http://localhost:8080/api/car-models?brand=Toyota" -H "Accept: application/json"curl -X GET "http://localhost:8080/api/car-models/1" -H "Accept: application/json"curl -X POST "http://localhost:8080/api/car-models" \
-H "Content-Type: application/json" \
-d '{
"brand": "Alpine",
"model": "A110 R",
"year": 2024,
"segment": "S",
"bodyStyle": "Coupe",
"country": "France",
"description": "The hardcore, 100% track-focused edition of the legendary Alpine A110, extensively utilizing carbon fiber to shed weight."
}'curl -X POST "http://localhost:8080/api/car-models/9/engine-specs" \
-H "Content-Type: application/json" \
-d '{
"versionName": "A110 R Turini",
"engineType": "Turbo Petrol",
"engineConfiguration": "1.8L L4",
"displacementCc": 1798,
"horsepowerHp": 300,
"torqueNm": 340,
"transmission": "7-speed dual-clutch EDC",
"drivetrain": "RWD",
"zeroToHundredSeconds": 3.9,
"topSpeedKph": 285,
"fuelConsumptionL100": 7.0
}'curl -X GET "http://localhost:8080/api/engine-specs/search/most-powerful" -H "Accept: application/json"curl -X GET "http://localhost:8080/api/stats/summary" -H "Accept: application/json"curl -X POST "http://localhost:8080/api/car-models" \
-H "Content-Type: application/json" \
-d '{
"brand": "",
"model": "Invalid",
"year": 1945,
"segment": "C"
}'Expected 400 Bad Request Response:
{
"timestamp": "2026-07-07T22:22:38.123456",
"status": 400,
"error": "Bad Request",
"message": "Validation error in request payload",
"path": "/api/car-models",
"validationErrors": {
"brand": "Brand cannot be empty",
"year": "Year must be at least 1950"
}
}Enjoy burning virtual rubber with the Track Day Legends API! ποΈπ¨
docker build -t ghcr.io/robertol18/track-day-legends-api:1.0.0 .docker push ghcr.io/robertol18/track-day-legends-api:1.0.0helm/track-day-legends/values-private.yamlis a local-only override file ignored by Git.helm/track-day-legends/create-ghcr-secret.local.shis a local-only script ignored by Git.
Update GHCR_TOKEN in helm/track-day-legends/create-ghcr-secret.local.sh and run the script for the target namespace. The existing ghcr-pull secret will be created or updated in place.
chmod +x ./helm/track-day-legends/create-ghcr-secret.local.sh
./helm/track-day-legends/create-ghcr-secret.local.sh track-day-legends-devhelm upgrade --install track-day-legends ./helm/track-day-legends \
-f ./helm/track-day-legends/values.yaml \
-f ./helm/track-day-legends/values-dev.yaml \
-f ./helm/track-day-legends/values-private.yaml \
--namespace track-day-legends-dev \
--create-namespace./helm/track-day-legends/create-ghcr-secret.local.sh track-day-legends-tst
helm upgrade --install track-day-legends ./helm/track-day-legends \
-f ./helm/track-day-legends/values.yaml \
-f ./helm/track-day-legends/values-tst.yaml \
-f ./helm/track-day-legends/values-private.yaml \
--namespace track-day-legends-tst \
--create-namespacehelm dependency build ./helm/observability
helm upgrade --install track-day-observability ./helm/observability \
-f ./helm/observability/values.yaml \
-f ./helm/observability/values-dev.yaml \
--namespace track-day-legends-dev \
--create-namespacehelm dependency build ./helm/observability
helm upgrade --install track-day-observability ./helm/observability \
-f ./helm/observability/values.yaml \
-f ./helm/observability/values-tst.yaml \
--namespace track-day-legends-tst \
--create-namespace- OpenTelemetry endpoint consumed by the API chart:
http://otel-collector:4318 - Metrics pipeline:
- App metrics (
/actuator/prometheus) scraped by Prometheus - OTEL metrics exported to Mimir (
/api/v1/push)
- App metrics (
- Logs pipeline:
- OTEL logs exported by Collector to Loki (
/otlp)
- OTEL logs exported by Collector to Loki (
- Traces pipeline:
- OTEL traces exported by Collector to Tempo (OTLP gRPC
:4317)
- OTEL traces exported by Collector to Tempo (OTLP gRPC
- Grafana preprovisions datasources for Prometheus, Mimir, Loki, and Tempo.
- Do not store real secrets in
values*.yaml. - Do not store real registry credentials in Git; keep them only in
create-ghcr-secret.local.sh, which is ignored by Git. - If a GHCR token is exposed, revoke it in GitHub, replace
GHCR_TOKENin the local script, and run it again. - Use
secret.existingSecret(or External Secrets/Sealed Secrets) in tst. - Kubernetes probes are mapped to Spring Boot Actuator endpoints:
/actuator/health/liveness/actuator/health/readiness