[#3195] feat(migration): OpenRewrite script to help with migration from Axon Framework 4.x to Axon(iq) Framework 5.x#4549
Merged
Conversation
54c81f6 to
056a3d1
Compare
c7d828f to
5f8ab4e
Compare
…Framework 4.x to Axoniq Framework 5.1.0
…n recipes and tests
…d BOM dependencies
…n 4.x to 5.x migration - Add `ConfigureEventSourcedAnnotationKotlinTest` to verify Kotlin-specific behaviors in migration recipes. - Introduce `ConvertCommandHandlerConstructorToCompanionObject` recipe to rewrite Kotlin `@CommandHandler` constructors into companion object methods meeting Axon 5.x requirements. - Add `ConvertCommandHandlerConstructorToCompanionObjectTest` to validate the recipe. - Add `MigrateAggregateTestFixtureSetupKotlinTest` to confirm Kotlin-compatible test fixture setup migration with proper class literal handling. - Replace fully qualified references with simple imports for `@CommandHandler` and `EventAppender`. - Update Kotlin migration recipes to ensure imports are correctly added for compatibility with Axon 5.x. - Introduce Kotlin upgrade step in migration YAML to support dependency and plugin alignment with Kotlin 2.x. - Improve handling of `CommandHandler` in companion object transformations to prevent import clashes during migration. - Introduce `AddEventTagAnnotationKotlinTest` to validate annotation of Kotlin primary constructor parameters. - Update `AddEventTagAnnotation` recipe to handle Kotlin-specific constructs such as `data class` primary constructors and `val/var` fields. - Improve annotation handling for Kotlin sources to ensure `@EventTag` is correctly applied and formatted. - Add test to validate fallback annotation placement for mismatched Kotlin field names. - Refactor `AddEventTagAnnotation` to properly handle Kotlin `data class` field declaration order. - Fix `isFirstFieldInClass` implementation to ensure correct field lookup in Kotlin and Java classes.
Closes the Gradle gap in `axon-migration` so the BOM rename + version
pin works end-to-end on Maven *and* Gradle projects (Groovy DSL,
Kotlin DSL, plain `platform(...)`, Spring Dependency Management
`mavenBom(...)`, and the Cinema-style `extra["axonFrameworkVersion"]`
+ `${property("...")}` indirection).
- Ship `migration/init.gradle` and document the Gradle invocation
(`gradle rewriteRun --init-script ...`) in the module README.
- Compose `gradle.ChangeManagedDependency` and
`gradle.ChangeExtraProperty` into the BOM YAMLs so Spring DM imports
and Groovy `ext.*` version properties get rewritten alongside the
existing Maven and `gradle.ChangeDependency` legs.
- Add `MigrateKotlinDslBomImport`, a small Kotlin-DSL-only recipe that
fills two upstream gaps: `SpringDependencyManagementPluginEntry`
doesn't extract version variables from `K.StringTemplate` whose
embedded expression is `property("...")`, and `ExtraProperty` doesn't
match the Kotlin-subscript `extra["..."] = "..."` shape. The recipe
rewrites the `groupId:artifactId:` prefix in `mavenBom(...)` while
preserving the `${property(...)}` indirection, and updates the
matching `extra["..."]` literal in the same pass.
- Cover the new Gradle paths with `Axon4ToAxon5BomGradleTest` (Groovy
+ Kotlin DSL, both free and commercial legs, including the
Cinema-style fixture). Tests attach a synthetic `GradleProject`
marker rather than spinning up a Gradle daemon via `withToolingApi()`,
keeping them lightweight (no `gradle-tooling-api` test dep needed).
- Expand annotation rewrites: add `@AggregateRoot` → `@EventSourcedEntity`, and clarify the removal of `@AggregateIdentifier` in favor of `tagKey` + `@EventTag`. - Document unconditional removal of `@CreationPolicy`, including `AggregateCreationPolicy`, and explain handling for `CREATE_IF_MISSING` and `ALWAYS`. - Provide additional guidance on lifted `@RoutingKey` and placement of `@EntityCreator` in constructors.
…nTestFixture` cleanup - Introduce `AddAxonTestFixtureTearDown` recipe to automatically generate an `@AfterEach tearDown()` method calling `fixture.stop()`, ensuring proper lifecycle management for `AxonTestFixture` in JUnit 5 tests. - Skip classes with existing `@AfterEach` methods or methods named `tearDown` to avoid duplication. - Limit changes to Java sources only; Kotlin support to be addressed separately. - Add comprehensive tests to verify correct migration behavior and idempotence.
5f8ab4e to
6a4169c
Compare
smcvb
requested changes
May 12, 2026
Contributor
smcvb
left a comment
There was a problem hiding this comment.
What an insane effort to provide this. Thanks a ton, @MateuszNaKodach! I've skimmed the recipes and tests, and validated the larger Recipe implementation. The rough line is there and I am loving it. But I do have some concerns (as usual) to cover before we can merge this. I'd expect us to be close though! Lots of my points are questions we can potentially disregard.
Replace placeholder `@author Axon Framework Team` and `@since 5.2.0` across all migration recipe classes with the correct values (`@author Mateusz Nowak`, `@since 5.1.1`). Also renames the test class `Axon4ToAxon5TestTest` to `Axon4ToAxon5TestFixtureTest` to read more naturally (reviewer suggestion); the corresponding file move ships in the same change so the new file is compilable at HEAD.
The Gradle init script was missing the standard Axon Framework header. Aligns with the rest of the module.
…ming into umbrella - `axon4-to-axon5-extension-tracing-opentelemetry.yml`: reduced to a placeholder no-op. Revisioning of the distributed-tracing extension is slated for a later release (issue #3594); doing the package move now would land ahead of the AF5 integration. - `axon4-to-axoniq5-event-streaming.yml`: corrected the description. `MultiStreamableMessageSource` has existed since AF 4.2; the new Axoniq Framework 5 module consolidates that pattern rather than introducing a wholly new concept without an AF4 counterpart. - `axon4-to-axoniq5-postgresql.yml`: removed entirely. The Axoniq PostgreSQL connector is new in AF5 with no AF4 source to rewrite, so an OpenRewrite recipe adds no value. README updated accordingly. - `axon4-to-axoniq5.yml`: wired `Axon4ToAxoniq5EventStreaming` into the commercial umbrella so the placeholder runs in the right order once it gains class-level mappings.
…all sites `AddEventTagAnnotation`'s scanner previously discovered event payload types from `@EventSourcingHandler` method parameters only. That misses a valid AF4 pattern: an event published via `AggregateLifecycle.apply` but never re-sourced in the entity (no matching `@EventSourcingHandler`). Without `@EventTag`, AF5 cannot determine the aggregate-id tag for that event at runtime. The scanner now also walks `AggregateLifecycle.apply(...)` invocations anywhere in the entity body and adds the first argument's payload type to the accumulator alongside the handler-based entries. The two sources union into the same map, so events that are both applied AND sourced remain handled exactly as before. New test `Axon4ToAxon5EventSourcingTest#taggsEventPublishedViaApply\ EvenWithoutEventSourcingHandler` covers the apply-but-not-sourced case.
…ension tests - BOM tests: bump sample starting version from 4.11.0 to 4.13.0 to match the current AF4 LTS line. - Reactor / spring-aot extension tests: bump sample version to 4.12.0 (the latest release of each extension on Maven Central). - Micrometer extension test: replace `GlobalMetricRegistry` (which does not exist in AF5) with `MessageCountingMonitor` so the asserted package rename references a class that actually ships in the AF5 metrics-micrometer module.
…chInterceptor` signatures `MessageHandlerInterceptor.handle(UoW, InterceptorChain) -> Object` becomes `interceptOnHandle(M, ProcessingContext, MessageHandlerInterceptorChain<M>) -> MessageStream<?>` (and likewise for the dispatch interface). The method body is intentionally left untouched: references to the dropped `unitOfWork` / `interceptorChain` / `messages` parameters turn into unresolved-symbol compile errors, surfacing every call site that needs human review. A class-level `// TODO #LLM` comment points to the migration path doc. The signature swap goes through a sub-parsed stub class so the rewriter can take real `J.MethodDeclaration` LST nodes for the new return type, name, and parameter list. The unused `UnitOfWork` import is force-removed even though the body still references its identifier — the body's `uow.X(...)` is a compile error anyway, and the user's IDE will clean up further unused imports during the manual body migration. Lifecycle-hook semantics (`onCommit` vs `runOnAfterCommit`, no AF5 equivalent for `onRollback`) and the `interceptorChain.proceed()` → `chain .proceed(message, context)` shift are intentionally NOT mechanized — a blind rewrite would silently land semantically wrong code in non-trivial interceptors. Scoped to fire only on classes that actually carry the AF4 `handle` method, so empty-body fixtures and abstract bases stay untouched.
6ffdaf7 to
8c73ca2
Compare
MateuszNaKodach
commented
May 18, 2026
smcvb
approved these changes
May 19, 2026
…ework.migration namespace - Rename the `name:` field in all 8 `axon4-to-axoniq5-*.yml` YAML recipes from `org.axonframework.migration.*` to `io.axoniq.framework.migration.*` - Update the 7 recipeList cross-references in the umbrella UpgradeAxon4ToAxoniq5 recipe - Relocate the 3 Axoniq-only test classes to `io/axoniq/framework/migration/` (git mv) and update their package declarations, scanRuntimeClasspath, and activateRecipes calls - Update the two `Axon4ToAxon5Bom*Test` inline commercial-leg references - Update recipe names in README.md examples and Group B table - Update recipe name in init.gradle usage comment Java source classes in org.axonframework.migration are not moved — they are shared between the free AF5 and commercial Axoniq recipes.
…o5): pattern Replace five ad-hoc TODO comment formats with a single grep-able tag that marks compiler-transparent manual follow-up locations — spots where the recipe made a best-effort change the compiler will not reject, but where developer intervention is still required: - Java/Kotlin line comment: // TODO(axon4to5): <action> - Java/Kotlin inline: /* TODO(axon4to5): <action> */ - Properties/YAML: # TODO(axon4to5): <action> Affected recipes: AnnotateObsoleteSequencingPolicyProperty, ConfigureEventSourcedAnnotation, MigrateAggregateTestFixtureSetup, MigrateMessageInterceptorSignatures, AddEventTagAnnotation. Update README.md to document the pattern and the grep command so developers know what to search for after running the migration.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Important for reviews
Don’t focus on the OpenRewrite scripts; instead, focus on the tests to verify whether they correctly modify the target project’s code.
How to run?
In order to test it on your own repository:
Execute in the repo to be migrated:
And be happy because of:
If you find any places that weren’t migrated as expected by OpenRewrite, ask Claude to update the migration recipes and provide the AI agent with both the expected outcome and the mistake it made.
This PR will resolves issue #3195.