Files
Adrien d8bcdce879 Squashed commit of the following:
commit 0d624137c2557c6eeb87020749e4977b821c2b5c
Author: Adrien <adrien.cesaro@proton.me>
Date:   Thu Apr 9 11:55:22 2026 +0200

    backend native image setup
2026-04-09 12:05:02 +02:00

11 KiB
Raw Permalink Blame History

Tasks: Native Image Deployment

Input: Design documents from /specs/005-native-image-deployment/ Prerequisites: plan.md , spec.md , research.md , data-model.md , contracts/

Tests: No test tasks — this feature has no unit/integration test requirements in the spec. Smoke testing is part of US1 implementation (T010, T011).

Organization: Tasks grouped by user story for independent implementation and verification.

Format: [ID] [P?] [Story] Description

  • [P]: Can run in parallel (different files, no incomplete-task dependencies)
  • [Story]: Which user story this task belongs to (US1, US2, US3)
  • Paths use backend/ and repo root (web app structure per constitution)

Phase 1: Setup (Shared Infrastructure)

Purpose: Verify prerequisites and baseline before touching any build files.

  • T001 Verify GraalVM 25 CE or Oracle GraalVM 25 is installed: run native-image --version and confirm output shows GraalVM 25; document install command (sdk install java 25-graalce) if missing
  • T002 [P] Verify baseline JVM build passes: run mvn package -DskipTests inside backend/ and confirm target/ai-teacher-backend-*.jar is produced

Checkpoint: GraalVM available, existing JVM build green — safe to modify build files


Phase 2: Foundational (Blocking Prerequisites)

Purpose: Add AOT processing and native compilation support to the Maven build. These changes are prerequisites for all user stories.

⚠️ CRITICAL: US1 cannot start until T007 confirms the native executable is produced.

  • T003 Add <profile id="native"> skeleton to backend/pom.xml and wire spring-boot-maven-plugin process-aot goal to the prepare-package phase inside that profile
  • T004 Add native-maven-plugin 0.10.6 to the native profile in backend/pom.xml with executions add-reachability-metadata and compile-no-fork (phase package), image name ai-teacher-backend, and build args --initialize-at-build-time=org.slf4j and -H:+ReportExceptionStackTraces
  • T005 [P] Create backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java implementing RuntimeHintsRegistrar: register resource pattern org/apache/pdfbox/resources/** and reflection for org.apache.pdfbox.pdmodel.font.encoding.GlyphList with MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS
  • T006 Add @ImportRuntimeHints(NativeHintsConfig.class) annotation to the main @SpringBootApplication class in backend/src/main/java/com/aiteacher/
  • T007 Run mvn -Pnative package -DskipTests inside backend/ and confirm target/ai-teacher-backend native executable is produced; fix any AOT compilation errors before proceeding

Checkpoint: target/ai-teacher-backend exists and exits cleanly — US1 implementation can begin


Phase 3: User Story 1 — Build Native Docker Image (Priority: P1) 🎯 MVP

Goal: One command (mvn -Pnative package jib:dockerBuild) produces a local Docker image containing the native backend. Container starts in < 1 s and all REST endpoints work.

Independent Test: Start native container with DB env vars → GET /api/v1/books returns 200 → startup log shows ready in under 1 second.

Implementation for User Story 1

  • T008 [US1] Add jib-maven-plugin 3.4.5 to backend/pom.xml <build><plugins> section (default build, not inside native profile): configure <from><image>gcr.io/distroless/base-nossl-debian12</image></from>, <to><image>ai-teacher-backend</image></to>, exposed port 8080, and <pluginExtensions> referencing com.google.cloud.tools.jib.maven.extension.nativeimage.JibNativeImageExtension; add jib-native-image-extension-maven 0.1.0 as plugin <dependency>
  • T009 [US1] Run mvn -Pnative package jib:dockerBuild -DskipTests inside backend/ and confirm docker images | grep ai-teacher-backend shows the produced image
  • T010 [US1] Start the native container with required env vars (SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORD, OPENAI_API_KEY) pointing to a local PostgreSQL; verify startup log shows "Started … in < 1.0 seconds" and GET /api/v1/books returns HTTP 200
  • T011 [US1] Run full smoke test against the running native container: POST /api/v1/books (PDF upload), POST /api/v1/chat (RAG query), GET /api/v1/chat/history, and HTTP Basic auth enforcement; for each MissingResourceException or ClassNotFoundException found, add the corresponding entry to backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java and rebuild (T009 → T010 loop) until all pass

Checkpoint: Native Docker image starts < 1 s, all 5 endpoints work — US1 complete


Phase 4: User Story 2 — JVM Mode Preserved (Priority: P2)

Goal: Default Maven build and existing Dockerfile continue to work with no changes required from developers who do not have GraalVM installed.

Independent Test: mvn package -DskipTests produces a fat-jar; docker build using backend/Dockerfile produces a working JVM container.

Implementation for User Story 2

  • T012 [P] [US2] Verify mvn package -DskipTests (no -Pnative) inside backend/ still produces target/ai-teacher-backend-*.jar; confirm the jar starts correctly with java -jar
  • T013 [P] [US2] Verify docker build -t ai-teacher-backend-jvm backend/ using the existing backend/Dockerfile succeeds and the resulting JVM container starts and serves GET /api/v1/books correctly

Checkpoint: JVM path fully unchanged — US2 complete


Phase 5: User Story 3 — Docker Compose Full Stack (Priority: P3)

Goal: docker compose -f docker-compose.native.yml up starts PostgreSQL + native backend together so developers can run the complete native stack locally with a single command.

Independent Test: docker compose -f docker-compose.native.yml up → all services healthy → GET http://localhost:8080/api/v1/books returns 200.

Implementation for User Story 3

  • T014 [US3] Create docker-compose.native.yml at repo root: include postgres service (same image and config as docker-compose.yml), add backend service using image ai-teacher-backend:latest with all required env vars sourced from .env, depends_on postgres with health-check condition, and port mapping 8080:8080
  • T015 [P] [US3] Create .env.example at repo root listing all env vars needed by the native stack (SPRING_DATASOURCE_URL, SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORD, OPENAI_API_KEY, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION) with placeholder values and inline comments

Checkpoint: Full native stack starts via docker compose — US3 complete


Phase 6: Polish & Cross-Cutting Concerns

Purpose: Constitution IV gate (README must be updated in the same PR as architectural change) and final documentation alignment.

  • T016 Update README.md at repo root: add a "Native Image Build" section with GraalVM install command (sdk install java 25-graalce), build command (mvn -Pnative package jib:dockerBuild), and run command (docker compose -f docker-compose.native.yml up); update the Mermaid architecture diagram to show the native build pipeline (Maven native profile → GraalVM native-image → Jib → Docker image)
  • T017 [P] Update specs/005-native-image-deployment/quickstart.md with any corrections or additions discovered during implementation (env var names, exact commands, troubleshooting entries)

Dependencies & Execution Order

Phase Dependencies

  • Setup (Phase 1): No dependencies — start immediately
  • Foundational (Phase 2): Requires Phase 1 completion — BLOCKS all user stories
  • US1 (Phase 3): Requires Phase 2 (T007 must pass) — primary deliverable
  • US2 (Phase 4): Requires Phase 2 (T003T006 must not break JVM path) — can run in parallel with US1 after Phase 2
  • US3 (Phase 5): Requires US1 completion (T009 must produce the Docker image)
  • Polish (Phase 6): Requires US1, US2, US3 all complete

User Story Dependencies

  • US1 (P1): Unblocked after Foundational — critical path
  • US2 (P2): Unblocked after Foundational — can run in parallel with US1 (different verification commands, no file conflicts)
  • US3 (P3): Depends on US1 (needs the Docker image to exist for docker-compose)

Within US1

T008 (Jib config) → T009 (build image) → T010 (verify startup) → T011 (smoke test + hint fixes, may loop back to T009)

Parallel Opportunities

  • T001 and T002 (Phase 1): both can run in parallel
  • T003/T004 (pom.xml edits) are sequential (same file); T005 (new Java file) can run in parallel with T003/T004
  • T012 and T013 (US2 verification): parallel, different commands
  • T014 and T015 (US3 docker-compose + .env.example): parallel, different files
  • T016 and T017 (Polish): parallel, different files
  • US1 (Phase 3) and US2 (Phase 4) can run in parallel after Foundational completes

Parallel Example: Foundational Phase

# In parallel — different files, no dependencies between them:
Task T005: "Create NativeHintsConfig.java in backend/src/main/java/com/aiteacher/config/"

# Sequential — same file (pom.xml):
Task T003: "Add native profile skeleton with spring-boot-maven-plugin process-aot"
Task T004: "Add native-maven-plugin 0.10.6 to native profile"  # after T003

Parallel Example: After Foundational Completes

# US1 and US2 can start simultaneously:
Developer A → Phase 3 (US1): T008 → T009 → T010 → T011
Developer B → Phase 4 (US2): T012, T013 (parallel)

Implementation Strategy

MVP First (User Story 1 Only)

  1. Complete Phase 1: Setup (T001, T002)
  2. Complete Phase 2: Foundational (T003T007) — CRITICAL, do not skip T007
  3. Complete Phase 3: US1 (T008T011)
  4. STOP and VALIDATE: Native image starts < 1 s, all endpoints work
  5. This alone satisfies FR-001 through FR-006 and SC-001 through SC-003

Incremental Delivery

  1. Phase 1 + Phase 2 → build toolchain ready
  2. Phase 3 (US1) → native Docker image working (MVP)
  3. Phase 4 (US2) → JVM fallback confirmed
  4. Phase 5 (US3) → full native stack via docker compose
  5. Phase 6 (Polish) → README updated (constitution IV gate — required before merge)

Notes

  • [P] tasks operate on different files or independent commands — safe to run concurrently
  • US2 (T012, T013) can be verified at any time after Phase 2; they are quick sanity checks
  • The hint-fix loop in T011 is the most likely source of iteration — budget extra time
  • Constitution IV gate (README update, T016) is mandatory — PR cannot merge without it
  • .env.example (T015) should be committed; actual .env must be in .gitignore