From 5501a9f3d61a203a6311458b57bf7c43dc19627c Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Wed, 23 Sep 2020 00:39:26 +0200 Subject: [PATCH 01/11] wip: working manual configuration, no tests for autoconfigure, no spb starter --- kotlin-example/pom.xml | 100 +++++++++++ .../example/AxonKotlinExampleApplication.kt | 84 ++++++++++ .../extension/kotlin/example/api/Commands.kt | 68 ++++++++ .../extension/kotlin/example/api/Events.kt | 80 +++++++++ .../kotlin/example/core/BankAccount.kt | 156 ++++++++++++++++++ .../example/core/BankAccountAdvanced.kt | 136 +++++++++++++++ .../projection/SimpleBankProjection.kt | 32 ++++ kotlin-springboot-autoconfigure/pom.xml | 69 ++++++++ .../AggregateWithImmutableIdentifier.kt | 30 ++++ ...tesWithImmutableIdentifierConfiguration.kt | 105 ++++++++++++ ...gregatesWithImmutableIdentifierSettings.kt | 27 +++ ...bleAggregateWithImmutableIdentifierScan.kt | 40 +++++ .../kotlin/spring/ReflectionExtensions.kt | 101 ++++++++++++ kotlin/pom.xml | 5 + .../AggregateConfigurerExtensions.kt | 40 +++++ .../aggregate/AggregateIdentifierConverter.kt | 46 ++++++ ...AggregateWithImmutableIdentifierFactory.kt | 120 ++++++++++++++ ...regateWithImmutableIdentifierRepository.kt | 56 +++++++ .../aggregate/ImmutableAggregateIdentifier.kt | 30 ++++ .../QueryUpdateEmitterExtensionsTest.kt | 15 ++ ...egateWithImmutableIdentifierFactoryTest.kt | 81 +++++++++ ...teWithImmutableIdentifierRepositoryTest.kt | 87 ++++++++++ .../extensions/kotlin/testObjects.kt | 32 ++++ pom.xml | 48 ++++++ 24 files changed, 1588 insertions(+) create mode 100644 kotlin-example/pom.xml create mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt create mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/api/Commands.kt create mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/api/Events.kt create mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt create mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt create mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/projection/SimpleBankProjection.kt create mode 100644 kotlin-springboot-autoconfigure/pom.xml create mode 100644 kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregateWithImmutableIdentifier.kt create mode 100644 kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt create mode 100644 kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierSettings.kt create mode 100644 kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/EnableAggregateWithImmutableIdentifierScan.kt create mode 100644 kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/ReflectionExtensions.kt create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateConfigurerExtensions.kt create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateIdentifierConverter.kt create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepository.kt create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableAggregateIdentifier.kt create mode 100644 kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactoryTest.kt create mode 100644 kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepositoryTest.kt diff --git a/kotlin-example/pom.xml b/kotlin-example/pom.xml new file mode 100644 index 0000000..046f8ec --- /dev/null +++ b/kotlin-example/pom.xml @@ -0,0 +1,100 @@ + + + + + 4.0.0 + + Axon Framework - Kotlin Extension Example + Module for the Kotlin Extension Example of Axon Framework + + + org.axonframework.extensions.kotlin + axon-kotlin-parent + 0.2.0-SNAPSHOT + + + axon-kotlin-example + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-validation + + + org.axonframework.extensions.kotlin + axon-kotlin-springboot-starter + + + io.github.microutils + kotlin-logging + + + org.axonframework + axon-spring-boot-starter + ${axon.version} + + + + org.axonframework + axon-server-connector + + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + 1.8 + + -Xjsr305=strict + + + no-arg + all-open + spring + + + + + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-maven-noarg + ${kotlin.version} + + + + + + diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt new file mode 100644 index 0000000..1103ff0 --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extension.kotlin.example + +import mu.KLogging +import org.axonframework.commandhandling.gateway.CommandGateway +import org.axonframework.eventhandling.tokenstore.TokenStore +import org.axonframework.eventhandling.tokenstore.inmemory.InMemoryTokenStore +import org.axonframework.eventsourcing.eventstore.EmbeddedEventStore +import org.axonframework.eventsourcing.eventstore.EventStore +import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine +import org.axonframework.extension.kotlin.example.api.CreateAdvancedBankAccountCommand +import org.axonframework.extension.kotlin.example.api.CreateBankAccountCommand +import org.axonframework.extension.kotlin.example.api.DepositMoneyCommand +import org.axonframework.extension.kotlin.example.core.AdvancedBankAccountService +import org.axonframework.extension.kotlin.example.core.BankAccountIdentifier +import org.axonframework.extension.kotlin.example.core.BankAccountService +import org.axonframework.extension.kotlin.spring.AggregatesWithImmutableIdentifierConfiguration +import org.axonframework.extension.kotlin.spring.EnableAggregateWithImmutableIdentifierScan +import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter +import org.axonframework.extensions.kotlin.send +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.ApplicationRunner +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Import +import java.util.* + +/** + * Starting point. + * @param args CLI parameters. + */ +fun main(args: Array) { + SpringApplication.run(AxonKotlinExampleApplication::class.java, *args) +} + +@SpringBootApplication +@EnableAggregateWithImmutableIdentifierScan +class AxonKotlinExampleApplication { + + companion object : KLogging() + +// @Autowired +// fun manualConfigure(configurer: Configurer) { +// configurer.configureAggregate( +// defaultAggregateConfiguration() +// .configureRepository { config -> +// EventSourcingAggregateWithImmutableIdentifierRepository( +// EventSourcingRepository +// .builder(BankAccount::class.java) +// .eventStore(config.eventStore()) +// .aggregateFactory(AggregateWithImmutableIdentifierFactory.usingIdentifier(UUID::class) { UUID.fromString(it) }) +// ) +// } +// ) +// } + + /** + * Configures to use in-memory embedded event store. + */ + @Bean + fun eventStore(): EventStore = EmbeddedEventStore.builder().storageEngine(InMemoryEventStorageEngine()).build() + + /** + * Configures to use in-memory token store. + */ + @Bean + fun tokenStore(): TokenStore = InMemoryTokenStore() + +} \ No newline at end of file diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/api/Commands.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/api/Commands.kt new file mode 100644 index 0000000..cea8263 --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/api/Commands.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extension.kotlin.example.api + +import org.axonframework.extension.kotlin.example.core.BankAccountIdentifier +import org.axonframework.modelling.command.TargetAggregateIdentifier +import javax.validation.constraints.Min + +/** + * Create account. + */ +data class CreateBankAccountCommand( + @TargetAggregateIdentifier + val bankAccountId: String, + @Min(value = 0, message = "Overdraft limit must not be less than zero") + val overdraftLimit: Long +) + +/** + * Create advanced account. + */ +data class CreateAdvancedBankAccountCommand( + @TargetAggregateIdentifier + val bankAccountId: BankAccountIdentifier, + @Min(value = 0, message = "Overdraft limit must not be less than zero") + val overdraftLimit: Long +) + + +/** + * Deposit money. + */ +data class DepositMoneyCommand( + @TargetAggregateIdentifier + val bankAccountId: String, + val amountOfMoney: Long +) + +/** + * Withdraw money. + */ +data class WithdrawMoneyCommand( + @TargetAggregateIdentifier + val bankAccountId: String, + val amountOfMoney: Long +) + +/** + * Return money if transfer is not possible. + */ +data class ReturnMoneyOfFailedBankTransferCommand( + @TargetAggregateIdentifier + val bankAccountId: String, + val amount: Long +) diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/api/Events.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/api/Events.kt new file mode 100644 index 0000000..25a3252 --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/api/Events.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extension.kotlin.example.api + +import org.axonframework.extension.kotlin.example.core.BankAccountIdentifier + +/** + * Account created. + */ +data class BankAccountCreatedEvent( + val id: String, + val overdraftLimit: Long +) + +/** + * Advanced account created. + */ +data class AdvancedBankAccountCreatedEvent( + val id: BankAccountIdentifier, + val overdraftLimit: Long +) + +/** + * Collecting event for increasing amount. + */ +sealed class MoneyAddedEvent( + open val bankAccountId: String, + open val amount: Long +) + +/** + * Money deposited. + */ +data class MoneyDepositedEvent(override val bankAccountId: String, override val amount: Long) : MoneyAddedEvent(bankAccountId, amount) + +/** + * Money returned. + */ +data class MoneyOfFailedBankTransferReturnedEvent(override val bankAccountId: String, override val amount: Long) : MoneyAddedEvent(bankAccountId, amount) + +/** + * Money received via transfer. + */ +data class DestinationBankAccountCreditedEvent(override val bankAccountId: String, override val amount: Long, val bankTransferId: String) : MoneyAddedEvent(bankAccountId, amount) + +/** + * Collecting event for decreasing amount. + */ +sealed class MoneySubtractedEvent( + open val bankAccountId: String, + open val amount: Long +) + +/** + * Money withdrawn. + */ +data class MoneyWithdrawnEvent(override val bankAccountId: String, override val amount: Long) : MoneySubtractedEvent(bankAccountId, amount) + +/** + * Money transferred. + */ +data class SourceBankAccountDebitedEvent(override val bankAccountId: String, override val amount: Long, val bankTransferId: String) : MoneySubtractedEvent(bankAccountId, amount) + +/** + * Money transfer rejected. + */ +data class SourceBankAccountDebitRejectedEvent(val bankTransferId: String) diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt new file mode 100644 index 0000000..8d90255 --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extension.kotlin.example.core + +import mu.KLogging +import org.axonframework.commandhandling.CommandHandler +import org.axonframework.commandhandling.gateway.CommandGateway +import org.axonframework.eventsourcing.EventSourcingHandler +import org.axonframework.extension.kotlin.example.AxonKotlinExampleApplication +import org.axonframework.extension.kotlin.example.api.* +import org.axonframework.extension.kotlin.spring.AggregateWithImmutableIdentifier +import org.axonframework.extensions.kotlin.aggregate.ImmutableAggregateIdentifier +import org.axonframework.extensions.kotlin.send +import org.axonframework.modelling.command.AggregateCreationPolicy +import org.axonframework.modelling.command.AggregateLifecycle.apply +import org.axonframework.modelling.command.CreationPolicy +import org.springframework.boot.ApplicationRunner +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.core.annotation.Order +import org.springframework.stereotype.Service +import java.util.* + +/** + * Bank configuration. + */ +@Configuration +class BankConfiguration(val bankAccountService: BankAccountService) { + /** + * Application runner of bank ops. + */ + @Bean + fun accountOperationsRunner() = ApplicationRunner { + bankAccountService.accountOperations() + } +} + +/** + * Bank service. + */ +@Service +class BankAccountService(val commandGateway: CommandGateway) { + + companion object : KLogging() + + /** + * Bank ops. + */ + fun accountOperations() { + val accountId = UUID.randomUUID().toString() + + logger.info { "\nPerforming basic operations on account $accountId" } + + commandGateway.send( + command = CreateBankAccountCommand(accountId, 100), + onSuccess = { _, result: Any?, _ -> + AxonKotlinExampleApplication.logger.info { "Successfully created account with id: $result" } + commandGateway.send( + command = DepositMoneyCommand(accountId, 20), + onSuccess = { c, _: Any?, _ -> logger.info { "Successfully deposited ${c.payload.amountOfMoney}" } }, + onError = { c, e, _ -> logger.error(e) { "Error depositing money on ${c.payload.bankAccountId}" } } + ) + }, + onError = { c, e, _ -> logger.error(e) { "Error creating account ${c.payload.bankAccountId}" } } + ) + + } +} + +/** + * Bank account aggregate as data class. + */ +@AggregateWithImmutableIdentifier +data class BankAccount( + @ImmutableAggregateIdentifier + private val id: UUID +) { + + private var overdraftLimit: Long = 0 + private var balanceInCents: Long = 0 + + /** + * Creates account. + */ + @CommandHandler + @CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING) + fun create(command: CreateBankAccountCommand): String { + apply(BankAccountCreatedEvent(command.bankAccountId, command.overdraftLimit)) + return command.bankAccountId + } + + /** + * Deposits money to account. + */ + @CommandHandler + fun deposit(command: DepositMoneyCommand) { + apply(MoneyDepositedEvent(id.toString(), command.amountOfMoney)) + } + + /** + * Withdraw money from account. + */ + @CommandHandler + fun withdraw(command: WithdrawMoneyCommand) { + if (command.amountOfMoney <= balanceInCents + overdraftLimit) { + apply(MoneyWithdrawnEvent(id.toString(), command.amountOfMoney)) + } + } + + /** + * Return money from account. + */ + @CommandHandler + fun returnMoney(command: ReturnMoneyOfFailedBankTransferCommand) { + apply(MoneyOfFailedBankTransferReturnedEvent(id.toString(), command.amount)) + } + + /** + * Handler to initialize bank accounts attributes. + */ + @EventSourcingHandler + fun on(event: BankAccountCreatedEvent) { + overdraftLimit = event.overdraftLimit + balanceInCents = 0 + } + + /** + * Handler adjusting balance. + */ + @EventSourcingHandler + fun on(event: MoneyAddedEvent) { + balanceInCents += event.amount + } + + /** + * Handler adjusting balance. + */ + @EventSourcingHandler + fun on(event: MoneySubtractedEvent) { + balanceInCents -= event.amount + } + +} \ No newline at end of file diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt new file mode 100644 index 0000000..25de56b --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extension.kotlin.example.core + +import mu.KLogging +import org.axonframework.commandhandling.CommandHandler +import org.axonframework.commandhandling.gateway.CommandGateway +import org.axonframework.eventsourcing.EventSourcingHandler +import org.axonframework.extension.kotlin.example.api.AdvancedBankAccountCreatedEvent +import org.axonframework.extension.kotlin.example.api.CreateAdvancedBankAccountCommand +import org.axonframework.extension.kotlin.spring.AggregateWithImmutableIdentifier +import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter +import org.axonframework.extensions.kotlin.aggregate.ImmutableAggregateIdentifier +import org.axonframework.extensions.kotlin.send +import org.axonframework.modelling.command.AggregateCreationPolicy +import org.axonframework.modelling.command.AggregateLifecycle.apply +import org.axonframework.modelling.command.CreationPolicy +import org.springframework.boot.ApplicationRunner +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.core.annotation.Order +import org.springframework.stereotype.Service +import java.util.* + +/** + * Advanced bank config. + */ +@Configuration +class AdvancedBankConfiguration(val advancedBankAccountService: AdvancedBankAccountService) { + + /** + * Application run starting bank ops. + */ + @Bean + fun advancedAccountOperationsRunner() = ApplicationRunner { + advancedBankAccountService.accountOperations() + } + + /** + * Bank identifier converter. + */ + @Bean + fun bankIdentifierConverter() = object : AggregateIdentifierConverter { + override fun apply(aggregateIdentifier: String) = BankAccountIdentifier(aggregateIdentifier.subSequence(3, aggregateIdentifier.length - 3).toString()) + } + + /** + * Long converter (not used), should remain to demonstrate correct converter selection. + */ + @Bean + fun longConverter() = object : AggregateIdentifierConverter { + override fun apply(aggregateIdentifier: String) = aggregateIdentifier.toLong() + } + +} + +/** + * Advanced bank service. + */ +@Service +class AdvancedBankAccountService(val commandGateway: CommandGateway) { + + companion object : KLogging() + + /** + * Runs account ops. + */ + fun accountOperations() { + + val accountIdAdvanced = BankAccountIdentifier(UUID.randomUUID().toString()) + logger.info { "\nPerforming advanced operations on account $accountIdAdvanced" } + + commandGateway.send( + command = CreateAdvancedBankAccountCommand(accountIdAdvanced, 100), + onSuccess = { _, result: Any?, _ -> + logger.info { "Successfully created account with id: $result" } + }, + onError = { c, e, _ -> logger.error(e) { "Error creating account ${c.payload.bankAccountId}" } } + ) + } +} + + +/** + * Value type for bank account identifier. + */ +data class BankAccountIdentifier(val id: String) { + override fun toString(): String = "<<<$id>>>" +} + +/** + * Aggregate using a complex type as identifier. + */ +@AggregateWithImmutableIdentifier +data class BankAccountAdvanced( + @ImmutableAggregateIdentifier + private val id: BankAccountIdentifier +) { + + private var overdraftLimit: Long = 0 + private var balanceInCents: Long = 0 + + /** + * Create command handler. + */ + @CommandHandler + @CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING) + fun create(command: CreateAdvancedBankAccountCommand): BankAccountIdentifier { + apply(AdvancedBankAccountCreatedEvent(command.bankAccountId, command.overdraftLimit)) + return command.bankAccountId + } + + + /** + * Handler to initialize bank accounts attributes. + */ + @EventSourcingHandler + fun on(event: AdvancedBankAccountCreatedEvent) { + overdraftLimit = event.overdraftLimit + balanceInCents = 0 + } +} + diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/projection/SimpleBankProjection.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/projection/SimpleBankProjection.kt new file mode 100644 index 0000000..3c569dc --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/projection/SimpleBankProjection.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extension.kotlin.example.projection + +import mu.KLogging +import org.axonframework.eventhandling.EventHandler +import org.axonframework.eventhandling.EventMessage +import org.springframework.stereotype.Component + +@Component +class SimpleBankProjection { + + companion object : KLogging() + + @EventHandler + fun > on(event: T) { + logger.info { "Received event ${event.payload}" } + } +} \ No newline at end of file diff --git a/kotlin-springboot-autoconfigure/pom.xml b/kotlin-springboot-autoconfigure/pom.xml new file mode 100644 index 0000000..20ccc14 --- /dev/null +++ b/kotlin-springboot-autoconfigure/pom.xml @@ -0,0 +1,69 @@ + + + + + 4.0.0 + + Axon Framework - Kotlin Extension SpringBoot AutoConfigure + Module for the Kotlin SpringBoot AutoConfigure of Axon Framework + + + org.axonframework.extensions.kotlin + axon-kotlin-parent + 0.2.0-SNAPSHOT + + + axon-kotlin-springboot-autoconfigure + + + + + org.axonframework.extensions.kotlin + axon-kotlin + ${project.version} + + + + org.springframework + spring-core + + + org.springframework + spring-context + + + org.springframework.boot + spring-boot + + + org.springframework.boot + spring-boot-autoconfigure + + + org.axonframework + axon-modelling + + + org.axonframework + axon-configuration + + + io.github.microutils + kotlin-logging + + + diff --git a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregateWithImmutableIdentifier.kt b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregateWithImmutableIdentifier.kt new file mode 100644 index 0000000..11543a5 --- /dev/null +++ b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregateWithImmutableIdentifier.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extension.kotlin.spring + +import org.axonframework.modelling.command.AggregateRoot + +/** + * Marker for an aggregate with immutable identifier. + * + * @since 0.2.0 + * @author Simon Zambrovski + */ +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CLASS) +@MustBeDocumented +@AggregateRoot +annotation class AggregateWithImmutableIdentifier \ No newline at end of file diff --git a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt new file mode 100644 index 0000000..9c46334 --- /dev/null +++ b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extension.kotlin.spring + +import mu.KLogging +import org.axonframework.config.AggregateConfigurer.defaultConfiguration +import org.axonframework.config.Configurer +import org.axonframework.eventsourcing.EventSourcingRepository +import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter +import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingIdentifier +import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingStringIdentifier +import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingUUIDIdentifier +import org.axonframework.extensions.kotlin.aggregate.EventSourcingAggregateWithImmutableIdentifierRepository +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.context.ApplicationContext +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.util.* + +/** + * Configuration to activate the aggregate with immutable identifier detection and registration of the corresponding factories and repositories. + * @see EnableAggregateWithImmutableIdentifierScan for activation. + * + * @author Simon Zambrovski + * @since 0.2.0 + */ +@Configuration +open class AggregatesWithImmutableIdentifierConfiguration( + private val context: ApplicationContext +) { + + companion object : KLogging() + + /** + * Initializes the settings. + * @return settings. + */ + @Bean + @ConditionalOnMissingBean + open fun initialize(): AggregatesWithImmutableIdentifierSettings { + val beans = context.getBeansWithAnnotation(EnableAggregateWithImmutableIdentifierScan::class.java) + require(beans.isNotEmpty()) { + "EnableAggregateWithImmutableIdentifierScan should be activated exactly once." + } + require(beans.size == 1) { + "EnableAggregateWithImmutableIdentifierScan should be activated exactly once, but was found on ${beans.size} beans:\n" + beans.map { it.key }.joinToString() + } + val basePackage = EnableAggregateWithImmutableIdentifierScan.getBasePackage(beans.entries.first().value) + return AggregatesWithImmutableIdentifierSettings(basePackage = basePackage + ?: throw IllegalStateException("Required setting basePackage could not be initialized, consider to provide your own AggregatesWithImmutableIdentifierSettings.") + ) + } + + @Autowired + fun configureAggregates( + configurer: Configurer, + settings: AggregatesWithImmutableIdentifierSettings, + @Autowired(required = false) identifierConverters: List>? + ) { + val converters = identifierConverters ?: emptyList() // fallback to empty list if none are defined + + logger.info { "Discovered ${converters.size} converters for aggregate identifiers." } + logger.info { "Scanning ${settings.basePackage} for aggregates" } + AggregateWithImmutableIdentifier::class + .findAnnotatedAggregateClasses(settings.basePackage) + .map { aggregateClazz -> + configurer.configureAggregate( + defaultConfiguration(aggregateClazz.java) + .configureRepository { config -> + EventSourcingAggregateWithImmutableIdentifierRepository( + builder = EventSourcingRepository + .builder(aggregateClazz.java) + .eventStore(config.eventStore()) + .aggregateFactory( + when (val idFieldClazz = aggregateClazz.extractAggregateIdentifierClass()) { + String::class -> usingStringIdentifier(aggregateClazz) + UUID::class -> usingUUIDIdentifier(aggregateClazz) + else -> usingIdentifier(aggregateClazz, idFieldClazz, converters.findIdentifierConverter(idFieldClazz)) + }.also { + logger.info { "Registering aggregate factory $it" } + } + ) + ) + } + ) + } + } + + +} + diff --git a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierSettings.kt b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierSettings.kt new file mode 100644 index 0000000..8e8b4b0 --- /dev/null +++ b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierSettings.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extension.kotlin.spring + +/** + * Settings class to pass values into configuration of aggregate scan. + * @param basePackage base package of scan. + * + * @author Simon Zambrovski + * @since 0.2.0 + */ +data class AggregatesWithImmutableIdentifierSettings( + val basePackage: String +) \ No newline at end of file diff --git a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/EnableAggregateWithImmutableIdentifierScan.kt b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/EnableAggregateWithImmutableIdentifierScan.kt new file mode 100644 index 0000000..93c7201 --- /dev/null +++ b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/EnableAggregateWithImmutableIdentifierScan.kt @@ -0,0 +1,40 @@ +package org.axonframework.extension.kotlin.spring + +import org.springframework.context.annotation.Import +import org.springframework.core.annotation.AnnotationUtils + +/** + * Annotation to enable aggregate scan. + * @param basePackage specifies the package to scan for aggregates, if not specified, defaults to base package of the annotated class. + * + * @author Simon Zambrovski + * @since 0.2.0 + */ +@MustBeDocumented +@Target(AnnotationTarget.CLASS, AnnotationTarget.ANNOTATION_CLASS) +@Import(AggregatesWithImmutableIdentifierConfiguration::class) +annotation class EnableAggregateWithImmutableIdentifierScan( + val basePackage: String = NULL_VALUE +) { + companion object { + + /** + * Null value to allow package scan on bean. + */ + const val NULL_VALUE = "" + + /** + * Reads base package from annotation or, if not provided from the annotated class. + * @param bean annotated bean + * @return base package or null if not defined and can't be read. + */ + fun getBasePackage(bean: Any): String? { + val annotation = AnnotationUtils.findAnnotation(bean::class.java, EnableAggregateWithImmutableIdentifierScan::class.java) ?: return null + return if (annotation.basePackage == NULL_VALUE) { + bean::class.java.`package`.name + } else { + annotation.basePackage + } + } + } +} \ No newline at end of file diff --git a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/ReflectionExtensions.kt b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/ReflectionExtensions.kt new file mode 100644 index 0000000..6ebf8e5 --- /dev/null +++ b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/ReflectionExtensions.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extension.kotlin.spring + +import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider +import org.springframework.core.type.filter.AnnotationTypeFilter +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter +import kotlin.reflect.jvm.jvmErasure + +/** + * Scans classpath for annotated aggregate classes. + * @param scanPackage package to scan. + * @param annotationClazz annotation class that is used on classes. + * @return list of annotated classes. + */ +internal fun KClass.findAnnotatedAggregateClasses(scanPackage: String): List> { + val provider = ClassPathScanningCandidateComponentProvider(false) + provider.addIncludeFilter(AnnotationTypeFilter(this.java)) + return provider.findCandidateComponents(scanPackage).map { + @Suppress("UNCHECKED_CAST") + Class.forName(it.beanClassName).kotlin as KClass + } +} + +/** + * Extracts the class of aggregate identifier from an aggregate. + * @param aggregateClazz aggregate class. + * @return class of aggregate identifier. + * @throws IllegalArgumentException if the required constructor is not found. + */ +internal fun KClass<*>.extractAggregateIdentifierClass(): KClass { + + /** + * Holder for constructor and its parameters. + * @param constructor constructor to holf info for. + */ + data class ConstructorParameterInfo(val constructor: KFunction) { + private val valueProperties by lazy { constructor.parameters.filter { it.kind == KParameter.Kind.VALUE } } // collect only "val" properties + + /** + * Check if the provided constructor has only one value parameter. + */ + fun isConstructorWithOneValue() = valueProperties.size == 1 + + /** + * Retrieves the class of value parameter. + * @return class of value. + */ + fun getParameterClass(): KClass<*> = valueProperties[0].type.jvmErasure + } + + val constructors = this.constructors.map { ConstructorParameterInfo(it) }.filter { it.isConstructorWithOneValue() } // exactly one parameter in primary constructor + require(constructors.size == 1) { "Expected exactly one constructor with aggregate identifier parameter, but found ${constructors.size}." } + @Suppress("UNCHECKED_CAST") + return constructors[0].getParameterClass() as KClass +} + + +/** + * Extension function to find a matching converter for provided identifier class. + * @param idClazz class of identifier to look for. + * @return a matching converter. + * @throws IllegalArgumentException if converter can not be identified (none or more than one are defined). + */ +internal fun List>.findIdentifierConverter(idClazz: KClass): AggregateIdentifierConverter { + val converters = this.filter { + idClazz == it.getConverterIdentifierClass() + }.map { + @Suppress("UNCHECKED_CAST") + it as AggregateIdentifierConverter + } + require(converters.isNotEmpty()) { + "Could not find an AggregateIdentifierConverter for ${idClazz.qualifiedName}. Consider to register a bean implementing AggregateIdentifierConverter<${idClazz.qualifiedName}>" + } + require(converters.size == 1) { + "Found more than one AggregateIdentifierConverter for ${idClazz.qualifiedName}. This is currently not supported." + } + return converters.first() +} + +/** + * Returns the concrete class of ID. + * @return class of aggregate identifier or null if it can't be resolved. + */ +internal fun AggregateIdentifierConverter<*>.getConverterIdentifierClass() = this::class.supertypes.first { superTypes -> superTypes.classifier == AggregateIdentifierConverter::class }.arguments[0].type?.jvmErasure diff --git a/kotlin/pom.xml b/kotlin/pom.xml index 68c3571..a02df73 100644 --- a/kotlin/pom.xml +++ b/kotlin/pom.xml @@ -35,5 +35,10 @@ axon-configuration provided + + org.slf4j + slf4j-simple + test + diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateConfigurerExtensions.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateConfigurerExtensions.kt new file mode 100644 index 0000000..6774c38 --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateConfigurerExtensions.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.axonframework.extensions.kotlin.aggregate + +import org.axonframework.common.jpa.EntityManagerProvider +import org.axonframework.config.AggregateConfigurer +import org.axonframework.config.AggregateConfigurer.jpaMappedConfiguration + +/** + * Creates default aggregate configurer with a usage of a reified type information. + * @param [A] type of aggregate. + */ +inline fun defaultAggregateConfiguration(): AggregateConfigurer = AggregateConfigurer.defaultConfiguration(A::class.java) + +/** + * Creates JPA-mapped aggregate configurer with a usage of a reified type information. + * @param [A] type of aggregate. + */ +inline fun jpaMappedAggregateConfiguration(): AggregateConfigurer = jpaMappedConfiguration(A::class.java) + +/** + * Creates JPA-mapped aggregate configurer with a usage of a reified type information. + * @param entityManagerProvider entity manager provider. + * @param [A] type of aggregate. + */ +inline fun jpaMappedAggregateConfiguration(entityManagerProvider: EntityManagerProvider): AggregateConfigurer = jpaMappedConfiguration(A::class.java, entityManagerProvider) \ No newline at end of file diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateIdentifierConverter.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateIdentifierConverter.kt new file mode 100644 index 0000000..e5257b3 --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateIdentifierConverter.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.axonframework.extensions.kotlin.aggregate + +import java.util.* +import java.util.function.Function + +/** + * Defines a converter from a string to custom identifier type. + * @param [ID] type of aggregate identifier. + * + * @author Simon Zambrovski + * @since 0.2.0 + */ +interface AggregateIdentifierConverter : Function { + + /** + * Default string converter. + */ + object DefaultString : AggregateIdentifierConverter { + override fun apply(it: String): String = it + override fun toString(): String = this::class.qualifiedName!! + } + + /** + * Default UUID converter. + */ + object DefaultUUID : AggregateIdentifierConverter { + override fun apply(it: String): UUID = UUID.fromString(it) + override fun toString(): String = this::class.qualifiedName!! + } +} \ No newline at end of file diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt new file mode 100644 index 0000000..ba948cd --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2010-2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.axonframework.extensions.kotlin.aggregate + +import org.axonframework.eventhandling.DomainEventMessage +import org.axonframework.eventsourcing.AggregateFactory +import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter.* +import java.util.* +import kotlin.reflect.KClass + +/** + * Factory to create aggregates [A] with immutable aggregate identifier of type [ID]. + * @constructor creates aggregate factory. + * @param clazz aggregate class. + * @param idClazz aggregate identifier class. + * @param idExtractor function to convert aggregate identifier from string to [ID]. + * @param [A] aggregate type. + * @param [ID] aggregate identifier type. + * + * @since 0.2.0 + * @author Simon Zambrovski + */ +data class AggregateWithImmutableIdentifierFactory( + private val clazz: KClass, + private val idClazz: KClass, + private val idExtractor: AggregateIdentifierConverter +) : AggregateFactory { + + companion object { + + /** + * Reified factory method for aggregate factory using string as aggregate identifier. + * @return instance of AggregateWithImmutableIdentifierFactory + */ + inline fun usingStringIdentifier() = usingIdentifier(String::class) { it } + + /** + * Factory method for aggregate factory using string as aggregate identifier. + * @return instance of AggregateWithImmutableIdentifierFactory + */ + fun usingStringIdentifier(clazz: KClass) = usingIdentifier(clazz, String::class, DefaultString) + + /** + * Reified factory method for aggregate factory using UUID as aggregate identifier. + * @return instance of AggregateWithImmutableIdentifierFactory + */ + inline fun usingUUIDIdentifier() = usingIdentifier(UUID::class, DefaultUUID::apply) + + /** + * Factory method for aggregate factory using UUID as aggregate identifier. + * @return instance of AggregateWithImmutableIdentifierFactory + */ + fun usingUUIDIdentifier(clazz: KClass) = usingIdentifier(clazz, UUID::class, DefaultUUID) + + /** + * Reified factory method for aggregate factory using specified identifier type and converter function. + * @param idClazz identifier class. + * @param idExtractor extractor function for identifier from string. + * @return instance of AggregateWithImmutableIdentifierFactory + */ + inline fun usingIdentifier(idClazz: KClass, noinline idExtractor: (String) -> ID) = + AggregateWithImmutableIdentifierFactory(clazz = A::class, idClazz = idClazz, idExtractor = object : AggregateIdentifierConverter { + override fun apply(it: String): ID = idExtractor(it) + }) + + /** + * Factory method for aggregate factory using specified identifier type and converter. + * @param idClazz identifier class. + * @param idExtractor extractor for identifier from string. + * @return instance of AggregateWithImmutableIdentifierFactory + */ + fun usingIdentifier(aggregateClazz: KClass, idClazz: KClass, idExtractor: AggregateIdentifierConverter) = + AggregateWithImmutableIdentifierFactory(clazz = aggregateClazz, idClazz = idClazz, idExtractor = idExtractor) + } + + + @Throws(IllegalArgumentException::class) + override fun createAggregateRoot(aggregateIdentifier: String, message: DomainEventMessage<*>?): A { + + val constructor = invokeReporting( + "The aggregate [${clazz.java.name}] doesn't provide a constructor for the identifier type [${idClazz.java.name}]." + ) { clazz.java.getConstructor(idClazz.java) } + + val id: ID = invokeReporting( + "The identifier [$aggregateIdentifier] could not be converted to the type [${idClazz.java.name}], required for the ID of aggregate [${clazz.java.name}]." + ) { idExtractor.apply(aggregateIdentifier) } + + return constructor.newInstance(id) + } + + override fun getAggregateType(): Class = clazz.java +} + +/** + * Tries to execute the given function or reports an error on failure. + * @param errorMessage message to report on error. + * @param function: function to invoke + */ +@Throws(IllegalArgumentException::class) +private fun invokeReporting(errorMessage: String, function: () -> T): T { + return try { + function.invoke() + } catch (e: Exception) { + throw IllegalArgumentException(errorMessage, e) + } +} diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepository.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepository.kt new file mode 100644 index 0000000..f5a151b --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepository.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extensions.kotlin.aggregate + +import org.axonframework.eventsourcing.EventSourcedAggregate +import org.axonframework.eventsourcing.EventSourcingRepository +import org.axonframework.messaging.unitofwork.CurrentUnitOfWork +import org.axonframework.modelling.command.Aggregate +import org.axonframework.modelling.command.LockAwareAggregate +import java.util.concurrent.Callable + +/** + * Event souring repository which uses a aggregate factory to create new aggregate passing null as first event. + * @param builder repository builder with configuration. + * + * @since 0.2.0 + * @author Simon Zambrovski + */ +class EventSourcingAggregateWithImmutableIdentifierRepository( + builder: Builder +) : EventSourcingRepository(builder) { + + override fun loadOrCreate(aggregateIdentifier: String, factoryMethod: Callable): Aggregate { + val factory = super.getAggregateFactory() + val uow = CurrentUnitOfWork.get() + val aggregates: MutableMap>> = managedAggregates(uow) + val aggregate = aggregates.computeIfAbsent(aggregateIdentifier) { aggregateId: String -> + try { + return@computeIfAbsent doLoadOrCreate(aggregateId) { + // call the factory and instead of newInstance on the aggregate class + factory.createAggregateRoot(aggregateId, null) + } + } catch (e: RuntimeException) { + throw e + } catch (e: Exception) { + throw RuntimeException(e) + } + } + uow.onRollback { aggregates.remove(aggregateIdentifier) } + prepareForCommit(aggregate) + return aggregate + } +} \ No newline at end of file diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableAggregateIdentifier.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableAggregateIdentifier.kt new file mode 100644 index 0000000..ddde09e --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableAggregateIdentifier.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extensions.kotlin.aggregate + +import org.axonframework.modelling.command.AggregateIdentifier + +/** + * Marker for an immutable aggregate identifier in the aggregate. + * + * @since 0.2.0 + * @author Simon Zambrovski + */ +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FIELD) +@AggregateIdentifier +@MustBeDocumented +annotation class ImmutableAggregateIdentifier \ No newline at end of file diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/QueryUpdateEmitterExtensionsTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/QueryUpdateEmitterExtensionsTest.kt index 6e98ad0..73ac53c 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/QueryUpdateEmitterExtensionsTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/QueryUpdateEmitterExtensionsTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2010-2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin import io.mockk.every diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactoryTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactoryTest.kt new file mode 100644 index 0000000..0e15317 --- /dev/null +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactoryTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extensions.kotlin.aggregate + +import org.axonframework.extensions.kotlin.TestLongAggregate +import org.axonframework.extensions.kotlin.TestStringAggregate +import org.axonframework.extensions.kotlin.TestUUIDAggregate +import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingIdentifier +import java.util.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + + +/** + * Test for the aggregate factory. + * + * @author Simon Zambrovski + */ +internal class AggregateWithImmutableIdentifierFactoryTest { + + @Test + fun `should create string aggregate`() { + val aggregateId = UUID.randomUUID().toString() + val factory = AggregateWithImmutableIdentifierFactory.usingStringIdentifier() + val aggregate = factory.createAggregateRoot(aggregateId, null) + + assertEquals(aggregateId, aggregate.aggregateId) + } + + @Test + fun `should create uuid aggregate`() { + val aggregateId = UUID.randomUUID() + val factory: AggregateWithImmutableIdentifierFactory = usingIdentifier(UUID::class) { UUID.fromString(it) } + val aggregate = factory.createAggregateRoot(aggregateId.toString(), null) + + assertEquals(aggregateId, aggregate.aggregateId) + } + + @Test + fun `should fail create aggregate with wrong constructor type`() { + val aggregateId = UUID.randomUUID() + // pretending the TestLongAggregate to have UUID as identifier. + val factory: AggregateWithImmutableIdentifierFactory = usingIdentifier(UUID::class) { UUID.fromString(it) } + + val exception = assertFailsWith { + factory.createAggregateRoot(aggregateId.toString(), null) + } + + assertEquals(exception.message, + "The aggregate [${factory.aggregateType.name}] doesn't provide a constructor for the identifier type [${UUID::class.java.name}].") + } + + @Test + fun `should fail create aggregate error in extractor`() { + val aggregateId = UUID.randomUUID() + // the extractor is broken. + val factory: AggregateWithImmutableIdentifierFactory = usingIdentifier(UUID::class) { throw java.lang.IllegalArgumentException("") } + + val exception = assertFailsWith { + factory.createAggregateRoot(aggregateId.toString(), null) + } + + assertEquals(exception.message, + "The identifier [$aggregateId] could not be converted to the type [${UUID::class.java.name}], required for the ID of aggregate [${factory.aggregateType.name}].") + } + +} \ No newline at end of file diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepositoryTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepositoryTest.kt new file mode 100644 index 0000000..4c67477 --- /dev/null +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepositoryTest.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.axonframework.extensions.kotlin.aggregate + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.axonframework.commandhandling.GenericCommandMessage +import org.axonframework.eventsourcing.AggregateFactory +import org.axonframework.eventsourcing.EventSourcingRepository +import org.axonframework.eventsourcing.eventstore.DomainEventStream +import org.axonframework.eventsourcing.eventstore.EventStore +import org.axonframework.extensions.kotlin.TestStringAggregate +import org.axonframework.messaging.unitofwork.DefaultUnitOfWork +import org.axonframework.messaging.unitofwork.UnitOfWork +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.* +import java.util.concurrent.Callable +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.fail + +/** + * Test for the aggregate repository using the factory. + * + * @author Simon Zambrovski + */ +internal class EventSourcingAggregateWithImmutableIdentifierRepositoryTest { + + private val aggregateIdentifier = UUID.randomUUID().toString() + private val eventStore = mockk() + private val aggregateFactory = mockk>() + private lateinit var uow: DefaultUnitOfWork<*> + + @BeforeTest + fun `init components`() { + uow = DefaultUnitOfWork.startAndGet(GenericCommandMessage("some payload")) + + every { aggregateFactory.aggregateType }.returns(TestStringAggregate::class.java) + every { aggregateFactory.createAggregateRoot(aggregateIdentifier, null) }.returns(TestStringAggregate(aggregateIdentifier)) + + // no events + every { eventStore.readEvents(aggregateIdentifier) }.returns(DomainEventStream.empty()) + } + + @AfterTest + fun `check uow`() { + assertEquals(UnitOfWork.Phase.STARTED, uow.phase()) + } + + @Test + fun `should ask factory to create the aggregate`() { + + + val repo = EventSourcingAggregateWithImmutableIdentifierRepository( + EventSourcingRepository + .builder(TestStringAggregate::class.java) + .eventStore(eventStore) + .aggregateFactory(aggregateFactory) + ) + + val factoryMethod = Callable { + fail("The factory method should not be called.") + } + val aggregate = repo.loadOrCreate(aggregateIdentifier = aggregateIdentifier, factoryMethod = factoryMethod) + + assertEquals(aggregateIdentifier, aggregate.identifierAsString()) + verify { + aggregateFactory.createAggregateRoot(aggregateIdentifier, null) + } + + } +} \ No newline at end of file diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/testObjects.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/testObjects.kt index 99c8961..c285d36 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/testObjects.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/testObjects.kt @@ -1,6 +1,23 @@ +/* + * Copyright (c) 2010-2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin +import org.axonframework.extensions.kotlin.aggregate.ImmutableAggregateIdentifier import org.axonframework.modelling.command.TargetAggregateIdentifier +import java.util.* /** * Simple Query class to be used in tests. @@ -11,3 +28,18 @@ internal data class ExampleQuery(val value: Number) * Simple Command class to be used in tests. */ internal data class ExampleCommand(@TargetAggregateIdentifier val id: String) + +/** + * Immutable aggregate with String identifier. + */ +internal data class TestStringAggregate(@ImmutableAggregateIdentifier val aggregateId: String) + +/** + * Immutable aggregate with UUID identifier. + */ +internal data class TestUUIDAggregate(@ImmutableAggregateIdentifier val aggregateId: UUID) + +/** + * Immutable aggregate with Long identifier. + */ +internal data class TestLongAggregate(@ImmutableAggregateIdentifier val aggregateId: Long) \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5d1918b..2e27ef4 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,8 @@ kotlin + kotlin-springboot-autoconfigure + kotlin-springboot-starter @@ -40,10 +42,39 @@ 1.7.30 2.13.3 0.10.1 + 2.3.2.RELEASE + + + ${project.groupId} + axon-kotlin + ${project.version} + + + + ${project.groupId} + axon-kotlin-springboot-autoconfigure + ${project.version} + + + + ${project.groupId} + axon-kotlin-springboot-starter + ${project.version} + + + + + org.springframework.boot + spring-boot-dependencies + ${springboot.version} + import + pom + + org.jetbrains.kotlin kotlin-bom @@ -57,6 +88,11 @@ axon-configuration ${axon.version} + + org.axonframework + axon-modelling + ${axon.version} + org.axonframework axon-test @@ -237,6 +273,18 @@ + + example + + + !skipExamples + + + + kotlin-example + + + From 63944ac7f49092794b5275f21d5719b7373c0390 Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Wed, 23 Sep 2020 00:41:33 +0200 Subject: [PATCH 02/11] forgot starter --- .java-version | 1 + kotlin-springboot-starter/pom.xml | 43 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 .java-version create mode 100644 kotlin-springboot-starter/pom.xml diff --git a/.java-version b/.java-version new file mode 100644 index 0000000..6259340 --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +1.8 diff --git a/kotlin-springboot-starter/pom.xml b/kotlin-springboot-starter/pom.xml new file mode 100644 index 0000000..5b09fd9 --- /dev/null +++ b/kotlin-springboot-starter/pom.xml @@ -0,0 +1,43 @@ + + + + + 4.0.0 + + Axon Framework - Kotlin Extension SpringBoot Starter + Module for the Kotlin Extension SpringBoot Starter of Axon Framework + + + org.axonframework.extensions.kotlin + axon-kotlin-parent + 0.2.0-SNAPSHOT + + + axon-kotlin-springboot-starter + + + + org.axonframework.extensions.kotlin + axon-kotlin-springboot-autoconfigure + ${project.version} + + + org.springframework.boot + spring-boot-starter + + + From b9b28798534346ff5a5c4589c5c7d69c472c6223 Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Thu, 24 Sep 2020 18:45:21 +0200 Subject: [PATCH 03/11] move example, all-open for spring --- .../example/AxonKotlinExampleApplication.kt | 15 -------------- .../kotlin/example/core/BankAccount.kt | 20 +++++++++++++++++++ pom.xml | 1 + 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt index f711b75..9e93b61 100644 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt @@ -40,21 +40,6 @@ class AxonKotlinExampleApplication { companion object : KLogging() -// @Autowired -// fun manualConfigure(configurer: Configurer) { -// configurer.configureAggregate( -// defaultAggregateConfiguration() -// .configureRepository { config -> -// EventSourcingAggregateWithImmutableIdentifierRepository( -// EventSourcingRepository -// .builder(BankAccount::class.java) -// .eventStore(config.eventStore()) -// .aggregateFactory(AggregateWithImmutableIdentifierFactory.usingIdentifier(UUID::class) { UUID.fromString(it) }) -// ) -// } -// ) -// } - /** * Configures to use in-memory embedded event store. */ diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt index a83b694..b456e6f 100644 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt @@ -38,6 +38,26 @@ import java.util.* */ @Configuration class BankConfiguration(val bankAccountService: BankAccountService) { + + +// /** +// * Example of manual configuration. +// */ +// @Autowired +// fun manualConfigure(configurer: Configurer) { +// configurer.configureAggregate( +// defaultAggregateConfiguration() +// .configureRepository { config -> +// EventSourcingAggregateWithImmutableIdentifierRepository( +// EventSourcingRepository +// .builder(BankAccount::class.java) +// .eventStore(config.eventStore()) +// .aggregateFactory(usingIdentifier(UUID::class) { UUID.fromString(it) }) +// ) +// } +// ) +// } + /** * Application runner of bank ops. */ diff --git a/pom.xml b/pom.xml index 1922138..1c59254 100644 --- a/pom.xml +++ b/pom.xml @@ -493,6 +493,7 @@ no-arg + spring all-open From d1a3d31a327a838507b3b9115ebb6975aab57bda Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Wed, 30 Sep 2020 11:55:35 +0200 Subject: [PATCH 04/11] get rid of unneeded annotation --- .../extension/kotlin/example/spring/Foo.kt | 2 ++ .../kotlin/example/spring/FooFactory.kt | 2 ++ .../kotlin/example/spring/MyFooConsumer.kt | 2 ++ .../aggregate/AggregateCreationCallback.kt | 5 ++++ .../aggregate/AggregateFactoryMethod.kt | 2 ++ .../aggregate/ImmutableAggregateIdentifier.kt | 30 ------------------- 6 files changed, 13 insertions(+), 30 deletions(-) create mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/Foo.kt create mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/FooFactory.kt create mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/MyFooConsumer.kt create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt delete mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableAggregateIdentifier.kt diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/Foo.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/Foo.kt new file mode 100644 index 0000000..ef09037 --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/Foo.kt @@ -0,0 +1,2 @@ +package org.axonframework.extension.kotlin.example.spring + diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/FooFactory.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/FooFactory.kt new file mode 100644 index 0000000..ef09037 --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/FooFactory.kt @@ -0,0 +1,2 @@ +package org.axonframework.extension.kotlin.example.spring + diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/MyFooConsumer.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/MyFooConsumer.kt new file mode 100644 index 0000000..ef09037 --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/MyFooConsumer.kt @@ -0,0 +1,2 @@ +package org.axonframework.extension.kotlin.example.spring + diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt new file mode 100644 index 0000000..563465f --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt @@ -0,0 +1,5 @@ +package org.axonframework.extensions.kotlin.aggregate + +interface AggregateCreationCallback { + fun aggregateCreated(aggregateInstance: A): Unit +} \ No newline at end of file diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt new file mode 100644 index 0000000..67435be --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt @@ -0,0 +1,2 @@ +package org.axonframework.extensions.kotlin.aggregate + diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableAggregateIdentifier.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableAggregateIdentifier.kt deleted file mode 100644 index ddde09e..0000000 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableAggregateIdentifier.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2020. Axon Framework - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.axonframework.extensions.kotlin.aggregate - -import org.axonframework.modelling.command.AggregateIdentifier - -/** - * Marker for an immutable aggregate identifier in the aggregate. - * - * @since 0.2.0 - * @author Simon Zambrovski - */ -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.FIELD) -@AggregateIdentifier -@MustBeDocumented -annotation class ImmutableAggregateIdentifier \ No newline at end of file From 9818081c745c13d3a64a910b74b29280fc90087f Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Wed, 30 Sep 2020 11:55:39 +0200 Subject: [PATCH 05/11] socs --- .../extension/kotlin/example/AxonKotlinExampleApplication.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt index 9e93b61..051abbf 100644 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt @@ -34,6 +34,9 @@ fun main(args: Array) { SpringApplication.run(AxonKotlinExampleApplication::class.java, *args) } +/** + * Main example application class. + */ @SpringBootApplication @EnableAggregateWithImmutableIdentifierScan class AxonKotlinExampleApplication { From d77d1d2db3e3a0bf4c1996b91c18a55b26c881d9 Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Wed, 30 Sep 2020 11:56:23 +0200 Subject: [PATCH 06/11] adopted to changes --- .../kotlin/example/core/BankAccount.kt | 4 ++-- .../kotlin/example/core/BankAccountAdvanced.kt | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt index b456e6f..56d7c2c 100644 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt @@ -22,9 +22,9 @@ import org.axonframework.eventsourcing.EventSourcingHandler import org.axonframework.extension.kotlin.example.AxonKotlinExampleApplication import org.axonframework.extension.kotlin.example.api.* import org.axonframework.extension.kotlin.spring.AggregateWithImmutableIdentifier -import org.axonframework.extensions.kotlin.aggregate.ImmutableAggregateIdentifier import org.axonframework.extensions.kotlin.send import org.axonframework.modelling.command.AggregateCreationPolicy +import org.axonframework.modelling.command.AggregateIdentifier import org.axonframework.modelling.command.AggregateLifecycle.apply import org.axonframework.modelling.command.CreationPolicy import org.springframework.boot.ApplicationRunner @@ -104,7 +104,7 @@ class BankAccountService(val commandGateway: CommandGateway) { */ @AggregateWithImmutableIdentifier data class BankAccount( - @ImmutableAggregateIdentifier + @AggregateIdentifier private val id: UUID ) { diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt index 25de56b..0ac3b7a 100644 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt @@ -23,15 +23,14 @@ import org.axonframework.extension.kotlin.example.api.AdvancedBankAccountCreated import org.axonframework.extension.kotlin.example.api.CreateAdvancedBankAccountCommand import org.axonframework.extension.kotlin.spring.AggregateWithImmutableIdentifier import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter -import org.axonframework.extensions.kotlin.aggregate.ImmutableAggregateIdentifier import org.axonframework.extensions.kotlin.send import org.axonframework.modelling.command.AggregateCreationPolicy +import org.axonframework.modelling.command.AggregateIdentifier import org.axonframework.modelling.command.AggregateLifecycle.apply import org.axonframework.modelling.command.CreationPolicy import org.springframework.boot.ApplicationRunner import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.core.annotation.Order import org.springframework.stereotype.Service import java.util.* @@ -84,11 +83,11 @@ class AdvancedBankAccountService(val commandGateway: CommandGateway) { logger.info { "\nPerforming advanced operations on account $accountIdAdvanced" } commandGateway.send( - command = CreateAdvancedBankAccountCommand(accountIdAdvanced, 100), - onSuccess = { _, result: Any?, _ -> - logger.info { "Successfully created account with id: $result" } - }, - onError = { c, e, _ -> logger.error(e) { "Error creating account ${c.payload.bankAccountId}" } } + command = CreateAdvancedBankAccountCommand(accountIdAdvanced, 100), + onSuccess = { _, result: Any?, _ -> + logger.info { "Successfully created account with id: $result" } + }, + onError = { c, e, _ -> logger.error(e) { "Error creating account ${c.payload.bankAccountId}" } } ) } } @@ -106,8 +105,8 @@ data class BankAccountIdentifier(val id: String) { */ @AggregateWithImmutableIdentifier data class BankAccountAdvanced( - @ImmutableAggregateIdentifier - private val id: BankAccountIdentifier + @AggregateIdentifier + private val id: BankAccountIdentifier ) { private var overdraftLimit: Long = 0 From 7f6c3ad4fe564191e6edf5544e85471971ebe19d Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Wed, 30 Sep 2020 11:57:03 +0200 Subject: [PATCH 07/11] creation callbacks --- ...tesWithImmutableIdentifierConfiguration.kt | 70 +++++++++++-------- .../aggregate/AggregateCreationCallback.kt | 27 ++++++- 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt index 9c46334..0a813ad 100644 --- a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt +++ b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt @@ -19,7 +19,9 @@ import mu.KLogging import org.axonframework.config.AggregateConfigurer.defaultConfiguration import org.axonframework.config.Configurer import org.axonframework.eventsourcing.EventSourcingRepository +import org.axonframework.extensions.kotlin.aggregate.AggregateCreationCallback import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter +import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingIdentifier import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingStringIdentifier import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingUUIDIdentifier @@ -39,8 +41,8 @@ import java.util.* * @since 0.2.0 */ @Configuration -open class AggregatesWithImmutableIdentifierConfiguration( - private val context: ApplicationContext +class AggregatesWithImmutableIdentifierConfiguration( + private val context: ApplicationContext ) { companion object : KLogging() @@ -51,7 +53,7 @@ open class AggregatesWithImmutableIdentifierConfiguration( */ @Bean @ConditionalOnMissingBean - open fun initialize(): AggregatesWithImmutableIdentifierSettings { + fun initialize(): AggregatesWithImmutableIdentifierSettings { val beans = context.getBeansWithAnnotation(EnableAggregateWithImmutableIdentifierScan::class.java) require(beans.isNotEmpty()) { "EnableAggregateWithImmutableIdentifierScan should be activated exactly once." @@ -61,45 +63,53 @@ open class AggregatesWithImmutableIdentifierConfiguration( } val basePackage = EnableAggregateWithImmutableIdentifierScan.getBasePackage(beans.entries.first().value) return AggregatesWithImmutableIdentifierSettings(basePackage = basePackage - ?: throw IllegalStateException("Required setting basePackage could not be initialized, consider to provide your own AggregatesWithImmutableIdentifierSettings.") + ?: throw IllegalStateException("Required setting basePackage could not be initialized, consider to provide your own AggregatesWithImmutableIdentifierSettings.") ) } @Autowired fun configureAggregates( - configurer: Configurer, - settings: AggregatesWithImmutableIdentifierSettings, - @Autowired(required = false) identifierConverters: List>? + configurer: Configurer, + settings: AggregatesWithImmutableIdentifierSettings, + @Autowired(required = false) identifierConverters: List>? ) { val converters = identifierConverters ?: emptyList() // fallback to empty list if none are defined logger.info { "Discovered ${converters.size} converters for aggregate identifiers." } logger.info { "Scanning ${settings.basePackage} for aggregates" } + AggregateWithImmutableIdentifier::class - .findAnnotatedAggregateClasses(settings.basePackage) - .map { aggregateClazz -> - configurer.configureAggregate( - defaultConfiguration(aggregateClazz.java) - .configureRepository { config -> - EventSourcingAggregateWithImmutableIdentifierRepository( - builder = EventSourcingRepository - .builder(aggregateClazz.java) - .eventStore(config.eventStore()) - .aggregateFactory( - when (val idFieldClazz = aggregateClazz.extractAggregateIdentifierClass()) { - String::class -> usingStringIdentifier(aggregateClazz) - UUID::class -> usingUUIDIdentifier(aggregateClazz) - else -> usingIdentifier(aggregateClazz, idFieldClazz, converters.findIdentifierConverter(idFieldClazz)) - }.also { - logger.info { "Registering aggregate factory $it" } - } - ) - ) - } - ) - } - } + .findAnnotatedAggregateClasses(settings.basePackage) + .map { aggregateClazz -> + + val aggregateFactory = when (val idFieldClazz = aggregateClazz.extractAggregateIdentifierClass()) { + String::class -> usingStringIdentifier(aggregateClazz) + UUID::class -> usingUUIDIdentifier(aggregateClazz) + else -> usingIdentifier(aggregateClazz, idFieldClazz, converters.findIdentifierConverter(idFieldClazz)) + }.also { + // logging callback + it.registerCallback(object : AggregateCreationCallback { + override fun aggregateCreated(aggregateInstance: Any, aggregateFactory: AggregateWithImmutableIdentifierFactory<*, *>) { + logger.trace { "Aggregate factory $aggregateFactory just created $aggregateInstance" } + } + }) + logger.info { "Registering aggregate factory $it" } + } + + configurer.configureAggregate( + defaultConfiguration(aggregateClazz.java) + .configureRepository { config -> + EventSourcingAggregateWithImmutableIdentifierRepository( + builder = EventSourcingRepository + .builder(aggregateClazz.java) + .eventStore(config.eventStore()) + .aggregateFactory(aggregateFactory) + ) + } + ) + } + } } diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt index 563465f..0bc089c 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt @@ -1,5 +1,30 @@ +/* + * Copyright (c) 2010-2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.aggregate +/** + * Informs about creation of an aggregate. + */ +@FunctionalInterface interface AggregateCreationCallback { - fun aggregateCreated(aggregateInstance: A): Unit + + /** + * Callback method called after the aggregate instance has been created. + * @param aggregateInstance aggregate instance. + * @param aggregateFactory factory created the aggregate. + */ + fun aggregateCreated(aggregateInstance: A, aggregateFactory: AggregateWithImmutableIdentifierFactory<*, *>): Unit } \ No newline at end of file From 5af77a88f8857770309c966e6c6f6ca984271d59 Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Wed, 30 Sep 2020 11:57:18 +0200 Subject: [PATCH 08/11] adopted changes --- .../org/axonframework/extensions/kotlin/testObjects.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/testObjects.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/testObjects.kt index c285d36..1c596bd 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/testObjects.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/testObjects.kt @@ -15,7 +15,7 @@ */ package org.axonframework.extensions.kotlin -import org.axonframework.extensions.kotlin.aggregate.ImmutableAggregateIdentifier +import org.axonframework.modelling.command.AggregateIdentifier import org.axonframework.modelling.command.TargetAggregateIdentifier import java.util.* @@ -32,14 +32,14 @@ internal data class ExampleCommand(@TargetAggregateIdentifier val id: String) /** * Immutable aggregate with String identifier. */ -internal data class TestStringAggregate(@ImmutableAggregateIdentifier val aggregateId: String) +internal data class TestStringAggregate(@AggregateIdentifier val aggregateId: String) /** * Immutable aggregate with UUID identifier. */ -internal data class TestUUIDAggregate(@ImmutableAggregateIdentifier val aggregateId: UUID) +internal data class TestUUIDAggregate(@AggregateIdentifier val aggregateId: UUID) /** * Immutable aggregate with Long identifier. */ -internal data class TestLongAggregate(@ImmutableAggregateIdentifier val aggregateId: Long) \ No newline at end of file +internal data class TestLongAggregate(@AggregateIdentifier val aggregateId: Long) \ No newline at end of file From 73cdbe61dc9874b294439431de7858e4e387f654 Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Wed, 30 Sep 2020 11:57:31 +0200 Subject: [PATCH 09/11] add factory method --- .../aggregate/AggregateFactoryMethod.kt | 19 ++++++ ...AggregateWithImmutableIdentifierFactory.kt | 62 ++++++++++++++----- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt index 67435be..d6c6ffe 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt @@ -1,2 +1,21 @@ +/* + * Copyright (c) 2010-2020. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.aggregate +/** + * Type alias for function creating the aggregate from id. + */ +typealias AggregateFactoryMethod = (ID) -> A \ No newline at end of file diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt index ba948cd..540e96c 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt @@ -18,7 +18,8 @@ package org.axonframework.extensions.kotlin.aggregate import org.axonframework.eventhandling.DomainEventMessage import org.axonframework.eventsourcing.AggregateFactory -import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter.* +import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter.DefaultString +import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter.DefaultUUID import java.util.* import kotlin.reflect.KClass @@ -27,6 +28,7 @@ import kotlin.reflect.KClass * @constructor creates aggregate factory. * @param clazz aggregate class. * @param idClazz aggregate identifier class. + * @param aggregateFactoryMethod factory method to create instances, defaults to default constructor of the provided [clazz]. * @param idExtractor function to convert aggregate identifier from string to [ID]. * @param [A] aggregate type. * @param [ID] aggregate identifier type. @@ -35,9 +37,11 @@ import kotlin.reflect.KClass * @author Simon Zambrovski */ data class AggregateWithImmutableIdentifierFactory( - private val clazz: KClass, - private val idClazz: KClass, - private val idExtractor: AggregateIdentifierConverter + val clazz: KClass, + val idClazz: KClass, + val aggregateFactoryMethod: AggregateFactoryMethod = extractConstructorFactory(clazz, idClazz), + val idExtractor: AggregateIdentifierConverter, + val callbacks: MutableSet> = mutableSetOf() ) : AggregateFactory { companion object { @@ -52,19 +56,19 @@ data class AggregateWithImmutableIdentifierFactory( * Factory method for aggregate factory using string as aggregate identifier. * @return instance of AggregateWithImmutableIdentifierFactory */ - fun usingStringIdentifier(clazz: KClass) = usingIdentifier(clazz, String::class, DefaultString) + fun usingStringIdentifier(clazz: KClass) = usingIdentifier(aggregateClazz = clazz, idClazz = String::class, idExtractor = DefaultString) /** * Reified factory method for aggregate factory using UUID as aggregate identifier. * @return instance of AggregateWithImmutableIdentifierFactory */ - inline fun usingUUIDIdentifier() = usingIdentifier(UUID::class, DefaultUUID::apply) + inline fun usingUUIDIdentifier() = usingIdentifier(idClazz = UUID::class, idExtractor = DefaultUUID::apply) /** * Factory method for aggregate factory using UUID as aggregate identifier. * @return instance of AggregateWithImmutableIdentifierFactory */ - fun usingUUIDIdentifier(clazz: KClass) = usingIdentifier(clazz, UUID::class, DefaultUUID) + fun usingUUIDIdentifier(clazz: KClass) = usingIdentifier(aggregateClazz = clazz, idClazz = UUID::class, idExtractor = DefaultUUID) /** * Reified factory method for aggregate factory using specified identifier type and converter function. @@ -73,9 +77,9 @@ data class AggregateWithImmutableIdentifierFactory( * @return instance of AggregateWithImmutableIdentifierFactory */ inline fun usingIdentifier(idClazz: KClass, noinline idExtractor: (String) -> ID) = - AggregateWithImmutableIdentifierFactory(clazz = A::class, idClazz = idClazz, idExtractor = object : AggregateIdentifierConverter { - override fun apply(it: String): ID = idExtractor(it) - }) + AggregateWithImmutableIdentifierFactory(clazz = A::class, idClazz = idClazz, idExtractor = object : AggregateIdentifierConverter { + override fun apply(it: String): ID = idExtractor(it) + }) /** * Factory method for aggregate factory using specified identifier type and converter. @@ -84,25 +88,49 @@ data class AggregateWithImmutableIdentifierFactory( * @return instance of AggregateWithImmutableIdentifierFactory */ fun usingIdentifier(aggregateClazz: KClass, idClazz: KClass, idExtractor: AggregateIdentifierConverter) = - AggregateWithImmutableIdentifierFactory(clazz = aggregateClazz, idClazz = idClazz, idExtractor = idExtractor) + AggregateWithImmutableIdentifierFactory(clazz = aggregateClazz, idClazz = idClazz, idExtractor = idExtractor) + + /** + * Tries to extract constructor from given class. Used as a default factory method for the aggregate. + * @param clazz aggregate class. + * @param idClazz id class. + * @return factory method to create new instances of aggregate. + */ + fun extractConstructorFactory(clazz: KClass, idClazz: KClass): AggregateFactoryMethod = { + val constructor = invokeReporting( + "The aggregate [${clazz.java.name}] doesn't provide a constructor for the identifier type [${idClazz.java.name}]." + ) { clazz.java.getConstructor(idClazz.java) } + constructor.newInstance(it) + } } @Throws(IllegalArgumentException::class) override fun createAggregateRoot(aggregateIdentifier: String, message: DomainEventMessage<*>?): A { - val constructor = invokeReporting( - "The aggregate [${clazz.java.name}] doesn't provide a constructor for the identifier type [${idClazz.java.name}]." - ) { clazz.java.getConstructor(idClazz.java) } - val id: ID = invokeReporting( - "The identifier [$aggregateIdentifier] could not be converted to the type [${idClazz.java.name}], required for the ID of aggregate [${clazz.java.name}]." + "The identifier [$aggregateIdentifier] could not be converted to the type [${idClazz.java.name}], required for the ID of aggregate [${clazz.java.name}]." ) { idExtractor.apply(aggregateIdentifier) } - return constructor.newInstance(id) + return aggregateFactoryMethod.invoke(id).also { notifyCallbacks(it) } } override fun getAggregateType(): Class = clazz.java + + /** + * Notify the callbacks. + */ + private fun notifyCallbacks(aggregateInstance: A) { + callbacks.forEach { it.aggregateCreated(aggregateInstance, this) } + } + + /** + * Registers a new callback. + * @param callback callback to be called on aggregate creation. + */ + public fun registerCallback(callback: AggregateCreationCallback) { + callbacks.add(callback) + } } /** From 892f57b06ac43dc0061cb908b2f54d544bffb550 Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Wed, 30 Sep 2020 11:57:59 +0200 Subject: [PATCH 10/11] fix docs, add helper method --- .../extension/kotlin/spring/ReflectionExtensions.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/ReflectionExtensions.kt b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/ReflectionExtensions.kt index 6ebf8e5..98e3188 100644 --- a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/ReflectionExtensions.kt +++ b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/ReflectionExtensions.kt @@ -26,7 +26,6 @@ import kotlin.reflect.jvm.jvmErasure /** * Scans classpath for annotated aggregate classes. * @param scanPackage package to scan. - * @param annotationClazz annotation class that is used on classes. * @return list of annotated classes. */ internal fun KClass.findAnnotatedAggregateClasses(scanPackage: String): List> { @@ -40,7 +39,6 @@ internal fun KClass.findAnnotatedAggregateClasses(scanPackage: S /** * Extracts the class of aggregate identifier from an aggregate. - * @param aggregateClazz aggregate class. * @return class of aggregate identifier. * @throws IllegalArgumentException if the required constructor is not found. */ @@ -99,3 +97,8 @@ internal fun List>.findIdentifierConverter(idCla * @return class of aggregate identifier or null if it can't be resolved. */ internal fun AggregateIdentifierConverter<*>.getConverterIdentifierClass() = this::class.supertypes.first { superTypes -> superTypes.classifier == AggregateIdentifierConverter::class }.arguments[0].type?.jvmErasure + +/** + * Converts a string to the same string with first lower letter. + */ +fun String?.toFirstLower() = this?.substring(0, 1)?.toLowerCase() + this?.substring(1, this?.length - 1) \ No newline at end of file From 0065514a8593b2ab78c9fcdb99675446d969439d Mon Sep 17 00:00:00 2001 From: Simon Zambrovski Date: Sun, 25 Oct 2020 17:38:27 +0100 Subject: [PATCH 11/11] feature: reacted to comments of @smcvb --- .java-version | 1 - kotlin-example/pom.xml | 7 --- .../example/AxonKotlinExampleApplication.kt | 18 ++++++- .../kotlin/example/core/BankAccount.kt | 23 +-------- .../example/core/BankAccountAdvanced.kt | 4 +- .../projection/SimpleBankProjection.kt | 32 ------------ .../extension/kotlin/example/spring/Foo.kt | 2 - .../kotlin/example/spring/FooFactory.kt | 2 - .../kotlin/example/spring/MyFooConsumer.kt | 2 - .../src/main/resources/application.yaml | 3 ++ ...tesWithImmutableIdentifierConfiguration.kt | 21 +++----- kotlin/pom.xml | 29 +++++++++-- .../axonframework/extensions/kotlin/Utils.kt | 15 ++++++ .../AggregateConfigurerExtensions.kt | 6 +-- .../aggregate/AggregateCreationCallback.kt | 30 ----------- .../aggregate/AggregateFactoryMethod.kt | 21 -------- ...ImmutableIdentifierAggregateRepository.kt} | 2 +- ...=> ImmutableIdentifierAggregateFactory.kt} | 51 +++++-------------- ...egateWithImmutableIdentifierFactoryTest.kt | 10 ++-- ...teWithImmutableIdentifierRepositoryTest.kt | 2 +- pom.xml | 28 ---------- 21 files changed, 93 insertions(+), 216 deletions(-) delete mode 100644 .java-version delete mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/projection/SimpleBankProjection.kt delete mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/Foo.kt delete mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/FooFactory.kt delete mode 100644 kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/MyFooConsumer.kt create mode 100644 kotlin-example/src/main/resources/application.yaml create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/Utils.kt delete mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt delete mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt rename kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/{EventSourcingAggregateWithImmutableIdentifierRepository.kt => EventSourcingImmutableIdentifierAggregateRepository.kt} (97%) rename kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/{AggregateWithImmutableIdentifierFactory.kt => ImmutableIdentifierAggregateFactory.kt} (72%) diff --git a/.java-version b/.java-version deleted file mode 100644 index 6259340..0000000 --- a/.java-version +++ /dev/null @@ -1 +0,0 @@ -1.8 diff --git a/kotlin-example/pom.xml b/kotlin-example/pom.xml index 9433ca3..eae6da5 100644 --- a/kotlin-example/pom.xml +++ b/kotlin-example/pom.xml @@ -46,13 +46,6 @@ org.axonframework axon-spring-boot-starter - - - - org.axonframework - axon-server-connector - - io.github.microutils diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt index 051abbf..3f1fdbd 100644 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/AxonKotlinExampleApplication.kt @@ -16,12 +16,18 @@ package org.axonframework.extension.kotlin.example import mu.KLogging +import org.axonframework.config.Configurer +import org.axonframework.eventhandling.EventBus +import org.axonframework.eventhandling.EventMessage +import org.axonframework.eventhandling.interceptors.EventLoggingInterceptor import org.axonframework.eventhandling.tokenstore.TokenStore import org.axonframework.eventhandling.tokenstore.inmemory.InMemoryTokenStore import org.axonframework.eventsourcing.eventstore.EmbeddedEventStore import org.axonframework.eventsourcing.eventstore.EventStore import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine import org.axonframework.extension.kotlin.spring.EnableAggregateWithImmutableIdentifierScan +import org.axonframework.messaging.interceptors.LoggingInterceptor +import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.annotation.Bean @@ -49,10 +55,20 @@ class AxonKotlinExampleApplication { @Bean fun eventStore(): EventStore = EmbeddedEventStore.builder().storageEngine(InMemoryEventStorageEngine()).build() + /** + * Configure logging interceptor. + */ + @Autowired + fun configureEventHandlingInterceptors(eventBus: EventBus) { + eventBus.registerDispatchInterceptor(EventLoggingInterceptor()) + } + /** * Configures to use in-memory token store. */ @Bean fun tokenStore(): TokenStore = InMemoryTokenStore() -} \ No newline at end of file +} + + diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt index 56d7c2c..0975c97 100644 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccount.kt @@ -37,26 +37,7 @@ import java.util.* * Bank configuration. */ @Configuration -class BankConfiguration(val bankAccountService: BankAccountService) { - - -// /** -// * Example of manual configuration. -// */ -// @Autowired -// fun manualConfigure(configurer: Configurer) { -// configurer.configureAggregate( -// defaultAggregateConfiguration() -// .configureRepository { config -> -// EventSourcingAggregateWithImmutableIdentifierRepository( -// EventSourcingRepository -// .builder(BankAccount::class.java) -// .eventStore(config.eventStore()) -// .aggregateFactory(usingIdentifier(UUID::class) { UUID.fromString(it) }) -// ) -// } -// ) -// } +class BankConfiguration(private val bankAccountService: BankAccountService) { /** * Application runner of bank ops. @@ -71,7 +52,7 @@ class BankConfiguration(val bankAccountService: BankAccountService) { * Bank service. */ @Service -class BankAccountService(val commandGateway: CommandGateway) { +class BankAccountService(private val commandGateway: CommandGateway) { companion object : KLogging() diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt index 0ac3b7a..0b790f0 100644 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt +++ b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/core/BankAccountAdvanced.kt @@ -38,7 +38,7 @@ import java.util.* * Advanced bank config. */ @Configuration -class AdvancedBankConfiguration(val advancedBankAccountService: AdvancedBankAccountService) { +class AdvancedBankConfiguration(private val advancedBankAccountService: AdvancedBankAccountService) { /** * Application run starting bank ops. @@ -70,7 +70,7 @@ class AdvancedBankConfiguration(val advancedBankAccountService: AdvancedBankAcco * Advanced bank service. */ @Service -class AdvancedBankAccountService(val commandGateway: CommandGateway) { +class AdvancedBankAccountService(private val commandGateway: CommandGateway) { companion object : KLogging() diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/projection/SimpleBankProjection.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/projection/SimpleBankProjection.kt deleted file mode 100644 index 3c569dc..0000000 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/projection/SimpleBankProjection.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2020. Axon Framework - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.axonframework.extension.kotlin.example.projection - -import mu.KLogging -import org.axonframework.eventhandling.EventHandler -import org.axonframework.eventhandling.EventMessage -import org.springframework.stereotype.Component - -@Component -class SimpleBankProjection { - - companion object : KLogging() - - @EventHandler - fun > on(event: T) { - logger.info { "Received event ${event.payload}" } - } -} \ No newline at end of file diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/Foo.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/Foo.kt deleted file mode 100644 index ef09037..0000000 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/Foo.kt +++ /dev/null @@ -1,2 +0,0 @@ -package org.axonframework.extension.kotlin.example.spring - diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/FooFactory.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/FooFactory.kt deleted file mode 100644 index ef09037..0000000 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/FooFactory.kt +++ /dev/null @@ -1,2 +0,0 @@ -package org.axonframework.extension.kotlin.example.spring - diff --git a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/MyFooConsumer.kt b/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/MyFooConsumer.kt deleted file mode 100644 index ef09037..0000000 --- a/kotlin-example/src/main/kotlin/org/axonframework/extension/kotlin/example/spring/MyFooConsumer.kt +++ /dev/null @@ -1,2 +0,0 @@ -package org.axonframework.extension.kotlin.example.spring - diff --git a/kotlin-example/src/main/resources/application.yaml b/kotlin-example/src/main/resources/application.yaml new file mode 100644 index 0000000..6da84a4 --- /dev/null +++ b/kotlin-example/src/main/resources/application.yaml @@ -0,0 +1,3 @@ +axon: + axonserver: + enabled: false \ No newline at end of file diff --git a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt index 0a813ad..1580813 100644 --- a/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt +++ b/kotlin-springboot-autoconfigure/src/main/kotlin/org/axonframework/extension/kotlin/spring/AggregatesWithImmutableIdentifierConfiguration.kt @@ -19,13 +19,11 @@ import mu.KLogging import org.axonframework.config.AggregateConfigurer.defaultConfiguration import org.axonframework.config.Configurer import org.axonframework.eventsourcing.EventSourcingRepository -import org.axonframework.extensions.kotlin.aggregate.AggregateCreationCallback import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter -import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory -import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingIdentifier -import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingStringIdentifier -import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingUUIDIdentifier -import org.axonframework.extensions.kotlin.aggregate.EventSourcingAggregateWithImmutableIdentifierRepository +import org.axonframework.extensions.kotlin.aggregate.ImmutableIdentifierAggregateFactory.Companion.usingIdentifier +import org.axonframework.extensions.kotlin.aggregate.ImmutableIdentifierAggregateFactory.Companion.usingStringIdentifier +import org.axonframework.extensions.kotlin.aggregate.ImmutableIdentifierAggregateFactory.Companion.usingUUIDIdentifier +import org.axonframework.extensions.kotlin.aggregate.EventSourcingImmutableIdentifierAggregateRepository import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.context.ApplicationContext @@ -87,20 +85,13 @@ class AggregatesWithImmutableIdentifierConfiguration( UUID::class -> usingUUIDIdentifier(aggregateClazz) else -> usingIdentifier(aggregateClazz, idFieldClazz, converters.findIdentifierConverter(idFieldClazz)) }.also { - - // logging callback - it.registerCallback(object : AggregateCreationCallback { - override fun aggregateCreated(aggregateInstance: Any, aggregateFactory: AggregateWithImmutableIdentifierFactory<*, *>) { - logger.trace { "Aggregate factory $aggregateFactory just created $aggregateInstance" } - } - }) - logger.info { "Registering aggregate factory $it" } + logger.debug { "Registering aggregate factory $it" } } configurer.configureAggregate( defaultConfiguration(aggregateClazz.java) .configureRepository { config -> - EventSourcingAggregateWithImmutableIdentifierRepository( + EventSourcingImmutableIdentifierAggregateRepository( builder = EventSourcingRepository .builder(aggregateClazz.java) .eventStore(config.eventStore()) diff --git a/kotlin/pom.xml b/kotlin/pom.xml index a7b7850..8bd1210 100644 --- a/kotlin/pom.xml +++ b/kotlin/pom.xml @@ -30,11 +30,6 @@ axon-kotlin - - - false - - org.axonframework @@ -42,4 +37,28 @@ provided + + + + javadoc-and-sources + + + performRelease + true + + + + + + org.jetbrains.dokka + dokka-maven-plugin + + + maven-source-plugin + 3.0.1 + + + + + diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/Utils.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/Utils.kt new file mode 100644 index 0000000..fd202c6 --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/Utils.kt @@ -0,0 +1,15 @@ +package org.axonframework.extensions.kotlin + +/** + * Tries to execute the given function or reports an error on failure. + * @param errorMessage message to report on error. + * @param function: function to invoke + */ +@Throws(IllegalArgumentException::class) +internal fun invokeReporting(errorMessage: String, function: () -> T): T { + return try { + function.invoke() + } catch (e: Exception) { + throw IllegalArgumentException(errorMessage, e) + } +} diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateConfigurerExtensions.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateConfigurerExtensions.kt index 6774c38..46660b2 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateConfigurerExtensions.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateConfigurerExtensions.kt @@ -24,17 +24,17 @@ import org.axonframework.config.AggregateConfigurer.jpaMappedConfiguration * Creates default aggregate configurer with a usage of a reified type information. * @param [A] type of aggregate. */ -inline fun defaultAggregateConfiguration(): AggregateConfigurer = AggregateConfigurer.defaultConfiguration(A::class.java) +inline fun defaultConfiguration(): AggregateConfigurer = AggregateConfigurer.defaultConfiguration(A::class.java) /** * Creates JPA-mapped aggregate configurer with a usage of a reified type information. * @param [A] type of aggregate. */ -inline fun jpaMappedAggregateConfiguration(): AggregateConfigurer = jpaMappedConfiguration(A::class.java) +inline fun jpaMappedConfiguration(): AggregateConfigurer = jpaMappedConfiguration(A::class.java) /** * Creates JPA-mapped aggregate configurer with a usage of a reified type information. * @param entityManagerProvider entity manager provider. * @param [A] type of aggregate. */ -inline fun jpaMappedAggregateConfiguration(entityManagerProvider: EntityManagerProvider): AggregateConfigurer = jpaMappedConfiguration(A::class.java, entityManagerProvider) \ No newline at end of file +inline fun jpaMappedConfiguration(entityManagerProvider: EntityManagerProvider): AggregateConfigurer = jpaMappedConfiguration(A::class.java, entityManagerProvider) \ No newline at end of file diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt deleted file mode 100644 index 0bc089c..0000000 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateCreationCallback.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2010-2020. Axon Framework - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.axonframework.extensions.kotlin.aggregate - -/** - * Informs about creation of an aggregate. - */ -@FunctionalInterface -interface AggregateCreationCallback { - - /** - * Callback method called after the aggregate instance has been created. - * @param aggregateInstance aggregate instance. - * @param aggregateFactory factory created the aggregate. - */ - fun aggregateCreated(aggregateInstance: A, aggregateFactory: AggregateWithImmutableIdentifierFactory<*, *>): Unit -} \ No newline at end of file diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt deleted file mode 100644 index d6c6ffe..0000000 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateFactoryMethod.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2010-2020. Axon Framework - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.axonframework.extensions.kotlin.aggregate - -/** - * Type alias for function creating the aggregate from id. - */ -typealias AggregateFactoryMethod = (ID) -> A \ No newline at end of file diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepository.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingImmutableIdentifierAggregateRepository.kt similarity index 97% rename from kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepository.kt rename to kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingImmutableIdentifierAggregateRepository.kt index f5a151b..f041416 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepository.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingImmutableIdentifierAggregateRepository.kt @@ -29,7 +29,7 @@ import java.util.concurrent.Callable * @since 0.2.0 * @author Simon Zambrovski */ -class EventSourcingAggregateWithImmutableIdentifierRepository( +class EventSourcingImmutableIdentifierAggregateRepository( builder: Builder ) : EventSourcingRepository(builder) { diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableIdentifierAggregateFactory.kt similarity index 72% rename from kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt rename to kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableIdentifierAggregateFactory.kt index 540e96c..293b95c 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactory.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/aggregate/ImmutableIdentifierAggregateFactory.kt @@ -20,6 +20,7 @@ import org.axonframework.eventhandling.DomainEventMessage import org.axonframework.eventsourcing.AggregateFactory import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter.DefaultString import org.axonframework.extensions.kotlin.aggregate.AggregateIdentifierConverter.DefaultUUID +import org.axonframework.extensions.kotlin.invokeReporting import java.util.* import kotlin.reflect.KClass @@ -36,37 +37,36 @@ import kotlin.reflect.KClass * @since 0.2.0 * @author Simon Zambrovski */ -data class AggregateWithImmutableIdentifierFactory( +data class ImmutableIdentifierAggregateFactory( val clazz: KClass, val idClazz: KClass, val aggregateFactoryMethod: AggregateFactoryMethod = extractConstructorFactory(clazz, idClazz), - val idExtractor: AggregateIdentifierConverter, - val callbacks: MutableSet> = mutableSetOf() + val idExtractor: AggregateIdentifierConverter ) : AggregateFactory { companion object { /** * Reified factory method for aggregate factory using string as aggregate identifier. - * @return instance of AggregateWithImmutableIdentifierFactory + * @return instance of ImmutableIdentifierAggregateFactory */ inline fun usingStringIdentifier() = usingIdentifier(String::class) { it } /** * Factory method for aggregate factory using string as aggregate identifier. - * @return instance of AggregateWithImmutableIdentifierFactory + * @return instance of ImmutableIdentifierAggregateFactory */ fun usingStringIdentifier(clazz: KClass) = usingIdentifier(aggregateClazz = clazz, idClazz = String::class, idExtractor = DefaultString) /** * Reified factory method for aggregate factory using UUID as aggregate identifier. - * @return instance of AggregateWithImmutableIdentifierFactory + * @return instance of ImmutableIdentifierAggregateFactory */ inline fun usingUUIDIdentifier() = usingIdentifier(idClazz = UUID::class, idExtractor = DefaultUUID::apply) /** * Factory method for aggregate factory using UUID as aggregate identifier. - * @return instance of AggregateWithImmutableIdentifierFactory + * @return instance of ImmutableIdentifierAggregateFactory */ fun usingUUIDIdentifier(clazz: KClass) = usingIdentifier(aggregateClazz = clazz, idClazz = UUID::class, idExtractor = DefaultUUID) @@ -74,10 +74,10 @@ data class AggregateWithImmutableIdentifierFactory( * Reified factory method for aggregate factory using specified identifier type and converter function. * @param idClazz identifier class. * @param idExtractor extractor function for identifier from string. - * @return instance of AggregateWithImmutableIdentifierFactory + * @return instance of ImmutableIdentifierAggregateFactory */ inline fun usingIdentifier(idClazz: KClass, noinline idExtractor: (String) -> ID) = - AggregateWithImmutableIdentifierFactory(clazz = A::class, idClazz = idClazz, idExtractor = object : AggregateIdentifierConverter { + ImmutableIdentifierAggregateFactory(clazz = A::class, idClazz = idClazz, idExtractor = object : AggregateIdentifierConverter { override fun apply(it: String): ID = idExtractor(it) }) @@ -85,10 +85,10 @@ data class AggregateWithImmutableIdentifierFactory( * Factory method for aggregate factory using specified identifier type and converter. * @param idClazz identifier class. * @param idExtractor extractor for identifier from string. - * @return instance of AggregateWithImmutableIdentifierFactory + * @return instance of ImmutableIdentifierAggregateFactory */ fun usingIdentifier(aggregateClazz: KClass, idClazz: KClass, idExtractor: AggregateIdentifierConverter) = - AggregateWithImmutableIdentifierFactory(clazz = aggregateClazz, idClazz = idClazz, idExtractor = idExtractor) + ImmutableIdentifierAggregateFactory(clazz = aggregateClazz, idClazz = idClazz, idExtractor = idExtractor) /** * Tries to extract constructor from given class. Used as a default factory method for the aggregate. @@ -112,37 +112,14 @@ data class AggregateWithImmutableIdentifierFactory( "The identifier [$aggregateIdentifier] could not be converted to the type [${idClazz.java.name}], required for the ID of aggregate [${clazz.java.name}]." ) { idExtractor.apply(aggregateIdentifier) } - return aggregateFactoryMethod.invoke(id).also { notifyCallbacks(it) } + return aggregateFactoryMethod.invoke(id) } override fun getAggregateType(): Class = clazz.java - /** - * Notify the callbacks. - */ - private fun notifyCallbacks(aggregateInstance: A) { - callbacks.forEach { it.aggregateCreated(aggregateInstance, this) } - } - - /** - * Registers a new callback. - * @param callback callback to be called on aggregate creation. - */ - public fun registerCallback(callback: AggregateCreationCallback) { - callbacks.add(callback) - } } /** - * Tries to execute the given function or reports an error on failure. - * @param errorMessage message to report on error. - * @param function: function to invoke + * Type alias for function creating the aggregate from id. */ -@Throws(IllegalArgumentException::class) -private fun invokeReporting(errorMessage: String, function: () -> T): T { - return try { - function.invoke() - } catch (e: Exception) { - throw IllegalArgumentException(errorMessage, e) - } -} +typealias AggregateFactoryMethod = (ID) -> A diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactoryTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactoryTest.kt index 0e15317..f18871f 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactoryTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/AggregateWithImmutableIdentifierFactoryTest.kt @@ -18,7 +18,7 @@ package org.axonframework.extensions.kotlin.aggregate import org.axonframework.extensions.kotlin.TestLongAggregate import org.axonframework.extensions.kotlin.TestStringAggregate import org.axonframework.extensions.kotlin.TestUUIDAggregate -import org.axonframework.extensions.kotlin.aggregate.AggregateWithImmutableIdentifierFactory.Companion.usingIdentifier +import org.axonframework.extensions.kotlin.aggregate.ImmutableIdentifierAggregateFactory.Companion.usingIdentifier import java.util.* import kotlin.test.Test import kotlin.test.assertEquals @@ -35,7 +35,7 @@ internal class AggregateWithImmutableIdentifierFactoryTest { @Test fun `should create string aggregate`() { val aggregateId = UUID.randomUUID().toString() - val factory = AggregateWithImmutableIdentifierFactory.usingStringIdentifier() + val factory = ImmutableIdentifierAggregateFactory.usingStringIdentifier() val aggregate = factory.createAggregateRoot(aggregateId, null) assertEquals(aggregateId, aggregate.aggregateId) @@ -44,7 +44,7 @@ internal class AggregateWithImmutableIdentifierFactoryTest { @Test fun `should create uuid aggregate`() { val aggregateId = UUID.randomUUID() - val factory: AggregateWithImmutableIdentifierFactory = usingIdentifier(UUID::class) { UUID.fromString(it) } + val factory: ImmutableIdentifierAggregateFactory = usingIdentifier(UUID::class) { UUID.fromString(it) } val aggregate = factory.createAggregateRoot(aggregateId.toString(), null) assertEquals(aggregateId, aggregate.aggregateId) @@ -54,7 +54,7 @@ internal class AggregateWithImmutableIdentifierFactoryTest { fun `should fail create aggregate with wrong constructor type`() { val aggregateId = UUID.randomUUID() // pretending the TestLongAggregate to have UUID as identifier. - val factory: AggregateWithImmutableIdentifierFactory = usingIdentifier(UUID::class) { UUID.fromString(it) } + val factory: ImmutableIdentifierAggregateFactory = usingIdentifier(UUID::class) { UUID.fromString(it) } val exception = assertFailsWith { factory.createAggregateRoot(aggregateId.toString(), null) @@ -68,7 +68,7 @@ internal class AggregateWithImmutableIdentifierFactoryTest { fun `should fail create aggregate error in extractor`() { val aggregateId = UUID.randomUUID() // the extractor is broken. - val factory: AggregateWithImmutableIdentifierFactory = usingIdentifier(UUID::class) { throw java.lang.IllegalArgumentException("") } + val factory: ImmutableIdentifierAggregateFactory = usingIdentifier(UUID::class) { throw java.lang.IllegalArgumentException("") } val exception = assertFailsWith { factory.createAggregateRoot(aggregateId.toString(), null) diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepositoryTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepositoryTest.kt index 4c67477..2dfed78 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepositoryTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/aggregate/EventSourcingAggregateWithImmutableIdentifierRepositoryTest.kt @@ -66,7 +66,7 @@ internal class EventSourcingAggregateWithImmutableIdentifierRepositoryTest { fun `should ask factory to create the aggregate`() { - val repo = EventSourcingAggregateWithImmutableIdentifierRepository( + val repo = EventSourcingImmutableIdentifierAggregateRepository( EventSourcingRepository .builder(TestStringAggregate::class.java) .eventStore(eventStore) diff --git a/pom.xml b/pom.xml index 38c78d2..016f2f4 100644 --- a/pom.xml +++ b/pom.xml @@ -45,9 +45,6 @@ 2.13.3 1.4.10 2.3.2.RELEASE - - - true @@ -239,30 +236,6 @@ - - - docs-and-sources - - - - performRelease - true - - - - - - org.jetbrains.dokka - dokka-maven-plugin - - - org.apache.maven.plugins - maven-source-plugin - - - - - coverage @@ -271,7 +244,6 @@ org.jacoco jacoco-maven-plugin - 0.8.6