Aman-Mittal opened a new pull request, #165:
URL: https://github.com/apache/fineract-consumer-facing/pull/165
Fixes #45
This adds an opt-in GraalVM native build of the BFF. The native build passes
the full Cucumber suite, starts in about 0.6 s, and idles under 100 MiB. The
JVM jar stays the default artifact. The PR is based on `main` and does not
depend on #163 or #164.
## Commits
**1. `fix: resolve the Fineract base URL and the audit query toggle at
runtime`**
Two settings were fixed when bean definitions were created. On the JVM that
happens at startup, but under Spring AOT it happens at build time, so a native
image silently ignored the deployment's values:
- **Fineract base URL.** The generated `@FeignClient(url =
"${fineract.client.base-url}")` baked the build machine's URL into all Fineract
clients. A new `FineractBaseUrlInterceptor` sets every request's target from
the runtime property. Feign's `HardCodedTarget` leaves an absolute URL alone,
so the runtime value wins on both JVM and native.
- **Audit query toggle.** `AuditQueryController` used
`@ConditionalOnProperty(consumer.audit.query-enabled)`, so it was dropped at
build time. The endpoint is now always mapped, and `AuditQueryServiceImpl`
returns 404 `error.msg.consumer.audit.query.disabled` while the toggle is off.
A side effect is that the scraped OpenAPI spec is now the same in every
environment.
**2. `feat: build the BFF as a GraalVM native image`**
- **Build.** `./gradlew -Pnative nativeCompile` uses the Native Build Tools
plugin (0.11.5, aligned with Boot 4.0.6). The plugin is applied only with
`-Pnative`, so ordinary `bootJar` and `test` runs gain no AOT processing.
- **`NativeRuntimeHints`.** This registers the reflection that neither
Spring AOT nor the GraalVM reachability metadata repository provides. Each
entry has a comment saying why it is needed, and `NativeRuntimeHintsTest` pins
them. The gaps were found by running the Cucumber suite against the AOT jar
under the native-image tracing agent:
- **Tomcat connector introspection.** Without it the binary fails at
startup.
- **Liquibase's `liquibase.change` model**, scanned at AOT time. Without
it the native image applies identical DDL, verified by diffing `pg_dump
--schema-only`, but computes different changeset checksums. It would then
refuse a database migrated by the JVM build, and the reverse.
- **Spring Security's Jackson modules, mixins and deserializers**, the JDK
collection types stored as `@class` type ids, and the token fields set on
read-back. Without them the whole open banking authorization flow fails,
because `JdbcOAuth2AuthorizationService` cannot read stored authorizations back.
- **Caffeine's generated cache classes** for `OwnedAccountsCache`, and the
array types Hibernate instantiates.
- **Monitoring.** The image is built with `--enable-monitoring=jmxserver`,
because actuator's Micrometer binders use the platform MBean server. This does
not open a remote JMX port.
- **Refresh scope.** `spring.cloud.refresh.enabled=false`. Refresh scope is
unused here and not supported under AOT.
- **Container.** `Dockerfile.native` packages a host-built binary, because
compilation needs about 10 GiB, more than Docker Desktop's VM usually has.
`compose.native.yaml` swaps it in for the `bff` service. Heap is sized from the
container limit with `-XX:MaximumHeapSizePercent=60`, and the process exits on
OOM.
- **CI.** The new `build-native.yml` workflow:
- compiles the image;
- brings up the stack with the native BFF;
- runs the full Cucumber suite;
- writes binary size, startup and memory to the job summary.
- The `graalvm/setup-graalvm` SHA is on the ASF allowlist.
- **Docs.** `docs/consumer/development/native-image.adoc` covers building,
testing, footprint, the AOT pitfalls (build-time conditions and placeholders),
and how to find new reflection gaps with the tracing agent.
## Native-only unit tests
None are added, because Mockito cannot run in a native image
(`graalvmNative.testSupport = false`). The functional test for the native build
is the existing Cucumber suite run against the native container. Code coverage
keeps coming from the JVM run.
## Footprint
Each run is the full Cucumber suite (77 scenarios) against the compose stack
on `main` (`apache/fineract:1.12.1`).
| Build | Container limit | Startup | Idle | Peak during suite | Scenarios |
|---|---|---|---|---|---|
| JVM jar (`-Xmx768m`) | 2 GB | 9.7 s | 726 MiB | 846 MiB | 77/77 |
| Native | 512 MB | 0.6 s | 94 MiB | 120 MiB | 77/77 |
| Native | 256 MB | 0.6 s | 64 MiB | 83 MiB | 77/77 |
The binary is about 220 MiB. The build takes about 2.5 minutes with a peak
of about 10 GiB on 22 cores; expect 10–15 minutes on a 4-core runner.
## Testing
- `./gradlew test`: 489 tests, 0 failures. That includes the new
`FineractBaseUrlInterceptorTest`, `AuditQueryServiceImplTest` and
`NativeRuntimeHintsTest`.
- `./gradlew cucumber` against the JVM build: 77/77.
- `./gradlew cucumber` against the native build: 77/77 at both 512 MB and
256 MB limits. These runs used a database first migrated by the JVM build,
which checks that Liquibase checksums are compatible between the two.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]