Compare commits
16 Commits
28be046dd6
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 16e954101f | |||
| 61369fc2bf | |||
| da16ea9afe | |||
| fedc1029ce | |||
| 857d97c142 | |||
| 4b333ff748 | |||
| 92ba0f5729 | |||
| 371c4fa5aa | |||
| 559633e58a | |||
| 652355f486 | |||
| d3e8d63287 | |||
| 05431bd77b | |||
| 8e9fd6935f | |||
| a85390ffb3 | |||
| 4547fb366e | |||
| fddec51712 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@ build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
application-dev.yml
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Spring Boot + Axon Framework
|
||||
### Concepts to implement
|
||||
- [ ] Event Sourcing
|
||||
- [x] Event Sourcing
|
||||
- [x] CQRS System
|
||||
- [ ] DDD
|
||||
- [ ] CQRS System
|
||||
|
||||
### Contributors
|
||||
- [x] Sambo Chea <sombochea@cubetiqs.com>
|
||||
@@ -3,8 +3,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
plugins {
|
||||
id("org.springframework.boot") version "2.3.3.RELEASE"
|
||||
id("io.spring.dependency-management") version "1.0.10.RELEASE"
|
||||
kotlin("jvm") version "1.3.72"
|
||||
kotlin("plugin.spring") version "1.3.72"
|
||||
kotlin("jvm") version "1.4.0"
|
||||
kotlin("plugin.spring") version "1.4.0"
|
||||
kotlin("plugin.jpa") version "1.4.0"
|
||||
}
|
||||
|
||||
group = "com.cubetiqs.demo"
|
||||
@@ -17,9 +18,18 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.axonframework:axon-spring-boot-starter:4.2.1")
|
||||
implementation("io.springfox:springfox-swagger2:2.9.2")
|
||||
implementation("io.springfox:springfox-swagger-ui:2.9.2")
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
runtimeOnly("org.postgresql:postgresql")
|
||||
|
||||
testImplementation("org.axonframework:axon-test:4.2.1")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test") {
|
||||
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
|
||||
}
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
package com.cubetiqs.demo.axon
|
||||
|
||||
import com.cubetiqs.demo.axon.util.ExecUtils
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.util.FileCopyUtils
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
@SpringBootApplication
|
||||
class AxonApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<AxonApplication>(*args)
|
||||
runApplication<AxonApplication>(*args)
|
||||
}
|
||||
|
||||
@RestController
|
||||
class MyDumper {
|
||||
@GetMapping("/dump")
|
||||
fun dumper(
|
||||
response: HttpServletResponse
|
||||
) {
|
||||
response.contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE
|
||||
val data = ExecUtils.execMySqlDump()
|
||||
FileCopyUtils.copy(data ?: ByteArray(0), response.outputStream)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.cubetiqs.demo.axon.aggregate
|
||||
|
||||
import com.cubetiqs.demo.axon.command.CreateAccountCommand
|
||||
import com.cubetiqs.demo.axon.command.CreditMoneyCommand
|
||||
import com.cubetiqs.demo.axon.command.DebitMoneyCommand
|
||||
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.InsufficientBalanceException
|
||||
import org.axonframework.commandhandling.CommandHandler
|
||||
import org.axonframework.eventsourcing.EventSourcingHandler
|
||||
import org.axonframework.modelling.command.AggregateIdentifier
|
||||
import org.axonframework.modelling.command.AggregateLifecycle
|
||||
import org.axonframework.serialization.Revision
|
||||
import org.axonframework.spring.stereotype.Aggregate
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
|
||||
@Aggregate
|
||||
@Revision("1.0")
|
||||
final class BankAccountAggregate() {
|
||||
@AggregateIdentifier
|
||||
private var id: UUID? = null
|
||||
private var balance: BigDecimal? = null
|
||||
private var owner: String? = null
|
||||
|
||||
@CommandHandler
|
||||
constructor(command: CreateAccountCommand) : this() {
|
||||
AggregateLifecycle.apply(
|
||||
AccountCreatedEvent(
|
||||
command.accountId,
|
||||
command.initialBalance,
|
||||
command.owner
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@EventSourcingHandler
|
||||
fun on(event: AccountCreatedEvent) {
|
||||
id = event.id
|
||||
owner = event.owner
|
||||
balance = event.initialBalance
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
fun handles(command: CreditMoneyCommand) {
|
||||
AggregateLifecycle.apply(
|
||||
MoneyCreditedEvent(
|
||||
command.accountId,
|
||||
command.creditAmount
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@EventSourcingHandler
|
||||
fun on(event: MoneyCreditedEvent) {
|
||||
balance = balance!!.add(event.creditAmount)
|
||||
}
|
||||
|
||||
@CommandHandler
|
||||
fun handle(command: DebitMoneyCommand) {
|
||||
AggregateLifecycle.apply(
|
||||
MoneyDebitedEvent(
|
||||
command.accountId,
|
||||
command.debitAmount
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@EventSourcingHandler
|
||||
@Throws(InsufficientBalanceException::class)
|
||||
fun on(event: MoneyDebitedEvent) {
|
||||
if (balance!! < event.debitAmount) {
|
||||
throw InsufficientBalanceException(event.accountId!!, event.debitAmount!!)
|
||||
}
|
||||
balance = balance!!.subtract(event.debitAmount)
|
||||
}
|
||||
}
|
||||
@@ -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<BankAccount?> {
|
||||
return this.accountCommandService.createAccount(creationDTO)
|
||||
}
|
||||
|
||||
@PutMapping(value = ["/credit/{accountId}"])
|
||||
fun creditMoneyToAccount(
|
||||
@PathVariable(value = "accountId") accountId: String,
|
||||
@RequestBody moneyCreditDTO: MoneyAmountDTO
|
||||
): CompletableFuture<String?> {
|
||||
return this.accountCommandService.creditMoneyToAccount(accountId, moneyCreditDTO)
|
||||
}
|
||||
|
||||
@PutMapping(value = ["/debit/{accountId}"])
|
||||
fun debitMoneyFromAccount(
|
||||
@PathVariable(value = "accountId") accountId: String,
|
||||
@RequestBody moneyDebitDTO: MoneyAmountDTO
|
||||
): CompletableFuture<String?> {
|
||||
return this.accountCommandService.debitMoneyFromAccount(accountId, moneyDebitDTO)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.cubetiqs.demo.axon.api
|
||||
|
||||
import com.cubetiqs.demo.axon.entity.BankAccount
|
||||
import com.cubetiqs.demo.axon.service.AccountQueryService
|
||||
import io.swagger.annotations.Api
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@RestController
|
||||
@RequestMapping(path = ["/accounts"])
|
||||
@Api(value = "Bank Account Queries", description = "Bank Account Query Events API")
|
||||
class AccountQueryController @Autowired constructor(
|
||||
private val accountQueryService: AccountQueryService
|
||||
) {
|
||||
@GetMapping("/{accountId}")
|
||||
fun findById(@PathVariable("accountId") accountId: String?): CompletableFuture<BankAccount?> {
|
||||
return this.accountQueryService.findById(accountId)
|
||||
}
|
||||
|
||||
@GetMapping("/{accountId}/events")
|
||||
fun listEventsForAccount(@PathVariable(value = "accountId") accountId: String?): List<Any?> {
|
||||
return this.accountQueryService.listEventsForAccount(accountId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.cubetiqs.demo.axon.command
|
||||
|
||||
import org.axonframework.modelling.command.TargetAggregateIdentifier
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
|
||||
data class CreateAccountCommand(
|
||||
@TargetAggregateIdentifier
|
||||
val accountId: UUID? = null,
|
||||
val initialBalance: BigDecimal? = null,
|
||||
val owner: String? = null
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.cubetiqs.demo.axon.command
|
||||
|
||||
import org.axonframework.modelling.command.TargetAggregateIdentifier
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
|
||||
data class CreditMoneyCommand(
|
||||
@TargetAggregateIdentifier
|
||||
val accountId: UUID? = null,
|
||||
val creditAmount: BigDecimal? = null
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.cubetiqs.demo.axon.command
|
||||
|
||||
import org.axonframework.modelling.command.TargetAggregateIdentifier
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
|
||||
data class DebitMoneyCommand(
|
||||
@TargetAggregateIdentifier
|
||||
val accountId: UUID? = null,
|
||||
val debitAmount: BigDecimal? = null
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.cubetiqs.demo.axon.config
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import springfox.documentation.builders.PathSelectors
|
||||
import springfox.documentation.builders.RequestHandlerSelectors
|
||||
import springfox.documentation.service.ApiInfo
|
||||
import springfox.documentation.service.Contact
|
||||
import springfox.documentation.spi.DocumentationType
|
||||
import springfox.documentation.spring.web.plugins.Docket
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
class SwaggerConfiguration {
|
||||
@Bean
|
||||
fun apiDocket(): Docket {
|
||||
return Docket(DocumentationType.SWAGGER_2)
|
||||
.select()
|
||||
.apis(
|
||||
RequestHandlerSelectors
|
||||
.basePackage("com.cubetiqs.demo.axon")
|
||||
)
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.apiInfo(apiInfo)
|
||||
}
|
||||
|
||||
private val apiInfo: ApiInfo
|
||||
get() = ApiInfo(
|
||||
"Spring Boot + Axon Demo",
|
||||
"Axon Project Demo (Event Sourcing, DDD and CQRS)",
|
||||
"0.0.1-SNAPSHOT",
|
||||
"Terms of Service",
|
||||
Contact("Sambo Chea", "https://cs.cubetiqs.com", "sombochea@cubetiqs.com"),
|
||||
"MIT",
|
||||
"",
|
||||
Collections.emptyList()
|
||||
)
|
||||
}
|
||||
17
src/main/kotlin/com/cubetiqs/demo/axon/config/WebConfig.kt
Normal file
17
src/main/kotlin/com/cubetiqs/demo/axon/config/WebConfig.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.cubetiqs.demo.axon.config
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
class WebConfig : WebMvcConfigurer {
|
||||
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
|
||||
registry.addResourceHandler("swagger-ui.html")
|
||||
.addResourceLocations("classpath:/META-INF/resources/")
|
||||
registry.addResourceHandler("/webjars/**")
|
||||
.addResourceLocations("classpath:/META-INF/resources/webjars/")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.cubetiqs.demo.axon.dto
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class AccountCreationDTO(
|
||||
var initialBalance: BigDecimal? = null,
|
||||
var owner: String? = null
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.cubetiqs.demo.axon.dto
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class MoneyAmountDTO(
|
||||
var amount: BigDecimal? = null
|
||||
)
|
||||
19
src/main/kotlin/com/cubetiqs/demo/axon/entity/BankAccount.kt
Normal file
19
src/main/kotlin/com/cubetiqs/demo/axon/entity/BankAccount.kt
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.cubetiqs.demo.axon.entity
|
||||
|
||||
import java.io.Serializable
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.Id
|
||||
import javax.persistence.Table
|
||||
|
||||
@Entity
|
||||
@Table(name = "bank_accounts")
|
||||
data class BankAccount(
|
||||
@Id
|
||||
var id: UUID? = null,
|
||||
|
||||
var owner: String? = null,
|
||||
|
||||
var balance: BigDecimal? = null
|
||||
) : Serializable
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.cubetiqs.demo.axon.event
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
data class AccountCreatedEvent(
|
||||
var id: UUID? = null,
|
||||
var initialBalance: BigDecimal? = null,
|
||||
var owner: String? = null
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cubetiqs.demo.axon.event
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
|
||||
data class MoneyCreditedEvent(
|
||||
val accountId: UUID? = null,
|
||||
val creditAmount: BigDecimal? = null
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cubetiqs.demo.axon.event
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
|
||||
data class MoneyDebitedEvent(
|
||||
val accountId: UUID? = null,
|
||||
val debitAmount: BigDecimal? = null
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.cubetiqs.demo.axon.exception
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
class AccountNotFoundException(id: UUID?) : Throwable("Cannot found account number [$id]")
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.cubetiqs.demo.axon.exception
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
class InsufficientBalanceException(accountId: UUID, debitAmount: BigDecimal) : Throwable(
|
||||
"Insufficient Balance: Cannot debit " + debitAmount +
|
||||
" from account number [" + accountId.toString() + "]"
|
||||
)
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.cubetiqs.demo.axon.projection
|
||||
|
||||
import com.cubetiqs.demo.axon.entity.BankAccount
|
||||
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
|
||||
import java.util.Optional
|
||||
|
||||
@Component
|
||||
class BankAccountProjection @Autowired constructor(
|
||||
private val bankAccountRepository: BankAccountRepository
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(this.javaClass)
|
||||
|
||||
@EventHandler
|
||||
fun on(event: AccountCreatedEvent) {
|
||||
log.debug("Handling a Bank Account creation command {}", event.id)
|
||||
val bankAccount = BankAccount(
|
||||
event.id,
|
||||
event.owner,
|
||||
event.initialBalance
|
||||
)
|
||||
this.bankAccountRepository.save(bankAccount)
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@Throws(AccountNotFoundException::class)
|
||||
fun on(event: MoneyCreditedEvent) {
|
||||
log.debug("Handling an Account Credit command {}", event.accountId)
|
||||
val optionalBankAccount: Optional<BankAccount> = this.bankAccountRepository.findById(event.accountId!!)
|
||||
if (optionalBankAccount.isPresent) {
|
||||
val bankAccount: BankAccount = optionalBankAccount.get()
|
||||
bankAccount.balance = bankAccount.balance!!.add(event.creditAmount)
|
||||
this.bankAccountRepository.save(bankAccount)
|
||||
} else {
|
||||
throw AccountNotFoundException(event.accountId)
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@Throws(AccountNotFoundException::class)
|
||||
fun on(event: MoneyDebitedEvent) {
|
||||
log.debug("Handling an Account Debit command {}", event.accountId)
|
||||
val optionalBankAccount: Optional<BankAccount> = this.bankAccountRepository.findById(event.accountId!!)
|
||||
if (optionalBankAccount.isPresent) {
|
||||
val bankAccount: BankAccount = optionalBankAccount.get()
|
||||
bankAccount.balance = bankAccount.balance!!.subtract(event.debitAmount)
|
||||
this.bankAccountRepository.save(bankAccount)
|
||||
} else {
|
||||
throw AccountNotFoundException(event.accountId)
|
||||
}
|
||||
}
|
||||
|
||||
@QueryHandler
|
||||
fun handle(query: FindBankAccountQuery): BankAccount? {
|
||||
log.debug("Handling FindBankAccountQuery query: {}", query)
|
||||
return this.bankAccountRepository.findById(query.id).orElse(null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.cubetiqs.demo.axon.query
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
data class FindBankAccountQuery(
|
||||
val id: UUID
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cubetiqs.demo.axon.repository
|
||||
|
||||
import com.cubetiqs.demo.axon.entity.BankAccount
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.util.UUID
|
||||
|
||||
@Repository
|
||||
interface BankAccountRepository : JpaRepository<BankAccount, UUID>
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.cubetiqs.demo.axon.service
|
||||
|
||||
import com.cubetiqs.demo.axon.dto.AccountCreationDTO
|
||||
import com.cubetiqs.demo.axon.dto.MoneyAmountDTO
|
||||
import com.cubetiqs.demo.axon.entity.BankAccount
|
||||
import org.springframework.stereotype.Service
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@Service
|
||||
interface AccountCommandService {
|
||||
fun createAccount(creationDTO: AccountCreationDTO): CompletableFuture<BankAccount?>
|
||||
|
||||
fun creditMoneyToAccount(
|
||||
accountId: String?,
|
||||
moneyCreditDTO: MoneyAmountDTO
|
||||
): CompletableFuture<String?>
|
||||
|
||||
fun debitMoneyFromAccount(
|
||||
accountId: String?,
|
||||
moneyDebitDTO: MoneyAmountDTO
|
||||
): CompletableFuture<String?>
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.cubetiqs.demo.axon.service
|
||||
|
||||
import com.cubetiqs.demo.axon.command.CreateAccountCommand
|
||||
import com.cubetiqs.demo.axon.command.CreditMoneyCommand
|
||||
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
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@Service
|
||||
class AccountCommandServiceImpl @Autowired constructor(
|
||||
private val commandGateway: CommandGateway
|
||||
) : AccountCommandService {
|
||||
override fun createAccount(creationDTO: AccountCreationDTO): CompletableFuture<BankAccount?> {
|
||||
return commandGateway.send(
|
||||
CreateAccountCommand(
|
||||
UUID.randomUUID(),
|
||||
creationDTO.initialBalance,
|
||||
creationDTO.owner,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun creditMoneyToAccount(accountId: String?, moneyCreditDTO: MoneyAmountDTO): CompletableFuture<String?> {
|
||||
return commandGateway.send(
|
||||
CreditMoneyCommand(
|
||||
accountId.formatUuid(),
|
||||
moneyCreditDTO.amount
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun debitMoneyFromAccount(accountId: String?, moneyDebitDTO: MoneyAmountDTO): CompletableFuture<String?> {
|
||||
return commandGateway.send(
|
||||
DebitMoneyCommand(
|
||||
accountId.formatUuid(),
|
||||
moneyDebitDTO.amount
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<BankAccount?>
|
||||
fun listEventsForAccount(accountId: String?): List<Any?>
|
||||
}
|
||||
@@ -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<BankAccount?> {
|
||||
return this.queryGateway.query(
|
||||
FindBankAccountQuery(accountId.formatUuid()),
|
||||
ResponseTypes.instanceOf(BankAccount::class.java)
|
||||
)
|
||||
}
|
||||
|
||||
override fun listEventsForAccount(accountId: String?): List<Any?> {
|
||||
return this.eventStore
|
||||
.readEvents(accountId.formatUuid().toString())
|
||||
.asStream()
|
||||
.map { it.payload }
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
43
src/main/kotlin/com/cubetiqs/demo/axon/util/ExecUtils.kt
Normal file
43
src/main/kotlin/com/cubetiqs/demo/axon/util/ExecUtils.kt
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.cubetiqs.demo.axon.util
|
||||
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
object ExecUtils {
|
||||
private const val MYSQLDUMP_FILE = "mysqldump"
|
||||
|
||||
fun execMySqlDump(): ByteArray? {
|
||||
var results: ByteArray?
|
||||
try {
|
||||
val command: MutableList<String> = mutableListOf()
|
||||
command.add(MYSQLDUMP_FILE)
|
||||
command.add("--databases")
|
||||
command.add("orderwebapp")
|
||||
command.add("--host")
|
||||
command.add("192.168.0.204")
|
||||
command.add("-usombochea")
|
||||
command.add("-p@Csb632612")
|
||||
|
||||
val builder = ProcessBuilder(*command.toTypedArray())
|
||||
.redirectErrorStream(false)
|
||||
val process = builder.start()
|
||||
BufferedInputStream(process.inputStream).use {
|
||||
ByteArrayOutputStream().use { stdout ->
|
||||
while (true) {
|
||||
val x = it.read()
|
||||
if (x == -1) {
|
||||
break
|
||||
}
|
||||
stdout.write(x)
|
||||
}
|
||||
results = stdout.toByteArray()
|
||||
process.waitFor()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println(e.message)
|
||||
return null
|
||||
}
|
||||
return results
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.cubetiqs.demo.axon.util.text
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
fun String?.formatUuid(): UUID {
|
||||
return UUID.fromString(this)
|
||||
}
|
||||
43
src/main/resources/application-dev.yml.sample
Normal file
43
src/main/resources/application-dev.yml.sample
Normal file
@@ -0,0 +1,43 @@
|
||||
server:
|
||||
port: 8182
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: axon-demo
|
||||
datasource:
|
||||
driverClassName: org.postgresql.Driver
|
||||
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:axon_demo}
|
||||
username: ${POSTGRES_USERNAME:root}
|
||||
password: ${POSTGRES_PASSWORD:root}
|
||||
hikari:
|
||||
max-lifetime: 1800000
|
||||
connection-timeout: 30000
|
||||
idle-timeout: 600000
|
||||
maximum-pool-size: 30
|
||||
allow-pool-suspension: true
|
||||
tomcat:
|
||||
max_active: 10
|
||||
max_idle: 5
|
||||
min-idle: 2
|
||||
initial_size: 5
|
||||
remove_abandoned: true
|
||||
jpa:
|
||||
database-platform: org.hibernate.dialect.PostgreSQLDialect
|
||||
show-sql: true
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
temp:
|
||||
use_jdbc_metadata_defaults: false
|
||||
open-in-view: true
|
||||
|
||||
axon:
|
||||
serializer:
|
||||
general: jackson
|
||||
axonserver:
|
||||
servers: localhost
|
||||
|
||||
logging:
|
||||
level.root: debug
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
3
src/main/resources/application.yml
Normal file
3
src/main/resources/application.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
10
src/test/kotlin/TestExecUtils.kt
Normal file
10
src/test/kotlin/TestExecUtils.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
import com.cubetiqs.demo.axon.util.ExecUtils
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class TestExecUtils {
|
||||
@Test
|
||||
fun dump() {
|
||||
val dump = ExecUtils.execMySqlDump()
|
||||
println("Dump size: ${dump?.size}")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user