All work

Case study 02 · EAV Labs

EAV Dispatch

A Spring Boot logistics API for shipments, drivers, vehicles, assignments, controlled delivery lifecycles and auditable dispatch workflows.

Role
Backend engineering and system design
Period
2026
Stack
Java 17 · Spring Boot 4.1 · Spring Data JPA · PostgreSQL 17 · Flyway · Testcontainers · Docker · Caddy · OCI · CodeQL
Links
Live API (opens in a new tab)API docs (opens in a new tab)Readiness check (opens in a new tab)Repository (opens in a new tab)
Swagger UI for the EAV Dispatch API at dispatch.env.pm, showing the API title, the production server and the vehicle endpoints.
01 · Context

Rules that live in the system, not in memory

I wanted a backend where the hard rules of dispatch live in code and in the database rather than in people’s heads: who is assigned to what, what state a shipment is in, and what happened along the way.

02 · Problem

Double-booking and lost history

Dispatch teams juggle drivers, vehicles and shipments under time pressure. When assignments live in chats and spreadsheets, a driver gets booked twice, statuses drift, and nobody can say exactly what happened to a delivery.

03 · My role

What I did

I designed the domain model and lifecycle rules, built the API with Spring Boot, owned the schema through Flyway migrations, and wrote concurrency tests against real PostgreSQL with Testcontainers. Authentication and multi-tenancy are deliberately left for after the MVP.

04 · Approach

One deployable, clear boundaries

A modular monolith: shipment workflow, drivers, vehicles, persistence and HTTP concerns sit in explicit package boundaries while staying one deployable application. PostgreSQL is the source of truth.

Deployment architecture, from the project README and deploy workflow.
05 · Key decisions

Decisions that shaped it

Decision 01

No double-booking, enforced in the database

Assignment checks use database locking plus active-assignment queries, verified with concurrent PostgreSQL Testcontainers tests.

Why: Rules enforced only in application code can be raced; database locking makes the guarantee hold under concurrent requests. The trade-off is more careful transactions, and tests that exercise real concurrency.

Decision 02

A controlled shipment lifecycle

Shipments move ASSIGNED → IN_TRANSIT → DELIVERED; terminal states reject further mutation and hard deletion.

Why: A small, explicit set of states makes every shipment’s status unambiguous and keeps finished deliveries trustworthy. The trade-off is that corrections need their own deliberate flows rather than edits.

Decision 03

Schema owned by migrations

Flyway owns schema changes (V1–V4) and Hibernate only validates mappings.

Why: Migrations make every schema change reviewable and repeatable in each environment. Letting Hibernate only validate means the code can never quietly change the database.

Decision 04

Auditable by default

Every lifecycle change is recorded in a chronological audit history with retention safeguards.

Why: When something goes wrong, the history answers who changed what, and when. The cost is more data to keep, which the retention safeguards hold in check.

06 · Result

Running, verified and released

Deployed from main to an OCI ARM64 VM after CI succeeds, with readiness verified locally and publicly. CI covers Maven verification, Testcontainers, a Compose smoke test and CodeQL; releases publish semantic-versioned images.

GitHub Actions workflow runs for EAV Dispatch: CI, security checks, a container smoke test and Deploy to OCI.
GitHub Actions on main: CI, security checks, a container smoke test and Deploy to OCI.
The live readiness probe of EAV Dispatch at dispatch.env.pm reporting status UP.
The public readiness probe, /actuator/health/readiness.

Known limits

  • Authentication and role-based authorisation are post-MVP.
  • Multi-tenant organisation boundaries are post-MVP.