From 6bb456fdcc3da846e26634b7a0e1a6fe1dac7ca1 Mon Sep 17 00:00:00 2001 From: davidfrigolet Date: Tue, 25 Nov 2025 12:00:43 +0000 Subject: [PATCH 1/8] feat: graalvm doc support --- inventory-orders-service/.sdkmanrc | 5 +- inventory-orders-service/README.md | 68 +++++++++++++++++++++++ inventory-orders-service/build.gradle.kts | 12 +++- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/inventory-orders-service/.sdkmanrc b/inventory-orders-service/.sdkmanrc index 74c0edb..639daa8 100644 --- a/inventory-orders-service/.sdkmanrc +++ b/inventory-orders-service/.sdkmanrc @@ -1 +1,4 @@ -java=17.0.2-open \ No newline at end of file +#java=17.0.2-open + +# Optional: uncomment to use a GraalVM distribution when working with native images +java=17.0.8-graal \ No newline at end of file diff --git a/inventory-orders-service/README.md b/inventory-orders-service/README.md index ba0b09b..253222b 100644 --- a/inventory-orders-service/README.md +++ b/inventory-orders-service/README.md @@ -67,6 +67,9 @@ This example showcases Flamingock’s ability to: - [Prerequisites](#prerequisites) - [Dependencies](#dependencies) - [How to Run this Example](#how-to-run-this-example) + - [Option 1: Run the Application (Recommended)](#option-1-run-the-application-recommended) + - [Option 2: Run Tests](#option-2-run-tests) + - [Option 3: Run with GraalVM Native Image (Optional)](#option-3-run-with-graalvm-native-image-optional) - [Proven Functionalities](#proven-functionalities) - [Implemented Changes](#implemented-changes) - [Contributing](#contributing) @@ -211,6 +214,71 @@ Run the integration tests with Testcontainers (no Docker Compose needed): ./gradlew test ``` +### Option 3: Run with GraalVM Native Image (Optional) + +If you want to showcase Flamingock running as a GraalVM native image, you can follow these **optional** steps. The regular JVM flow above still works as-is. + +For full details, see the official docs: https://docs.flamingock.io/frameworks/graalvm + +#### 1. Use a GraalVM Java distribution + +Using SDKMAN: + +```bash +sdk env install # uses .sdkmanrc in this folder +sdk use java 22.0.2-graalce # or any installed GraalVM distribution compatible with your setup +``` + +The default `.sdkmanrc` keeps Java 17, but includes a commented example GraalVM version you can enable. + +#### 2. Ensure GraalVM support dependencies are present + +This example already includes the Flamingock GraalVM integration and resource configuration: + +- `build.gradle.kts` contains: + - `implementation("io.flamingock:flamingock-graalvm:$flamingockVersion")` +- `resource-config.json` in the project root includes: + - `META-INF/flamingock/metadata.json` resources required at native-image time + +If you copy this example to your own project, make sure you add the same pieces (or follow the docs linked above). + +#### 3. Build the fat JAR + +First build the application as usual: + +```bash +./gradlew clean build +``` + +This generates a runnable JAR under `build/libs/` (for example `build/libs/inventory-orders-service-1.0-SNAPSHOT.jar`). + +#### 4. Create the native image + +From the project root, run (adjust the JAR name and output binary name if needed): + +```bash +native-image \ + --no-fallback \ + --features=io.flamingock.graalvm.RegistrationFeature \ + -H:ResourceConfigurationFiles=resource-config.json \ + -H:+ReportExceptionStackTraces \ + --initialize-at-build-time=org.slf4j.simple \ + -jar build/libs/inventory-orders-service-1.0-SNAPSHOT.jar \ + inventory-orders-service +``` + +This uses Flamingock's GraalVM feature to automatically register all required reflection metadata. + +#### 5. Run the native image + +With Docker Compose infrastructure already running (see [Option 1](#option-1-run-the-application-recommended)), start the native binary: + +```bash +./inventory-orders-service +``` + +The application will execute the same Flamingock migrations as when running on the regular JVM, but with GraalVM-native startup and footprint characteristics. + ## Troubleshooting ### Schema Registry Connection Issues diff --git a/inventory-orders-service/build.gradle.kts b/inventory-orders-service/build.gradle.kts index 2861905..20665cd 100644 --- a/inventory-orders-service/build.gradle.kts +++ b/inventory-orders-service/build.gradle.kts @@ -26,7 +26,7 @@ repositories { group = "io.flamingock" version = "1.0-SNAPSHOT" -val flamingockVersion = "1.0.0-beta.1" +val flamingockVersion = "1.0.0-beta.3" logger.lifecycle("Building with flamingock version: $flamingockVersion") val mongodbVersion = "5.5.1" @@ -39,6 +39,10 @@ dependencies { // Flamingock Dependencies implementation(platform("io.flamingock:flamingock-community-bom:$flamingockVersion")) implementation("io.flamingock:flamingock-community") + // Optional: enable GraalVM native image support for Flamingock + // See: https://docs.flamingock.io/frameworks/graalvm + // Uncomment + implementation("io.flamingock:flamingock-graalvm:$flamingockVersion") annotationProcessor("io.flamingock:flamingock-processor:$flamingockVersion") // MongoDB dependencies @@ -73,6 +77,12 @@ application { mainClass = "io.flamingock.examples.inventory.InventoryOrdersApp" } +tasks.named("jar") { + manifest { + attributes["Main-Class"] = application.mainClass + } +} + tasks.withType { options.compilerArgs.add("-parameters") } From e91eb2a375152eb1a752ab2f8e2492ad21d33a88 Mon Sep 17 00:00:00 2001 From: davidfrigolet Date: Tue, 25 Nov 2025 18:20:18 +0000 Subject: [PATCH 2/8] feat: graalvm doc support --- inventory-orders-service/META-INF/MANIFEST.MF | 3 +++ inventory-orders-service/build.gradle.kts | 12 ++++++++++-- inventory-orders-service/resource-config.json | 7 +++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 inventory-orders-service/META-INF/MANIFEST.MF create mode 100644 inventory-orders-service/resource-config.json diff --git a/inventory-orders-service/META-INF/MANIFEST.MF b/inventory-orders-service/META-INF/MANIFEST.MF new file mode 100644 index 0000000..a205b10 --- /dev/null +++ b/inventory-orders-service/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: io.flamingock.examples.inventory.InventoryOrdersApp + diff --git a/inventory-orders-service/build.gradle.kts b/inventory-orders-service/build.gradle.kts index 20665cd..5a3e2af 100644 --- a/inventory-orders-service/build.gradle.kts +++ b/inventory-orders-service/build.gradle.kts @@ -26,7 +26,7 @@ repositories { group = "io.flamingock" version = "1.0-SNAPSHOT" -val flamingockVersion = "1.0.0-beta.3" +val flamingockVersion = "1.0.0-beta.4" logger.lifecycle("Building with flamingock version: $flamingockVersion") val mongodbVersion = "5.5.1" @@ -79,8 +79,16 @@ application { tasks.named("jar") { manifest { - attributes["Main-Class"] = application.mainClass + attributes["Main-Class"] = "io.flamingock.examples.inventory.InventoryOrdersApp" } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + from(sourceSets.main.get().output) + + from({ + configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) } + }) } tasks.withType { diff --git a/inventory-orders-service/resource-config.json b/inventory-orders-service/resource-config.json new file mode 100644 index 0000000..599fa54 --- /dev/null +++ b/inventory-orders-service/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources": { + "includes": [ + { "pattern": "META-INF/flamingock/metadata.json" } + ] + } +} From c414fea3079af39923cd6def26d03a58df47153d Mon Sep 17 00:00:00 2001 From: davidfrigolet Date: Wed, 26 Nov 2025 08:54:11 +0000 Subject: [PATCH 3/8] feat: graalvm doc support --- inventory-orders-service/README.md | 32 +++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/inventory-orders-service/README.md b/inventory-orders-service/README.md index 253222b..eaa56f3 100644 --- a/inventory-orders-service/README.md +++ b/inventory-orders-service/README.md @@ -242,15 +242,41 @@ This example already includes the Flamingock GraalVM integration and resource co If you copy this example to your own project, make sure you add the same pieces (or follow the docs linked above). -#### 3. Build the fat JAR +#### 3. Build the fat (uber) JAR -First build the application as usual: +First build the application as usual, which also creates a **fat / uber JAR** bundling all runtime dependencies and a `Main-Class` manifest entry: ```bash ./gradlew clean build ``` -This generates a runnable JAR under `build/libs/` (for example `build/libs/inventory-orders-service-1.0-SNAPSHOT.jar`). +The `jar` task in `build.gradle.kts` is configured like this: + +```kotlin +tasks.named("jar") { + manifest { + attributes["Main-Class"] = "io.flamingock.examples.inventory.InventoryOrdersApp" + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + from(sourceSets.main.get().output) + + from({ + configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) } + }) +} +``` + +This produces an executable uber JAR under `build/libs/` (for example `build/libs/inventory-orders-service-1.0-SNAPSHOT.jar`). + +> **Why this matters for GraalVM** +> +> GraalVM's `native-image -jar` mode expects a JAR that: +> - has a valid `Main-Class` manifest attribute, and +> - contains all the classes and dependencies reachable from that entry point. +> +> The fat/uber JAR configuration above ensures those requirements are met, which is essential for the native-image step to work reliably. #### 4. Create the native image From caf70a449a7a622cbf8a42120040b83b1b5ffaf1 Mon Sep 17 00:00:00 2001 From: davidfrigolet Date: Wed, 26 Nov 2025 08:55:34 +0000 Subject: [PATCH 4/8] feat: graalvm doc support --- inventory-orders-service/.sdkmanrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inventory-orders-service/.sdkmanrc b/inventory-orders-service/.sdkmanrc index 639daa8..81bc6df 100644 --- a/inventory-orders-service/.sdkmanrc +++ b/inventory-orders-service/.sdkmanrc @@ -1,4 +1,4 @@ -#java=17.0.2-open +java=17.0.2-open # Optional: uncomment to use a GraalVM distribution when working with native images -java=17.0.8-graal \ No newline at end of file +# java=17.0.8-graal \ No newline at end of file From 05b6cae1c82c3b9519dd2f751592d045bc52a6cd Mon Sep 17 00:00:00 2001 From: bercianor Date: Thu, 4 Dec 2025 20:33:26 +0000 Subject: [PATCH 5/8] refactor: update Audit Store to derive it from Target System --- .../flamingock/examples/inventory/InventoryOrdersApp.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/inventory-orders-service/src/main/java/io/flamingock/examples/inventory/InventoryOrdersApp.java b/inventory-orders-service/src/main/java/io/flamingock/examples/inventory/InventoryOrdersApp.java index 9e70768..11f3301 100644 --- a/inventory-orders-service/src/main/java/io/flamingock/examples/inventory/InventoryOrdersApp.java +++ b/inventory-orders-service/src/main/java/io/flamingock/examples/inventory/InventoryOrdersApp.java @@ -16,12 +16,10 @@ package io.flamingock.examples.inventory; -import com.mongodb.client.MongoClient; import io.flamingock.api.annotations.EnableFlamingock; import io.flamingock.api.annotations.Stage; import io.flamingock.community.Flamingock; import io.flamingock.community.mongodb.sync.driver.MongoDBSyncAuditStore; -import io.flamingock.examples.inventory.util.MongoDBUtil; import io.flamingock.examples.inventory.util.TargetSystems; import io.flamingock.internal.core.store.CommunityAuditStore; @@ -49,9 +47,9 @@ public static void main(String[] args) throws Exception { //This could return any of the available community audit stores private static CommunityAuditStore auditStore() { - MongoClient mongoClient = MongoDBUtil.getMongoClient("mongodb://localhost:27017/"); - return new MongoDBSyncAuditStore(mongoClient, DATABASE_NAME); + MongoDBSyncTargetSystem targetSystem = TargetSystems.mongoDBSyncTargetSystem(); + return MongoDBSyncAuditStore.from(targetSystem); } -} \ No newline at end of file +} From 1c45350425d38475ce909b9d797a3e4536657572 Mon Sep 17 00:00:00 2001 From: bercianor Date: Wed, 10 Dec 2025 21:11:57 +0000 Subject: [PATCH 6/8] fix: last change updates --- .../flamingock/examples/inventory/InventoryOrdersApp.java | 1 + .../flamingock/examples/inventory/SuccessExecutionTest.java | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/inventory-orders-service/src/main/java/io/flamingock/examples/inventory/InventoryOrdersApp.java b/inventory-orders-service/src/main/java/io/flamingock/examples/inventory/InventoryOrdersApp.java index 11f3301..b106b57 100644 --- a/inventory-orders-service/src/main/java/io/flamingock/examples/inventory/InventoryOrdersApp.java +++ b/inventory-orders-service/src/main/java/io/flamingock/examples/inventory/InventoryOrdersApp.java @@ -22,6 +22,7 @@ import io.flamingock.community.mongodb.sync.driver.MongoDBSyncAuditStore; import io.flamingock.examples.inventory.util.TargetSystems; import io.flamingock.internal.core.store.CommunityAuditStore; +import io.flamingock.targetystem.mongodb.sync.MongoDBSyncTargetSystem; import static io.flamingock.examples.inventory.util.TargetSystems.DATABASE_NAME; diff --git a/inventory-orders-service/src/test/java/io/flamingock/examples/inventory/SuccessExecutionTest.java b/inventory-orders-service/src/test/java/io/flamingock/examples/inventory/SuccessExecutionTest.java index ac8db19..746fd6f 100644 --- a/inventory-orders-service/src/test/java/io/flamingock/examples/inventory/SuccessExecutionTest.java +++ b/inventory-orders-service/src/test/java/io/flamingock/examples/inventory/SuccessExecutionTest.java @@ -149,9 +149,9 @@ static void beforeAll() throws Exception { static class TestConfig {} private static void runFlamingockMigrations(MongoClient mongoClient, KafkaSchemaManager schemaManager, LaunchDarklyClient launchDarklyClient) { - CommunityAuditStore auditStore = new MongoDBSyncAuditStore(mongoClient, DATABASE_NAME); - MongoDBSyncTargetSystem mongoTarget = new MongoDBSyncTargetSystem(MONGODB_TARGET_SYSTEM, mongoClient, DATABASE_NAME); + CommunityAuditStore auditStore = MongoDBSyncAuditStore.from(mongoTarget); + NonTransactionalTargetSystem kafkaTarget = new NonTransactionalTargetSystem(KAFKA_TARGET_SYSTEM).addDependency(schemaManager); NonTransactionalTargetSystem flagTarget = new NonTransactionalTargetSystem(FEATURE_FLAG_TARGET_SYSTEM).addDependency(launchDarklyClient); @@ -265,4 +265,4 @@ private void verifyChangeExecution(List auditLogs, String changeId) { && "APPLIED".equals(log.getString("state"))); assertTrue(hasApplied, "Change " + changeId + " should have APPLIED entry"); } -} \ No newline at end of file +} From 38ba9df8e490332522f7bb0068a9938bc98e6a72 Mon Sep 17 00:00:00 2001 From: bercianor Date: Thu, 11 Dec 2025 22:07:05 +0000 Subject: [PATCH 7/8] fix: pr comments --- inventory-orders-service/.sdkmanrc | 5 +--- inventory-orders-service/README.md | 28 +++++++++++------------ inventory-orders-service/build.gradle.kts | 2 +- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/inventory-orders-service/.sdkmanrc b/inventory-orders-service/.sdkmanrc index 81bc6df..6d54fb2 100644 --- a/inventory-orders-service/.sdkmanrc +++ b/inventory-orders-service/.sdkmanrc @@ -1,4 +1 @@ -java=17.0.2-open - -# Optional: uncomment to use a GraalVM distribution when working with native images -# java=17.0.8-graal \ No newline at end of file +java=17.0.8-graal diff --git a/inventory-orders-service/README.md b/inventory-orders-service/README.md index eaa56f3..4d4145a 100644 --- a/inventory-orders-service/README.md +++ b/inventory-orders-service/README.md @@ -9,15 +9,15 @@ ___ This example simulates an **e-commerce service** that manages inventory and orders. It demonstrates how Flamingock coordinates multiple target systems in lockstep using the **Change-as-Code** approach. -The story begins when the **marketing team** launches a promotional campaign that requires support for **discount codes**. +The story begins when the **marketing team** launches a promotional campaign that requires support for **discount codes**. To implement this feature safely, the product and engineering teams plan a sequence of deployments, each introducing incremental changes in a controlled, auditable way. -As development progresses, the **sales team** also requests the ability to quickly search and report on orders by discount code. +As development progresses, the **sales team** also requests the ability to quickly search and report on orders by discount code. This leads the engineers to add an index on the new field as part of the rollout, ensuring the system remains both functional and performant. ### Lifecycle of the feature rollout -1. **Initial deployment** +1. **Initial deployment** *Business driver:* prepare the system for discounts while keeping the feature hidden. - Add the `discountCode` field to the `orders` collection in **MongoDB**. - Update the `OrderCreated` schema in **Kafka** to include the same field. @@ -28,7 +28,7 @@ This leads the engineers to add an index on the new field as part of the rollout - [`UpdateOrderCreatedSchema`](#updateordercreatedschema) - [`AddFeatureFlagDiscounts`](#addfeatureflagdiscounts) -2. **Second deployment** +2. **Second deployment** *Business driver:* ensure existing and new orders remain consistent. - Backfill existing orders with a default `discountCode` (e.g. `"NONE"`). - Application logic begins to populate the field for new orders, still hidden behind the flag. @@ -37,7 +37,7 @@ This leads the engineers to add an index on the new field as part of the rollout - [`BackfillDiscountsForExistingOrders`](#backfilldiscountsforexistingorders) - [`AddIndexOnDiscountCode`](#addindexondiscountcode) -3. **Runtime activation (no deployment)** +3. **Runtime activation (no deployment)** *Business driver:* marketing activates discounts for customers. - The feature flag is enabled at runtime using a feature-flag tool (e.g. Unleash, LaunchDarkly). - No redeployment is required — the system is already prepared. @@ -226,16 +226,16 @@ Using SDKMAN: ```bash sdk env install # uses .sdkmanrc in this folder -sdk use java 22.0.2-graalce # or any installed GraalVM distribution compatible with your setup +sdk use java 17.0.8-graal # or any installed GraalVM distribution compatible with your setup ``` -The default `.sdkmanrc` keeps Java 17, but includes a commented example GraalVM version you can enable. +The default `.sdkmanrc` included with this example already uses Java 17 with GraalVM. #### 2. Ensure GraalVM support dependencies are present This example already includes the Flamingock GraalVM integration and resource configuration: -- `build.gradle.kts` contains: +- `build.gradle.kts` contains (commented): - `implementation("io.flamingock:flamingock-graalvm:$flamingockVersion")` - `resource-config.json` in the project root includes: - `META-INF/flamingock/metadata.json` resources required at native-image time @@ -364,15 +364,15 @@ This example demonstrates the following Flamingock capabilities: ## Implemented Changes -| Deployment Step | Change Name | Target Systems | Operation | Description | -|--------------------------------|-------------------------------------------------------------------------------------|-----------------------|-----------------------------------|----------------------------------------------------------------------------------------------| -| [Initial](#initial-deployment) | `AddDiscountCodeFieldToOrders` | MongoDB | Alter collection / add field | Adds `discountCode` (nullable) to the orders collection | -| [Initial](#initial-deployment) | `UpdateOrderCreatedSchema` | Kafka Schema Registry | Register new schema version | Publishes a new version of the OrderCreated event schema including discountCode | +| Deployment Step | Change Name | Target Systems | Operation | Description | +|--------------------------------|-------------------------------------------------------------------------------------|-----------------------|-----------------------------------|----------------------------------------------------------------------------------------------| +| [Initial](#initial-deployment) | `AddDiscountCodeFieldToOrders` | MongoDB | Alter collection / add field | Adds `discountCode` (nullable) to the orders collection | +| [Initial](#initial-deployment) | `UpdateOrderCreatedSchema` | Kafka Schema Registry | Register new schema version | Publishes a new version of the OrderCreated event schema including discountCode | | [Initial](#initial-deployment) | `AddFeatureFlagDiscounts` | LaunchDarkly API | Create flags | Creates feature flags for discount functionality using LaunchDarkly Management API | | [Second](#second-deployment) | `BackfillDiscountsForExistingOrders` | MongoDB | Update | Updates existing orders with discountCode = "NONE" | | [Second](#second-deployment) | `AddIndexOnDiscountCode` | MongoDB | Create index | Creates an index on discountCode to support reporting and efficient lookups | | [Final](#final-deployment) | `CleanupFeatureFlagDiscounts` | LaunchDarkly API | Archive flags | Archives temporary feature flags once the feature is permanent and code guards are removed | -| [Final](#final-deployment) | `CleanupOldSchemaVersion` | Kafka Schema Registry | Disable/delete old schema version | Removes outdated schema once all consumers have migrated to the new version | +| [Final](#final-deployment) | `CleanupOldSchemaVersion` | Kafka Schema Registry | Disable/delete old schema version | Removes outdated schema once all consumers have migrated to the new version | ## Example Output @@ -417,4 +417,4 @@ This repository is licensed under the [Apache License 2.0](../LICENSE.md). ## Explore, experiment, and empower your projects with Flamingock! -Let us know what you think or where you'd like to see Flamingock used next. \ No newline at end of file +Let us know what you think or where you'd like to see Flamingock used next. diff --git a/inventory-orders-service/build.gradle.kts b/inventory-orders-service/build.gradle.kts index 5a3e2af..7ff31de 100644 --- a/inventory-orders-service/build.gradle.kts +++ b/inventory-orders-service/build.gradle.kts @@ -42,7 +42,7 @@ dependencies { // Optional: enable GraalVM native image support for Flamingock // See: https://docs.flamingock.io/frameworks/graalvm // Uncomment - implementation("io.flamingock:flamingock-graalvm:$flamingockVersion") + // implementation("io.flamingock:flamingock-graalvm:$flamingockVersion") annotationProcessor("io.flamingock:flamingock-processor:$flamingockVersion") // MongoDB dependencies From 59a31d7bc9723e552975783b73a5c16b5e2f08eb Mon Sep 17 00:00:00 2001 From: bercianor Date: Thu, 11 Dec 2025 22:07:25 +0000 Subject: [PATCH 8/8] refactor: update testcontainers --- inventory-orders-service/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inventory-orders-service/build.gradle.kts b/inventory-orders-service/build.gradle.kts index 7ff31de..bfdb4d2 100644 --- a/inventory-orders-service/build.gradle.kts +++ b/inventory-orders-service/build.gradle.kts @@ -68,9 +68,9 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2") - testImplementation("org.testcontainers:mongodb:1.21.3") - testImplementation("org.testcontainers:kafka:1.21.3") - testImplementation("org.testcontainers:junit-jupiter:1.21.3") + testImplementation("org.testcontainers:testcontainers-mongodb:2.0.2") + testImplementation("org.testcontainers:testcontainers-kafka:2.0.2") + testImplementation("org.testcontainers:testcontainers-junit-jupiter:2.0.2") } application {