Skip to content

About

Cold chain custody and compliance API. Java 25, Spring Boot 4, Oracle 23ai. Modular monolith with a rule catalogue enforced in CI.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ColdChain

ci

Cold chain custody and compliance API. A pharmaceutical shipment passes from hand to hand between organizations, its temperature is measured for the entire journey, and when it closes it either has a certificate or it doesn't.

Status: the five modules are built and closed. Every rule in the catalogue has a machine that checks it, and the integration suite runs against a real Oracle raised from nothing on every push. The specification lives in docs/specification.md, the decisions in docs/adr/, and the order the work was done in docs/branching-plan.md.

The problem

A batch of vaccines leaves a lab, a carrier picks it up, it stops at an intermediate warehouse, a second carrier picks it up and it arrives at a hospital. Four different organizations have held the box, and none of them takes another's word for it. At the end of the journey somebody has to answer two questions, with evidence:

  1. Did the batch stay within its temperature range?
  2. Who was holding it at any given moment?

This project answers those two questions and nothing else.

Why this domain

The domain was chosen because it forces three problems a CRUD app never has to face:

  • Several organizations see the same shipment without owning it. Visibility across organizations has to be solved without inventing an organization tree — see ADR-003.
  • The verdict is real arithmetic over a time series: cumulative minutes out of band, data coverage. It is not a boolean somebody ticks by hand.
  • Readings arrive in volume, out of order and repeated. Idempotent ingestion, indexes and partitioning stop being decoration.

The five modules

They are numbered by dependency: that is also the build order.

# Module Responsibility
01 identity Organizations, users, roles and scopes. Who is asking, and with what permission.
02 catalog Storage profiles, products and sites. What is shipped and under what conditions.
03 shipment The shipment, its chain of custody and who can see it.
04 telemetry Devices, temperature readings and excursion detection.
05 compliance The certificate, its verdict and the findings that back it.
flowchart LR
  catalog -- "frozen thresholds" --> shipment
  shipment -- "ShipmentDispatched" --> telemetry
  telemetry -- "ExcursionOpened / Closed" --> shipment
  shipment -- "ShipmentClosed" --> compliance
  telemetry -- "series and excursions" --> compliance
  identity -. "org and scopes on every request" .-> shipment
Loading

Modules talk to each other through domain events, synchronous and in-process. No join crosses a module boundary: references go by UUID and reads go through the owner's api/. Each module declares its allowed dependencies in code — @ApplicationModule(allowedDependencies = {...}) — and an import that is not on the list fails the build.

Inside a module the shape is hexagonal: a pure-Java domain model, a repository port it declares, and an adapter implementing it over JPA. The business rules — the state machine, the verdict arithmetic, the excursion detection — are plain objects, so they are unit-tested in milliseconds with no database anywhere near them. That is the point of it, and the reasoning is in ADR-007.

Stack

  • Java 25 (LTS) and Spring Boot 4
  • Oracle Database 23ai Free, schema governed by Flyway alone — no ddl-auto
  • Spring Modulith + ArchUnit for the boundaries, backed by a rule catalogue that runs as a CI gate — see rules/
  • Testcontainers with gvenzl/oracle-free — a real Oracle in the integration tests, not H2 pretending to be Oracle
  • Spring Security with our own JWT, OpenAPI, Docker Compose
  • Every message the API answers with travels as a key and resolves in English, Spanish and Portuguese, chosen by Accept-Language

The reasoning is in ADR-001 and ADR-006.

What this project enforces

Everything from the folder down to the field is declared in rules/project-rules.md — every rule across sixteen groups, each with a severity, the test that checks it, and the state that test is in. It is not a style guide: ./gradlew rules runs it on every push and every pull request, and it has no switch to turn it off.

The catalogue verifies itself, too: a rule that cites a test that does not exist fails the build, a rule still waiting for its test cannot quietly acquire one without moving its row, and a tolerated violation with no owner and no expiry date fails as well. Nothing is left planned: the day a rule was written without a machine is the day the build said so.

Writing those machines is what found the defects worth finding — an audit entry that came back from the database with an identifier that was not its own, two concepts wearing two names each, four columns abbreviated past the point of being guessable, and thirteen aggregates where the second writer erased the first without a trace.

./gradlew rules      # the gate. Everything that does not need Oracle
./gradlew rulesDb    # the rules that can only be checked against a real database

How to run it

cp .env.example .env             # local credentials, never committed
docker compose up -d --wait      # Oracle 23ai Free and the API, on 1521 and 8080
scripts/demo.sh                  # the whole story below, end to end

The stack answers at http://localhost:8080/api, and the contract it serves is browsable at http://localhost:8080/api/swagger-ui.html.

To work on the code, run the API from Gradle against the same database:

docker compose up -d oracle      # only the database
./gradlew bootRun                # Flyway migrates, then the API on 8080
./gradlew test                   # the domain in milliseconds, plus the rule gate
./gradlew integrationTest        # with Testcontainers, requires Docker
./gradlew rebuild                # a database from nothing: migrate, start, run everything

The domain suite is the one that proves the shape is worth its price: 41 tests in 52 milliseconds, with no Spring context and no database anywhere near them — the state machine, the verdict arithmetic, the excursion detection, the temperature band and the custody hash chain. A rule in the catalogue fails the build if a calculator loses its test, or if a domain test reaches for a framework.

The demo

docker compose up -d --wait
scripts/demo.sh                  # needs curl and jq

One run of scripts/demo.sh, against the stack you just raised:

  1. Three organizations register — a laboratory, a carrier and a hospital — and none of them can see anything that is not theirs.
  2. The laboratory declares a 2 – 8 °C profile, a product and a shipment of 400 vials, and dispatches it with a sensor attached. The thresholds are frozen at dispatch: changing the profile afterwards cannot change what this shipment was judged against.
  3. The carrier accepts the handoff with a single-use code and custody changes hands.
  4. The gateway pushes three hours of readings, one every five minutes, with a 45-minute excursion above 8 °C and a 65-minute blackout. The same batch is sent again and comes back REPLAYED: nothing is duplicated.
  5. The hospital takes delivery — and nobody asks for a certificate, because closing the shipment already issued one:
verdict            FAIL
coverage           71.43 % of the expected samples
longest excursion  45 minutes
content hash       04ddb79e045da4d2d57c4d2e056577087e96fcd9eb4e50567028fd82196c96f5
finding            DATA_GAP · CRITICAL · {"coveragePercent":71.43,"minimum":80}
finding            EXCURSION_ABOVE_MAX · CRITICAL · {"durationMinutes":45,"peakCelsius":11.5}

The script is not a narration: it fails if the verdict is not the one written above. It runs in CI on every push, against Oracle raised from nothing, and the OpenAPI document the API served during that run is published as a build artifact.

Documentation

License

MIT.

About

Cold chain custody and compliance API. Java 25, Spring Boot 4, Oracle 23ai. Modular monolith with a rule catalogue enforced in CI.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages