This sibling application validates minimal-java-orm against authentication and profile following slices of the open-source RealWorld API.
It intentionally keeps its domain model free of ORM, JDBC, web, and dependency
injection types. The UserRepository and FollowRepository contracts each have
two implementations:
- ORM adapters using minimal-java-orm;
- direct JDBC adapters as the comparison baseline.
domain/ model/ holds plain models and repository ports; application/ holds services.
persistence/ ORM and raw-JDBC adapters.
http/ Javalin routes, JSON, BCrypt passwords, and JWT authentication.
app/ HikariCP, PostgreSQL, configuration, and explicit wiring.
The build uses a Gradle composite to consume ../minimal-java-orm directly during
development.
ORM adapters run their callbacks through a small persistence scope. Direct and transactional scopes share one exception-translating decorator, keeping JDBC transaction control, SQL-state inspection, and repeated exception wrapping out of the repository implementations.
The domain model uses Lombok @Data, generated constructors, and
@FieldNameConstants. Lombok is compile-only and does not appear in the domain
artifact's runtime dependency graph. Persistence code uses constants such as
User.Fields.email and Follow.Fields.followerId when addressing mapped Java
fields. Follow, Tag, and Favorite are mapped as ordinary relationship
entities with surrogate IDs. Their relationship arguments follow the table verb:
Follow(id, followerId, followedId), Tag(id, articleId, name), and
Favorite(id, userId, articleId).
Comment is an ordinary entity rather than a relationship assertion:
Comment(id, articleId, authorId, body, createdAt, updatedAt).
POST /api/usersPOST /api/users/loginGET /api/userPUT /api/userGET /api/profiles/{username}POST /api/profiles/{username}/followDELETE /api/profiles/{username}/followPOST /api/articlesGET /api/articles/feedGET /api/articlesGET /api/articles/{slug}PUT /api/articles/{slug}DELETE /api/articles/{slug}POST /api/articles/{slug}/favoriteDELETE /api/articles/{slug}/favoritePOST /api/articles/{slug}/commentsGET /api/articles/{slug}/commentsDELETE /api/articles/{slug}/comments/{commentId}GET /api/tags
Article lists accept limit and offset; articlesCount remains the total
number of rows matching the author, tag, and favorited filters.
The pinned upstream authentication and profile Hurl files are stored in
contract/.
sdk env
./gradlew testRepository and HTTP integration tests use Testcontainers PostgreSQL and automatically skip when Docker is unavailable.
To execute the unchanged upstream Hurl contract against both adapters:
./scripts/run-contracts.sh allPass orm or jdbc instead of all to run only one adapter.
Start PostgreSQL:
docker compose up -d --wait postgresStart the application:
REALWORLD_JWT_SECRET=local-development-secret \
REALWORLD_PERSISTENCE=orm \
./gradlew :app:runSet REALWORLD_PERSISTENCE=jdbc to use the baseline adapter.
The deterministic ORM-versus-JDBC single-Article read benchmark is documented in
benchmark/README.md. Run the full comparison with:
sdk env
./scripts/run-benchmark.sh allConfiguration:
| Variable | Default |
|---|---|
REALWORLD_PERSISTENCE |
orm |
REALWORLD_DB_URL |
jdbc:postgresql://localhost:5432/realworld |
REALWORLD_DB_USER |
realworld |
REALWORLD_DB_PASSWORD |
realworld |
REALWORLD_DB_POOL_SIZE |
10 |
PORT |
8080 |
REALWORLD_JWT_SECRET |
required |
Licensed under the Apache License 2.0. The copied RealWorld contract files and dependency attributions are documented in Third-Party Notices.