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

214 lines
9.2 KiB
Markdown

# Implementation Plan: Native Image Deployment
**Branch**: `005-native-image-deployment` | **Date**: 2026-04-07 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/005-native-image-deployment/spec.md`
## Summary
Add a `native` Maven profile to the backend that compiles the Spring Boot 4.0.5 application to a
GraalVM 25 native executable, then packages it into a minimal Docker image via Jib. The JVM build
(default profile + existing Dockerfile) remains unchanged. A companion `docker-compose.native.yml`
enables local full-stack testing with the native image. README.md is updated with build
instructions and an updated architecture diagram.
## Technical Context
**Language/Version**: Java 25 (backend), TypeScript / Node 20 (frontend)
**Primary Dependencies**: Spring Boot 4.0.5, Spring AI 2.0.0-M4, `native-maven-plugin` 0.10.6,
`jib-maven-plugin` 3.4.5, `jib-native-image-extension-maven` 0.1.0
**Storage**: PostgreSQL 16 + pgvector (unchanged)
**Testing**: Spring Boot Test / JUnit 5 (unchanged); native integration test via smoke-test on
produced Docker image
**Target Platform**: Linux x86_64 container (Docker), GraalVM 25 CE or Oracle GraalVM 25
**Project Type**: Web application — backend API + frontend client (Option 2 structure)
**Performance Goals**: Backend container starts in < 1 s; idle RSS < 150 MB
**Constraints**: Native profile is opt-in; JVM mode unchanged; no new runtime dependencies;
cross-compilation out of scope
**Scale/Scope**: Single backend deployable unit (1 Docker image); frontend unchanged
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
| Principle | Status | Notes |
|-----------|--------|-------|
| I. KISS | ✅ Pass | Build-time change only; no new runtime layers or abstractions. |
| II. Easy to Change | ✅ Pass | `native` profile is additive; default JVM profile unchanged. Native hints isolated in one `NativeHintsConfig` class. |
| III. Web-First Architecture | ✅ Pass | REST API contract unchanged. |
| IV. Documentation as Architecture | ⚠️ Required | README.md MUST be updated with native build instructions and updated Mermaid diagram showing the build pipeline. |
| Technology Constraints | ✅ Pass | Still 1 backend + 1 frontend deployable unit; no new services. |
**Post-design re-check**: After Phase 1 — Principle IV gate requires README update in same PR.
## Project Structure
### Documentation (this feature)
```text
specs/005-native-image-deployment/
├── plan.md # This file
├── spec.md # Feature specification
├── research.md # Phase 0 — toolchain & compatibility decisions
├── data-model.md # Phase 1 — no new entities (confirmed)
├── quickstart.md # Phase 1 — developer build guide
├── contracts/
│ └── build-contract.md # Build inputs/outputs & unchanged REST API
└── tasks.md # Phase 2 output (/speckit.tasks — NOT created here)
```
### Source Code (repository root)
```text
backend/
├── pom.xml # +native profile, +Jib plugin, +NativeHintsConfig
├── src/
│ └── main/
│ └── java/com/aiteacher/
│ └── config/
│ └── NativeHintsConfig.java # NEW — RuntimeHints for PDFBox, AWS SDK gaps
└── src/
└── main/
└── resources/
└── META-INF/
└── native-image/ # Optional: manual JSON hints if AOT gaps remain
docker-compose.yml # UNCHANGED (JVM mode)
docker-compose.native.yml # NEW — native image + postgres full stack
README.md # UPDATED — build instructions + Mermaid diagram
```
**Structure Decision**: Follows Option 2 (web application). Only backend is modified; frontend
is untouched. New files are minimal: one Java config class, one docker-compose file.
## Complexity Tracking
> No constitution violations. No entry required.
---
## Implementation Phases
### Phase 1: Maven `native` Profile + Native Maven Plugin
**Goal**: `mvn -Pnative package` produces a native executable `target/ai-teacher-backend`.
**Tasks**:
1. Add `<profile id="native">` to `backend/pom.xml`:
- Wire `spring-boot-maven-plugin` `process-aot` goal to `prepare-package` phase.
- Add `native-maven-plugin` 0.10.6 with `add-reachability-metadata` and `compile-no-fork`
executions.
- Set `<imageName>ai-teacher-backend</imageName>` and add essential build args:
`-H:+ReportExceptionStackTraces`, `--initialize-at-build-time=org.slf4j`.
2. Create `backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java`:
- Implements `RuntimeHintsRegistrar`.
- Registers PDFBox classpath resources: `org/apache/pdfbox/resources/**`.
- Registers PDFBox reflection entries for font/encoding classes.
- Registers AWS SDK `SdkPojo` subtypes used by S3 (discovered iteratively).
- Annotate main application class or a `@Configuration` class with
`@ImportRuntimeHints(NativeHintsConfig.class)`.
3. Validate: run `mvn -Pnative package -DskipTests` and confirm executable is produced.
**Acceptance**: `target/ai-teacher-backend` exists, `./target/ai-teacher-backend --help`
exits cleanly, basic startup reaches DB connection stage.
---
### Phase 2: Jib Plugin — Package Native Image into Docker
**Goal**: `mvn -Pnative package jib:dockerBuild` produces a local Docker image.
**Tasks**:
1. Add `jib-maven-plugin` 3.4.5 to the `<build><plugins>` section (outside native profile,
but configured to package the native executable when the profile is active):
- Add `jib-native-image-extension-maven` 0.1.0 as a plugin dependency.
- Configure `<from><image>gcr.io/distroless/base-nossl-debian12</image></from>`.
- Set `<to><image>ai-teacher-backend</image></to>` (overridable via `-Djib.to.image`).
- Add `<pluginExtensions>` pointing to `JibNativeImageExtension`.
- Set `<container><ports><port>8080</port></ports></container>`.
2. Verify Jib picks up the native executable from `target/ai-teacher-backend` and sets it
as entrypoint.
3. Run `mvn -Pnative package jib:dockerBuild` and confirm:
- Image exists in local Docker: `docker images | grep ai-teacher-backend`.
- Container starts: `docker run --rm -e SPRING_DATASOURCE_URL=... ai-teacher-backend`.
**Acceptance**: Image starts, logs show "Started AiTeacherApplication in < 1.0 seconds",
and `GET /api/v1/books` returns 200 (with DB connected).
---
### Phase 3: Integration Smoke Test
**Goal**: All existing features work in native mode.
**Tasks**:
1. Start PostgreSQL locally (or via existing docker-compose.yml).
2. Start the native container with full environment variables.
3. Verify:
- Flyway migrations run successfully at startup.
- `POST /api/v1/books` (PDF upload + embedding) succeeds.
- `POST /api/v1/chat` (RAG chat) returns a non-empty answer.
- Spring Security HTTP Basic auth is enforced.
4. Fix any `MissingResourceException` or `ClassNotFoundException` by adding entries to
`NativeHintsConfig` and rebuilding.
**Acceptance**: All 5 REST API endpoints respond correctly with native image (FR-005).
---
### Phase 4: Docker Compose for Native Stack
**Goal**: `docker compose -f docker-compose.native.yml up` starts the full stack.
**Tasks**:
1. Create `docker-compose.native.yml` at repo root:
- `postgres` service: same as `docker-compose.yml` (copy).
- `backend` service: uses `ai-teacher-backend:latest` (the Jib-built native image).
- Wires all required env vars via `.env` file reference or inline secrets.
- Adds `depends_on` with health check for postgres.
2. Document the `.env` file format in `quickstart.md`.
**Acceptance**: `docker compose -f docker-compose.native.yml up` → all services healthy →
app responds at `http://localhost:8080/api/v1/books`.
---
### Phase 5: README Update (Constitution IV Gate)
**Goal**: README.md reflects the new build pipeline and updated deployment diagram.
**Tasks**:
1. Add "Native Image Build" section to README.md with:
- Prerequisite: GraalVM 25 install instructions (sdkman command).
- Build command: `mvn -Pnative package jib:dockerBuild`.
- Run command: `docker compose -f docker-compose.native.yml up`.
2. Update the Mermaid architecture diagram in README.md to show:
- Build pipeline: Maven native profile → GraalVM native-image → Jib → Docker image.
- Keep the runtime diagram unchanged (same components, just a lighter container).
**Acceptance**: README.md Mermaid diagram renders correctly on GitHub; build section
contains all commands from `quickstart.md`.
---
## Risk Log
| Risk | Likelihood | Mitigation |
|------|------------|------------|
| Spring AI 2.0.0-M4 missing native hints for some AI response types | Medium | Iterative hint discovery during Phase 3 smoke test |
| PDFBox font loading fails at runtime in native | Medium | Register full `org/apache/pdfbox/resources/**` in `NativeHintsConfig` |
| AWS SDK S3 reflection errors | Medium | Switch to `url-connection-client` (simpler); lazy-init S3Client |
| GraalVM 25 availability in CI | Low | Documented prerequisite; CI pipeline config out of scope (can be added as follow-up) |
| Native build time > 5 min | Low | Acceptable for a first build; not a blocker for POC |