Development
Prerequisites
- JDK 21 or later
- sbt 1.x
- Docker (required for integration tests)
Project Structure
Oni is a multi-module sbt project:
| Module | Description |
|---|---|
oni |
Main application (server, endpoints, services, entities) |
it |
Database-agnostic integration test helpers |
it-postgres |
Integration tests against PostgreSQL (via Testcontainers) |
it-sqlserver |
Integration tests against SQL Server (via Testcontainers) |
Within the oni module, source is organized as:
oni/src/main/scala/org/mbari/oni/
endpoints/ # Tapir endpoint definitions (one file per API group)
services/ # Business logic and caching
jpa/
entities/ # Hibernate-mapped JPA entities (Java)
repositories/ # Repository interfaces
config/ # AppConfig, JwtConfig, HttpConfig, DatabaseConfig
Main.scala # Entry point
Useful SBT Commands
| Command | Description |
|---|---|
compile |
Compile all modules |
test |
Run unit tests |
itPostgres/test |
Run integration tests against PostgreSQL |
itSqlserver/test |
Run integration tests against SQL Server |
itPostgres/testOnly <test-class> |
Run a single integration test against PostgreSQL |
itSqlserver/testOnly <test-class> |
Run a single integration test against SQL Server |
stage |
Build a runnable staging directory at target/oni/universal/stage |
Docker/stage |
Generate a Dockerfile at target/oni/docker/stage |
docker:publishLocal |
Build a local Docker image |
doc |
Generate Scaladoc + this site to target/docs/site |
scalafmtAll |
Format all source files (Scala 3 indent-based style) |
Running Locally
Start a PostgreSQL instance (Docker is easiest):
docker run -d \
-e POSTGRES_PASSWORD=password \
-e POSTGRES_DB=oni \
-p 5432:5432 \
postgres:16
Then run the application directly from sbt:
sbt "oni/run"
Oni will apply Flyway migrations automatically on first start.
Adding Documentation
Markdown files placed under oni/src/docs/_docs/ are automatically picked up by scaladoc when you run doc. Subdirectories create sections in the generated site.
Architecture Notes
Startup Sequence
Main.scala performs the following on startup:
- Forces JVM timezone to UTC (prevents JDBC timestamp conversion issues)
- Reads configuration from
reference.conf(overridden by environment variables) - Creates the Hibernate
EntityManagerFactoryand runs Flyway migrations - Starts the Vert.x HTTP server with gzip compression and a worker thread pool
- Registers all Tapir endpoint routes, the Swagger UI, and the Prometheus metrics handler
- Attaches request/response logging (DEBUG for all requests, INFO for response timing)
Caching
FastPhylogenyService builds an in-memory tree of the entire knowledgebase using Caffeine and serves phylogeny queries (up/down/taxa/siblings) entirely from cache. The cache is invalidated on any concept write.
Individual JPA entities are also cached at the Hibernate second-level cache layer via the Caffeine JCache integration configured in application.conf.
Authentication Flow
- Client calls
POST /v1/authwithAuthorization: APIKEY <client-secret>orPOST /v1/auth/loginwith credentials - Server returns a signed JWT
- Client includes
Authorization: Bearer <token>on subsequent write requests - Tapir security logic validates the JWT on each protected endpoint
Error Handling
Services return Either[Throwable, T]. Endpoints translate the Left cases to appropriate HTTP status codes. The Vert.x error handler distinguishes client errors from server faults and logs only unexpected 5xx errors at ERROR level.
Testing
Integration tests use Testcontainers to spin up real database containers. No manual database setup is needed — Docker must be running.
Test classes are organized under the it module's shared helpers with database-specific suites in it-postgres and it-sqlserver.