Skip to content

Repository files navigation

Minimal Java ORM — RealWorld validation

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.

Modules

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).

Implemented RealWorld endpoints

  • POST /api/users
  • POST /api/users/login
  • GET /api/user
  • PUT /api/user
  • GET /api/profiles/{username}
  • POST /api/profiles/{username}/follow
  • DELETE /api/profiles/{username}/follow
  • POST /api/articles
  • GET /api/articles/feed
  • GET /api/articles
  • GET /api/articles/{slug}
  • PUT /api/articles/{slug}
  • DELETE /api/articles/{slug}
  • POST /api/articles/{slug}/favorite
  • DELETE /api/articles/{slug}/favorite
  • POST /api/articles/{slug}/comments
  • GET /api/articles/{slug}/comments
  • DELETE /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/.

Build and test

sdk env
./gradlew test

Repository 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 all

Pass orm or jdbc instead of all to run only one adapter.

Run locally

Start PostgreSQL:

docker compose up -d --wait postgres

Start the application:

REALWORLD_JWT_SECRET=local-development-secret \
REALWORLD_PERSISTENCE=orm \
./gradlew :app:run

Set REALWORLD_PERSISTENCE=jdbc to use the baseline adapter.

Benchmark

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 all

Configuration:

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

License

Licensed under the Apache License 2.0. The copied RealWorld contract files and dependency attributions are documented in Third-Party Notices.

About

RealWorld validation project for minimal-java-orm

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages