Class AddAggregateScopeToDeadlineCalls

java.lang.Object
org.openrewrite.Recipe
org.openrewrite.ScanningRecipe<AddAggregateScopeToDeadlineCalls.Accumulator>
org.axonframework.migration.AddAggregateScopeToDeadlineCalls
All Implemented Interfaces:
Cloneable

public class AddAggregateScopeToDeadlineCalls extends org.openrewrite.ScanningRecipe<AddAggregateScopeToDeadlineCalls.Accumulator>
Passes the aggregate scope explicitly to the DeadlineManager calls of an Axon Framework 4 aggregate that relied on the scope of the running handler.

Axon Framework 4 ran every aggregate handler inside the aggregate's scope, so schedule(trigger, name), schedule(trigger, name, payload) and cancelAllWithinScope(name) described the scope themselves, as new AggregateScopeDescriptor(type, identifier). The Axoniq Framework legacy module keeps these overloads, but only a Saga handler runs inside a scope in Axon Framework 5: called from an entity handler they throw an IllegalStateException. The recipe rewrites them to the overloads taking a ScopeDescriptor, passing the descriptor Axon Framework 4 built:

  • the type is the declared type of the class the aggregate's repository was configured for: the type of its @Aggregate or @AggregateRoot annotation, or its simple name when it declares none. Axon Framework 4 used that type for every subtype of a polymorphic aggregate. As the configuration is not always in the sources, the recipe approximates that class: the topmost class of the hierarchy that a configuration call in the sources registers (configureAggregate(..), AggregateConfigurer.defaultConfiguration(..) or jpaMappedConfiguration(..), or a repository builder(..)); else the topmost annotated class, which Spring registers as the root; else the class itself, unless it is abstract;
  • the identifier is the member annotated with @AggregateIdentifier, or with @EntityId or a persistence @Id on an annotated aggregate.
Keeping Axon Framework 4's type matters beyond the call itself: cancelAllWithinScope only cancels deadlines whose scope equals the given one, including deadlines a not yet upgraded node scheduled.

The identifier is read when the call is made. Axon Framework 4 resolved it lazily, when the unit of work prepared its commit, but the legacy module runs a call made outside a Saga immediately, so a lazy identifier would be read at the same moment.

A creation command handler, a @CommandHandler constructor or a @CreationPolicy(ALWAYS) or CREATE_IF_MISSING handler, has no identifier to read yet: the aggregate sets it once it applied its first event. There the recipe reads the command's target identifier instead, when the command class in the aggregate's source set declares exactly one @TargetAggregateIdentifier field, readable as a record accessor, a getter or a public field. Axon Framework 4 routed the command to the aggregate of that identifier, and loaded an existing one by it for CREATE_IF_MISSING.

Where the recipe cannot build the descriptor, it leaves a // TODO(axon4to5): marker on the statement: in any other constructor or static method, which has no aggregate instance to read the identifier from (a creation command handler becomes a static method in Axon Framework 5), in a creation handler whose command offers no such target identifier, in a member entity of an aggregate, and in an aggregate whose identifier or type the recipe cannot find. The recipe rewrites Java sources only: in an aggregate written in another JVM language, such as Kotlin, it marks every such call instead. Calls with an explicit scope, cancelSchedule and cancelAll, Sagas, and classes that are neither an aggregate nor one of its entities are left alone. The recipe is idempotent.

Since:
5.4.0
Author:
Mateusz Nowak
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static class 
    What the scanning phase learns about the source classes.

    Nested classes/interfaces inherited from class org.openrewrite.Recipe

    org.openrewrite.Recipe.Builder, org.openrewrite.Recipe.DelegatingRecipe
  • Field Summary

    Fields inherited from class org.openrewrite.Recipe

    contributors, examples, PANIC
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
     
     
    getInitialValue(org.openrewrite.ExecutionContext ctx)
     
    org.openrewrite.TreeVisitor<?,org.openrewrite.ExecutionContext>
     
    org.openrewrite.TreeVisitor<?,org.openrewrite.ExecutionContext>
     

    Methods inherited from class org.openrewrite.ScanningRecipe

    clone, generate, generate, getAccumulator, getVisitor

    Methods inherited from class org.openrewrite.Recipe

    addDataTable, aggregateDataTableDescriptors, builder, buildRecipeList, causesAnotherCycle, createRecipeDescriptor, equals, getContributors, getDataTableDescriptors, getDescriptor, getEstimatedEffortPerOccurrence, getExamples, getInstanceName, getInstanceNameSuffix, getJacksonPolymorphicTypeTag, getMaintainers, getName, getRecipeList, getTags, hashCode, maxCycles, noop, onComplete, run, run, run, setContributors, setExamples, validate, validate, validateAll, validateAll, withOptions

    Methods inherited from class java.lang.Object

    finalize, getClass, notify, notifyAll, toString, wait, wait, wait