Code Monkey home page Code Monkey logo

extension-kotlin's Introduction

Axon Framework - Kotlin Extension

Maven Central Build Status SonarCloud Status Open Source Helpers

Note: This extension is still in an experimental stage.

Axon Framework is a framework for building evolutionary, event-driven microservice systems, based on the principles of Domain Driven Design, Command-Query Responsibility Segregation (CQRS) and Event Sourcing.

As such it provides you the necessary building blocks to follow these principles. Building blocks like Aggregate factories and Repositories, Command, Event and Query Buses and an Event Store. The framework provides sensible defaults for all of these components out of the box.

This set up helps you create a well-structured application without having to bother with the infrastructure. The main focus can thus become your business functionality.

This repository provides an extension to the Axon Framework: Kotlin. It provides functionality to leverage Kotlin features to be used with Axon Framework.

For more information on anything Axon, please visit our website, http://axoniq.io.

Getting started

Dependencies

For the Kotlin extension itself you can get the version from the axon-bom or use the following coordinates:

Maven

<dependency>
    <groupId>org.axonframework.extensions.kotlin</groupId>
    <artifactId>axon-kotlin</artifactId>
    <version>4.6.0</version>
</dependency>

Gradle

implementation("org.axonframework.extensions.kotlin:axon-kotlin:4.6.0")

For the Kotlin testing extension itself please use the following coordinates:

Maven

<dependency>
    <groupId>org.axonframework.extensions.kotlin</groupId>
    <artifactId>axon-kotlin-test</artifactId>
    <version>4.6.0</version>
</dependency>

Gradle

implementation("org.axonframework.extensions.kotlin:axon-kotlin-test:4.6.0")

Receiving help

Are you having trouble using the extension? We'd like to help you out the best we can! There are a couple of things to consider when you're traversing anything Axon:

  • Checking the reference guide should be your first stop, as the majority of possible scenarios you might encounter when using Axon should be covered there.
  • If the Reference Guide does not cover a specific topic you would've expected, we'd appreciate if you could file an issue about it for us.
  • There is a forum to support you in the case the reference guide did not sufficiently answer your question. Axon Framework and Server developers will help out on a best effort basis. Know that any support from contributors on posted question is very much appreciated on the forum.
  • Next to the forum we also monitor Stack Overflow for any questions which are tagged with axon.

Feature requests and issue reporting

We use GitHub's issue tracking system for new feature request, extension enhancements and bugs. Prior to filing an issue, please verify that it's not already reported by someone else.

When filing bugs:

  • A description of your setup and what's happening helps us figuring out what the issue might be
  • Do not forget to provide the version you're using
  • If possible, share a stack trace, using the Markdown semantic ```

When filing features:

  • A description of the envisioned addition or enhancement should be provided
  • (Pseudo-)Code snippets showing what it might look like help us understand your suggestion better
  • If you have any thoughts on where to plug this into the framework, that would be very helpful too
  • Lastly, we value contributions to the framework highly. So please provide a Pull Request as well!

Building the extension

If you want to build the extension locally, you need to check it out from GiHub and run the following command:

./mvnw clean install

Producing JavaDocs and Sources archive

Please execute the following command line if you are interested in producing KDoc and Source archives:

./mvnw clean install -Pjavadoc-and-sources

extension-kotlin's People

Contributors

azzazzel avatar codedrivenmitch avatar dependabot[bot] avatar github-actions[bot] avatar gklijs avatar hiddewie avatar hsenasilva avatar jangalinski avatar lfgcampos avatar radoslawsobies avatar rsobies avatar sandjelkovic avatar saratorrey avatar smcvb avatar snyk-bot avatar stefanmirkovic avatar zambrovski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

extension-kotlin's Issues

Extensions

Just some extension I use:

  • QueryTyped:
    interface QueryTyped<R>

  • QueryGatewayExtension:

inline fun <reified T> QueryGateway.queryTyped(query: QueryTyped<T>): CompletableFuture<T> {
    return query(query, T::class.java)
}

inline fun <R: List<T>, reified T> QueryGateway.queryTypedList(query: QueryTyped<R>): CompletableFuture<List<T>> {
    return query(query, ResponseTypes.multipleInstancesOf(T::class.java))
}

// ...

I have similar extensions for command gateway as well.

  • CommandTyped:
    interface CommandTyped<R>

And some other extensions

Add Gateway extensions tests

Extension functions on QueryGateway and CommandGateway are currently not tested. In addition to verifying that the correct method is being called with correct generic parameters, tests would also act as a small usage example.

For mocking MockK could be used.

Scatter Gather queries extensions

To use Scatter-Gather queries, gateway methods require ResponseType objects to be used to indicate return types.

To make it Kotlin idiomatic, extension methods on QueryGateway should be implemented to use reified generics, provide a method per ResponseType (Single without a suffix/Optional/Multiple Many) and forward those calls to existing methods on the QueryGateway.

New methods should also use Kotlin.time Duration in their signatures and convert to appropriate value and time unit for QueryGateway method invocation. Time unit of TimeUnit.MILLISECONDS might be a good option.
Since Kotlin time is still experimental, this might not be a good idea for the time being. When it stabilizes, it might be worth to reconsider

Examples of these types of extensions created for the query method can be found here.

This issue is similar to #11, but for different methods and types of queries.

Create builder extensions to avoid passing Foo::class.java

Scenario

  • extension-kotlin version: any
  • Axon Framework version: any
  • Description of your use case:

As a developer I want to avoid to pass Foo::class.java.

For example:

  • EventSourcingRepository.builder(Class)
  • GenericJpaRepository.builder(Class)

Setup developer CI / CD

As a developer I would like to start CI/CD pipeline and get instant feedback on my commit.

I would suggest to use "github actions" as developer CI/CD.

Fix problems building on JDK 11

Currently, the extension is built using JDK 11 and JDK 8 on GH.
The dokka plugin is not working JDK 11 (as far as I know), so the default for building should be JDK8, if we build dokka pages, or even better the creation of sources and javadoc should be moved to a separate profile used by release process only (which can then work on JDK 8).

Provide improved methods for AggregateLifecycle

Scenario

  • extension-kotlin version: any
  • Axon Framework version: any
  • Description of your use case:

AggregateLifecycle should provide Kotlin-like API. Since relevant AggregateLifecycle methods are static,
there is no way to extend existing class, but we can define new top-level functions.

Relevant methods are:

  • apply is a kotlin function on Any, we should name it for example applyEvent
  • createNew should get reified class and a factory function

Improve AggregateTextFixture

Scenario

  • extension-kotlin version: 0.1.0
  • Axon Framework version: 4.4.x
  • Description of your use case: (detailed description or executable reproducer, e.g. GitHub repo)

Current Behaviour

  1. AggregateTestFixture currently requires a Class<T> aggregateType to be created.
  2. The method named when of AggregateTestFixture is used to set up the test, which forces Kotlin users to escape the method name since when is a keyword in Kotlin

Wanted Behaviour

  1. instead of passing Java's class object to AggregateTestFixture, a factory method with a reified generic parameter can be used instead. That would allow for a syntax like zaggregateTestFixture()instead ofAggregateTestFixture(GiftCard.javaClass)`
  2. An extension method on AggregateTestFixture which will alias/delegate to when, but will be called something else that is not a keyword. One option could be whenever, but anything else that make sense will do.
  3. This will require a new test module of the extension, to allow extension users to use the module only for testing.

Possible Workarounds

Fixture factories can not be used as factory methods

Basic information

  • Axon Framework version: -
  • JDK version: -
  • Kotlin Extension version: 0.2.0-SNAPSHOT
  • Complete executable reproducer if available (e.g. GitHub Repo):

Steps to reproduce

Try to use call reified aggregateTestFixture or reified sagaTestFixture as a factory method, meaning without an already created fixture instance.

Expected behaviour

Use these methods as they were intended to - factory methods to create new Fixtures.

var fixture: AggregateTestFixture<MyAggregate> = aggregateTestFixture()

These methods can not be extensions to already existing fixtures but should be simple standalone functions that use reified generics.

More information can be found here #73 (comment)

Release mvp?

Although there is a lot to do ... shouldn't we just release an 0.1.0, so we can start using this in our projects?

build for kotlin 1.3.50

I personally believe it's a good idea to not just update to the latest kotlin version, but to use the embeddedKotlinVersion of gradle as an indicator which version can be viewed as current standard version ...

With the latest gradle 6, this would be 1.3.50 ... this repo still uses 1.3.41

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

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.

Add version in classes documentation

All classes KDocs should contain @since tag with the version they have been added in. This should be done once the initial version has been established.

Use kotlin coroutine return types in query extensions instead of CompletableFuture & Optional

Enhancement Description

The current query extension methods return values using Java's CompletableFuture and Optional types. The usage of these types seems awkward to me in modern Kotlin that uses coroutines. This enhancement request is to encapsulate the use of Java types in favor of pure Kotlin idioms.

Current Behaviour

Note the use of Java types here, for example:

inline fun <reified R, reified Q> QueryGateway.queryOptional(query: Q): CompletableFuture<Optional<R>> {
    return this.query(query, ResponseTypes.optionalInstanceOf(R::class.java))
}

This requires the developer to adapt CompletableFuture to Kotlin's Deferred or Flow types (depending on single vs multiple response types), as well as adapting Optional to Kotlin's nullable type system.

Wanted Behaviour

I've been trying to create new extension methods that only expose Kotlin types.

Consider the following:

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.future.asDeferred
import java.util.Optional
import java.util.concurrent.CompletableFuture

fun <T> Optional<T>.orNull(): T? = orElse(null)

fun <T> CompletableFuture<Optional<T>>.asDeferredOfNullable(): Deferred<T?> = thenApply { it.orNull() }.asDeferred()

Now, notice how I use them in the extension method queryNullableAsDeferred below, where Schedule is a Spring Data MongoDB persistent entity on the read side:

import kotlinx.coroutines.Deferred
import kotlinx.coroutines.future.asDeferred
import org.axonframework.commandhandling.gateway.CommandGateway
import org.axonframework.extensions.kotlin.queryOptional
import org.axonframework.queryhandling.QueryGateway
import org.springframework.stereotype.Component

// note use of Kotlin async types here

inline fun <reified R, reified Q> QueryGateway.queryNullableAsDeferred(q: Q): Deferred<R?> =
    queryOptional<R, Q>(q).asDeferredOfNullable()

@Component
class CqrsSchedulingService(val cgw: CommandGateway, val qgw: QueryGateway) {
    fun createSchedule(cmd: CreateScheduleCommand): Deferred<Unit> {
        return cgw.send<Unit>(cmd).asDeferred() // note conversion to Deferred here
    }

    fun updateSchedule(cmd: UpdateScheduleCommand): Deferred<Unit> {
        return cgw.send<Nothing>(cmd).asDeferred() // note conversion to Deferred here
    }

    fun findScheduleById(q: FindScheduleByIdQuery): Deferred<Schedule?> { // note use of Deferred and Kotlin's ? nullable type operator
        return qgw.queryNullableAsDeferred(q) // note use of extension method here
    }
}

I'm no Kotlin expert yet, so maybe this needs some fine-tuning, but this seems more natural to me. I think this issue could be expanded to other parts of the codebase as well (like CommandGateway.send(..), as they also return CompletableFuture.

Possible Workarounds

Subscription queries extensions

To use Subscription queries, gateway methods require ResponseType objects to be used to indicate return types.

To make it Kotlin idiomatic, extension methods on QueryGateway should be implemented to use reified generics, provide a method per ResponseType (Single without a suffix/Optional/Multiple Many) and forward those calls to existing methods on the QueryGateway. The same ResponseType can be used for both Initial and Update response types.

Examples of these types of extensions created for the query method can be found here

This issue is similar to #11 and #16, but for different methods and types of queries.

Add Maven cache to accelerate build

Currently, GitHub Actions are downloading the required dependencies and plugins from the artifact repository every time. This step is time-consuming and unneeded. Setup a .m2/repository artifact cache to accelerate the build.

Improve pom and build management

This issue is a collection of small issues detected during development.
It includes:

  • use latest stable versions of maven plugins
  • make sure to use latest version of kotlin-logging
  • make sure slf4-simple is available for testing to avoid warnings on test run

Kotlin Serializer implementation

I have not looked into it yet but I think it can be useful to provide something similar to JacksonSerializer/XStreamSerializer/etc using kotlinx.serialization.

Since I had issues with Jackson with default values and inline class: event changes

Using kotlinx.serialization may be a better solution.
If not mistaken, they do not support inline class yet but it will come later for sure.

I will try an implementation later of org.axonframework.serialization.Serializer using kotlinx.serialization.

Create a possibility to work with aggregates having immutable identifier (e.g. data class with a val)

Kotlin immutability is a strong feature. Based on Java implementation, aggregates members have to be mutable, to be able to get modified from the event sourcing handlers (including the aggregate identifier).

As shown on Event-Driven-Microservices 2019 conference, this can be changed (shown in Scala) by providing a special aggregate factory, which creates aggregates with at least immutable aggregate identifier.

`@EventHandler` does not support kotlin coroutine

Axon version

4.3

Example

    @EventHandler
    suspend fun on(event: UserEvent.Updated) {
        userRepository.update(event)
    }

Result

Caused by: org.axonframework.messaging.annotation.UnsupportedHandlerException: Unable to resolve parameter 1 (Continuation) in handler public java.lang.Object com.ukonnra.wonderland.teaparty.application.service.UserProjection.on(com.ukonnr
a.wonderland.teaparty.domain.user.UserEvent$Updated,kotlin.coroutines.Continuation<? super kotlin.Unit>).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.