From d3e8d6328724e38bfc7b34e6a39c0d5099875bd1 Mon Sep 17 00:00:00 2001 From: Sambo Chea Date: Fri, 21 Aug 2020 11:30:40 +0700 Subject: [PATCH] completely implementation about cqrs and event sourcing But not testing yet And not yet for rest too for query --- .../demo/axon/api/AccountCommandController.kt | 46 +++++++++++++++++++ .../demo/axon/api/AccountQueryController.kt | 18 ++++++++ .../axon/projection/BankAccountProjection.kt | 8 ++++ .../demo/axon/query/FindBankAccountQuery.kt | 2 +- .../axon/service/AccountCommandServiceImpl.kt | 9 ++-- .../demo/axon/service/AccountQueryService.kt | 11 +++++ .../axon/service/AccountQueryServiceImpl.kt | 33 +++++++++++++ .../demo/axon/util/text/StringExtension.kt | 7 +++ src/main/resources/application-dev.yml | 6 ++- 9 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/com/cubetiqs/demo/axon/api/AccountCommandController.kt create mode 100644 src/main/kotlin/com/cubetiqs/demo/axon/api/AccountQueryController.kt create mode 100644 src/main/kotlin/com/cubetiqs/demo/axon/service/AccountQueryService.kt create mode 100644 src/main/kotlin/com/cubetiqs/demo/axon/service/AccountQueryServiceImpl.kt create mode 100644 src/main/kotlin/com/cubetiqs/demo/axon/util/text/StringExtension.kt diff --git a/src/main/kotlin/com/cubetiqs/demo/axon/api/AccountCommandController.kt b/src/main/kotlin/com/cubetiqs/demo/axon/api/AccountCommandController.kt new file mode 100644 index 0000000..507c6e5 --- /dev/null +++ b/src/main/kotlin/com/cubetiqs/demo/axon/api/AccountCommandController.kt @@ -0,0 +1,46 @@ +package com.cubetiqs.demo.axon.api + +import com.cubetiqs.demo.axon.dto.AccountCreationDTO +import com.cubetiqs.demo.axon.dto.MoneyAmountDTO +import com.cubetiqs.demo.axon.entity.BankAccount +import com.cubetiqs.demo.axon.service.AccountCommandService +import io.swagger.annotations.Api +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController +import java.util.concurrent.CompletableFuture + +@RestController +@RequestMapping(path = ["/accounts"]) +@Api(value = "Bank Account Commands", description = "Bank Account Commands API") +class AccountCommandController @Autowired constructor( + private val accountCommandService: AccountCommandService +) { + @PostMapping + @ResponseStatus(value = HttpStatus.CREATED) + fun createAccount(@RequestBody creationDTO: AccountCreationDTO): CompletableFuture { + return this.accountCommandService.createAccount(creationDTO) + } + + @PutMapping(value = ["/credit/{accountId}"]) + fun creditMoneyToAccount( + @PathVariable(value = "accountId") accountId: String, + @RequestBody moneyCreditDTO: MoneyAmountDTO + ): CompletableFuture { + return this.accountCommandService.creditMoneyToAccount(accountId, moneyCreditDTO) + } + + @PutMapping(value = ["/debit/{accountId}"]) + fun debitMoneyFromAccount( + @PathVariable(value = "accountId") accountId: String, + @RequestBody moneyDebitDTO: MoneyAmountDTO + ): CompletableFuture? { + return this.accountCommandService.debitMoneyFromAccount(accountId, moneyDebitDTO) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/demo/axon/api/AccountQueryController.kt b/src/main/kotlin/com/cubetiqs/demo/axon/api/AccountQueryController.kt new file mode 100644 index 0000000..04f8e2d --- /dev/null +++ b/src/main/kotlin/com/cubetiqs/demo/axon/api/AccountQueryController.kt @@ -0,0 +1,18 @@ +package com.cubetiqs.demo.axon.api + +import com.cubetiqs.demo.axon.entity.BankAccount +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import java.util.concurrent.CompletableFuture + +class AccountQueryController { + @GetMapping("/{accountId}") + fun findById(@PathVariable("accountId") accountId: String?): CompletableFuture? { + return this.accountQueryService.findById(accountId) + } + + @GetMapping("/{accountId}/events") + fun listEventsForAccount(@PathVariable(value = "accountId") accountId: String?): List? { + return this.accountQueryService.listEventsForAccount(accountId) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/demo/axon/projection/BankAccountProjection.kt b/src/main/kotlin/com/cubetiqs/demo/axon/projection/BankAccountProjection.kt index bf953d7..b536227 100644 --- a/src/main/kotlin/com/cubetiqs/demo/axon/projection/BankAccountProjection.kt +++ b/src/main/kotlin/com/cubetiqs/demo/axon/projection/BankAccountProjection.kt @@ -5,8 +5,10 @@ import com.cubetiqs.demo.axon.event.AccountCreatedEvent import com.cubetiqs.demo.axon.event.MoneyCreditedEvent import com.cubetiqs.demo.axon.event.MoneyDebitedEvent import com.cubetiqs.demo.axon.exception.AccountNotFoundException +import com.cubetiqs.demo.axon.query.FindBankAccountQuery import com.cubetiqs.demo.axon.repository.BankAccountRepository import org.axonframework.eventhandling.EventHandler +import org.axonframework.queryhandling.QueryHandler import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -56,4 +58,10 @@ class BankAccountProjection @Autowired constructor( throw AccountNotFoundException(event.accountId) } } + + @QueryHandler + fun handle(query: FindBankAccountQuery): BankAccount? { + log.debug("Handling FindBankAccountQuery query: {}", query) + return this.bankAccountRepository.findById(query.accountId).orElse(null) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/demo/axon/query/FindBankAccountQuery.kt b/src/main/kotlin/com/cubetiqs/demo/axon/query/FindBankAccountQuery.kt index 0a6d704..baf41cf 100644 --- a/src/main/kotlin/com/cubetiqs/demo/axon/query/FindBankAccountQuery.kt +++ b/src/main/kotlin/com/cubetiqs/demo/axon/query/FindBankAccountQuery.kt @@ -3,5 +3,5 @@ package com.cubetiqs.demo.axon.query import java.util.UUID data class FindBankAccountQuery( - val id: UUID + val accountId: UUID ) \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/demo/axon/service/AccountCommandServiceImpl.kt b/src/main/kotlin/com/cubetiqs/demo/axon/service/AccountCommandServiceImpl.kt index 86c0162..fb40f0f 100644 --- a/src/main/kotlin/com/cubetiqs/demo/axon/service/AccountCommandServiceImpl.kt +++ b/src/main/kotlin/com/cubetiqs/demo/axon/service/AccountCommandServiceImpl.kt @@ -6,6 +6,7 @@ import com.cubetiqs.demo.axon.command.DebitMoneyCommand import com.cubetiqs.demo.axon.dto.AccountCreationDTO import com.cubetiqs.demo.axon.dto.MoneyAmountDTO import com.cubetiqs.demo.axon.entity.BankAccount +import com.cubetiqs.demo.axon.util.text.formatUuid import org.axonframework.commandhandling.gateway.CommandGateway import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @@ -29,7 +30,7 @@ class AccountCommandServiceImpl @Autowired constructor( override fun creditMoneyToAccount(accountId: String?, moneyCreditDTO: MoneyAmountDTO): CompletableFuture { return commandGateway.send( CreditMoneyCommand( - formatUuid(accountId), + accountId.formatUuid(), moneyCreditDTO.amount ) ) @@ -38,13 +39,11 @@ class AccountCommandServiceImpl @Autowired constructor( override fun debitMoneyFromAccount(accountId: String?, moneyDebitDTO: MoneyAmountDTO): CompletableFuture { return commandGateway.send( DebitMoneyCommand( - formatUuid(accountId), + accountId.formatUuid(), moneyDebitDTO.amount ) ) } - private fun formatUuid(accountId: String?): UUID { - return UUID.fromString(accountId) - } + } \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/demo/axon/service/AccountQueryService.kt b/src/main/kotlin/com/cubetiqs/demo/axon/service/AccountQueryService.kt new file mode 100644 index 0000000..735c1f2 --- /dev/null +++ b/src/main/kotlin/com/cubetiqs/demo/axon/service/AccountQueryService.kt @@ -0,0 +1,11 @@ +package com.cubetiqs.demo.axon.service + +import com.cubetiqs.demo.axon.entity.BankAccount +import org.springframework.stereotype.Service +import java.util.concurrent.CompletableFuture + +@Service +interface AccountQueryService { + fun findById(accountId: String?): CompletableFuture + fun listEventsForAccount(accountId: String?): List +} \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/demo/axon/service/AccountQueryServiceImpl.kt b/src/main/kotlin/com/cubetiqs/demo/axon/service/AccountQueryServiceImpl.kt new file mode 100644 index 0000000..626cead --- /dev/null +++ b/src/main/kotlin/com/cubetiqs/demo/axon/service/AccountQueryServiceImpl.kt @@ -0,0 +1,33 @@ +package com.cubetiqs.demo.axon.service + +import com.cubetiqs.demo.axon.entity.BankAccount +import com.cubetiqs.demo.axon.query.FindBankAccountQuery +import com.cubetiqs.demo.axon.util.text.formatUuid +import org.axonframework.eventsourcing.eventstore.EventStore +import org.axonframework.messaging.responsetypes.ResponseTypes +import org.axonframework.queryhandling.QueryGateway +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service +import java.util.concurrent.CompletableFuture +import java.util.stream.Collectors + +@Service +class AccountQueryServiceImpl @Autowired constructor( + private val queryGateway: QueryGateway, + private val eventStore: EventStore +) : AccountQueryService { + override fun findById(accountId: String?): CompletableFuture { + return this.queryGateway.query( + FindBankAccountQuery(accountId.formatUuid()), + ResponseTypes.instanceOf(BankAccount::class.java) + ) + } + + override fun listEventsForAccount(accountId: String?): List { + return this.eventStore + .readEvents(accountId.formatUuid().toString()) + .asStream() + .map { it.payload } + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/cubetiqs/demo/axon/util/text/StringExtension.kt b/src/main/kotlin/com/cubetiqs/demo/axon/util/text/StringExtension.kt new file mode 100644 index 0000000..3010c4b --- /dev/null +++ b/src/main/kotlin/com/cubetiqs/demo/axon/util/text/StringExtension.kt @@ -0,0 +1,7 @@ +package com.cubetiqs.demo.axon.util.text + +import java.util.UUID + +fun String?.formatUuid(): UUID { + return UUID.fromString(this) +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index a43557f..1a349c6 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -29,4 +29,8 @@ spring: dialect: org.hibernate.dialect.PostgreSQLDialect temp: use_jdbc_metadata_defaults: false - open-in-view: true \ No newline at end of file + open-in-view: true + +axon: + serializer: + general: jackson \ No newline at end of file