Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scope is not active or entity cannot be found when using kotlin extension for "applyEvent" #286

Closed
Blackdread opened this issue Apr 18, 2023 · 11 comments

Comments

@Blackdread
Copy link

Basic information

  • Axon Framework version: BOM version 4.7.2
  • JDK version: 17 but using kotlin 1.8.20
  • Complete executable reproducer if available (e.g. GitHub Repo):

Steps to reproduce

@Aggregate
class MyRootAggregate {

    @AggregateIdentifier
    private lateinit var rootId: RootId

    @AggregateMember(eventForwardingMode = ForwardMatchingInstances::class)
    val someEntities = EnumMap<EntityType, SomeEntity>(EntityType::class.java)

    private constructor() {
        // Required by Axon Framework
    }

    @CommandHandler
    private constructor(cmd: RootCreateCmd) {
        AggregateLifecycle.apply(RootCreatedEvent(cmd.auditEntry))
//        applyEvent(RootCreatedEvent(cmd.auditEntry))  // using that have error "Cannot request current Scope if none is active"
    }

    @EventSourcingHandler
    private fun on(evt: RootCreatedEvent) {
        rootId = evt.rootId
    }

    // ========= MyEntity

    @CommandHandler
    private fun handle(cmd: RootMyEntityCreateCmd) {
        if (someEntities.containsKey(cmd.type)) {
            throw RootMyEntityAlreadyExistException(cmd.type)
        }
//        applyEvent(
        AggregateLifecycle.apply( // using that fixes the issue
             RootMyEntityCreatedEvent(
                cmd.type,
                cmd.auditEntry
            )
        )
    }

    @EventSourcingHandler
    private fun on(evt: RootMyEntityCreatedEvent) {
        someEntities[evt.type] = SomeEntity(evt.someId, evt.type, this)
    }
}


class SomeEntity(
    private val someId: SomeId,
    @EntityId
    private val type: EntityType,
    private val parent: Root, // todo might be an issue with snapshots... To be confirmed, transient?
) {

    internal val others = mutableMapOf<OtherId, Other>()

    // ========= Add level

    @CommandHandler
    private fun handle(cmd: SomeOtherAddCmd) {
        // no errors if replace with AggregateLifecycle.apply(...)
        val applyMore = applyEvent(
            SomeOtherEvent(cmd.otherId)
        )
    }

    @EventSourcingHandler
    private fun on(evt: SomeOtherEventEvent) {
        levels[evt.otherId] = Other(
               // ....
        )
    }
}

Expected behaviour

We should be able to use applyEvent from kotlin-extension

Actual behaviour

An error is thrown within the aggregate.

For info: Aggregate is fully tested with AggregateTestFixture for root and entities and for all commands/events, no errors.

Root aggregate

In case of root aggregate, I get error Cannot request current Scope if none is active, using the debugger it is thrown from org.axonframework.messaging.Scope

Entity of root aggregate

In case of entity of root aggregate, I get error Cannot request current Scope if none is active, using the debugger it is thrown from org.axonframework.messaging.Scope or error Aggregate cannot handle command ... as there is no entity instance within the aggregate to forward it to.
The code for @EntityID is correct as it works in AggregateTestFixture.

Solution found

As strange as it seems but errors disappear if replace applyEvent(...) by AggregateLifecycle.apply(...).
The only reason I can think of is that in the BOM, not all dependencies have the same axon framework version and for some reason something is broken in some version.

@Blackdread
Copy link
Author

One side question

Within the entity, there is the private val parent: Root
Does it work with snapshot?
Should it be marked transient and even if I add it, I think this is not going to work due to how the object is created from snapshots.
Am I correct?
What solution do we have for that?
Using inner class of kotlin might work, I would have access to fields of the root aggregate within entities but that means I would end up with a file class with few thousand of lines

@smcvb
Copy link
Member

smcvb commented Apr 18, 2023

First and foremost, as you're dealing with a Kotlin Extension predicament, your issue should've been constructed under the https://github.com/AxonFramework/extension-kotlin.

Secondly, I find it odd you're faced with this predicament.
If we check the extension function's implementation:

fun applyEvent(payload: Any): ApplyMore = AggregateLifecycle.apply(payload)

We can see it literally invokes AggregateLifecycle.apply(...), which is the exact bit of code you've replaced it for.
Sadly enough, the applyEvent extension function has no test cases in the Kotlin Extension...
I think your code may be used to validate whether we can reproduce the predicament, so I'll give that a try.

@smcvb smcvb transferred this issue from AxonFramework/AxonFramework Apr 18, 2023
@smcvb smcvb self-assigned this Apr 18, 2023
@smcvb
Copy link
Member

smcvb commented Apr 18, 2023

I have just written a minimal test case using the AggregateTestFixture, but both applyEvent and AggregateLifecycle#apply work as I would expect within a Kotlin application without any exceptions.
I went ahead with the fixture solution, although I noticed you pointed out you have fixture tests in place too.
Purely for validation.

To validate further, I'll construct a sample project using our initializr to see if I can replay the behavior you're seeing.
So, stay tuned.

I do note two other pointers from your issues, which I'd like to reply to.

First pointer

The only reason I can think of is that in the BOM, not all dependencies have the same axon framework version and for some reason something is broken in some version.

As nothing has recently changed around the AggregateLifecycle nor the extension function of the Kotlin Extension, I wouldn't expect a patch release mismatch to cause any issues.
Nonetheless, have you checked which versions are used within your project?
Although an extension may use another version of Axon Framework then your project, I would still anticipate the BOMs version to be prevailing.
However, you should be able to check if there are multiple Axon Framework libraries included.

Second pointer

Within the entity, there is the private val parent: Root
Does it work with snapshot?
Should it be marked transient and even if I add it, I think this is not going to work due to how the object is created from snapshots.
Am I correct?

I believe the Serializer will deal with cyclic links during the serialization process.
Hence, the snapshot of your aggregate, which will be serialized prior to storage, should work.

@smcvb
Copy link
Member

smcvb commented Apr 18, 2023

So, as promised, I've also made a minimal sample Spring Boot app.
However, also there, the extension function applyEvent(...) and AggregateLifecycle#apply methods work as expected.

For clarity, the aggregate looks like this:

@Aggregate
class MyAggregate {

    @AggregateIdentifier
    private lateinit var id: String

    constructor()

    @CommandHandler
    @CreationPolicy(AggregateCreationPolicy.ALWAYS)
    fun handle(cmd: ExampleCommand) {
        val event = ExampleEvent(cmd.id)
        if (cmd.extensionFunction) {
            applyEvent(event)
            print("extension!")
        } else {
            AggregateLifecycle.apply(event)
            print("none extension!")
        }
    }

    @EventSourcingHandler
    fun on(event: ExampleEvent) {
        this.id = event.id
    }
}

class ExampleCommand(@TargetAggregateIdentifier val id: String, val extensionFunction: Boolean = false)
class ExampleEvent(val id: String)

Although it's smaller than your sample, it should trigger similar behavior, I'd assume.
So with this, I have a request for you, @Blackdread.

Can you share a minimal reproducible project showcasing the issue you're facing?
With that in hand, I should be able to find where the predicament lies.

@Blackdread
Copy link
Author

Thank you for going through the issue, I will try to setup a project with reproducible issue.
I also find the error very strange, tests with AggregateTestFixture are all green, and kotlin extension is just a static alias.
I also tried tests with the givenCommands but they are also passing.

In pom, I have

<dependencies>
    <dependency>
        <groupId>org.axonframework</groupId>
        <artifactId>axon-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.axonframework</groupId>
        <artifactId>axon-micrometer</artifactId>
    </dependency>
    <!-- Axon Extension - Kotlin -->
    <dependency>
        <groupId>org.axonframework.extensions.kotlin</groupId>
        <artifactId>axon-kotlin</artifactId>
    </dependency>
    <!-- Axon Extension - Reactor -->
    <dependency>
        <groupId>org.axonframework.extensions.reactor</groupId>
        <artifactId>axon-reactor</artifactId>
    </dependency>
    <dependency>
        <groupId>org.axonframework.extensions.reactor</groupId>
        <artifactId>axon-reactor-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
        <groupId>org.axonframework.extensions.reactor</groupId>
        <artifactId>axon-reactor-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.axonframework</groupId>
        <artifactId>axon-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.axonframework.extensions.kotlin</groupId>
        <artifactId>axon-kotlin-test</artifactId>
        <version>4.7.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.axonframework</groupId>
            <artifactId>axon-bom</artifactId>
            <version>4.7.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

@Blackdread
Copy link
Author

@smcvb I did reproduce it https://github.com/Blackdread/axon-event-apply-error
I was writing that I was not able to reproduce it but I finally did it :)
Very weird issue.
It is in the final commit that it introduced the error 2ad9c29b08c085a58557379b7b3b7cfc499edc99, some dependency seems to be the reason.
If I checkout the commit just before dependency added 564470423285db36c6678171e9d909bc2a6c986f then there is no error

error-from-root
error-from-entity

@Blackdread
Copy link
Author

For some weird reason, it seems to be spring-boot-devtools.
Worst part is that it is not even used for development, useless dependency that I will be happy to remove...

What can Axon do?
This still seems weird that it makes the kotlin extension fail

@smcvb
Copy link
Member

smcvb commented Apr 19, 2023

Great find, @Blackdread!
I tested the addition of spring-boot-devtools just now and reached the same conclusion as you have.

In the past, we actually had to make a minor addition to Axon Framework and the extensions to accommodate Spring Boot Devtools.
I went and tested this fix we did out for the Kotlin Extension too, and it works. :-)

I do find it odd this surfaces for the applyEvent extension function though.
Nonetheless, as the spring-boot-devtools support, as included for the other extensions, works for the Kotlin Extension also, I think we've got a straightforward fix.

@smcvb
Copy link
Member

smcvb commented Apr 19, 2023

I have just added a pull request adding the behavior to the Kotlin Extension.
You can find the PR here.

I have not set it as a bug, though, as I think it's more a form of undocumented missing support.
This makes it a feature, targeted towards 4.8.0, which should be released somewhere this summer.

I assume you're fine with disabling devtools for the time being as a workaround?
As another workaround, you should be able to add the spring-devtools.properties file in your own project too.
I hope all this clarifies things and helps you further, @Blackdread!

@Blackdread
Copy link
Author

I am fine with disabling it for now.
Thank you.

smcvb added a commit that referenced this issue Apr 19, 2023
…vtools-support

[#286] Add `spring-boot-devtools` support
@smcvb
Copy link
Member

smcvb commented Apr 19, 2023

Closing this issue, as the Spring Boot Devtools support has been introduced by PR #287.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants