d8bcdce879
commit 0d624137c2557c6eeb87020749e4977b821c2b5c Author: Adrien <adrien.cesaro@proton.me> Date: Thu Apr 9 11:55:22 2026 +0200 backend native image setup
214 lines
9.2 KiB
Markdown
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 |
|