Add data classes and aggregate

Add command and events
This commit is contained in:
Sambo Chea 2020-08-21 10:55:40 +07:00
parent fddec51712
commit 4547fb366e
10 changed files with 213 additions and 10 deletions

View File

@ -3,8 +3,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
id("org.springframework.boot") version "2.3.3.RELEASE" id("org.springframework.boot") version "2.3.3.RELEASE"
id("io.spring.dependency-management") version "1.0.10.RELEASE" id("io.spring.dependency-management") version "1.0.10.RELEASE"
kotlin("jvm") version "1.3.72" kotlin("jvm") version "1.4.0"
kotlin("plugin.spring") version "1.3.72" kotlin("plugin.spring") version "1.4.0"
kotlin("plugin.jpa") version "1.4.0"
} }
group = "com.cubetiqs.demo" group = "com.cubetiqs.demo"
@ -17,9 +18,18 @@ repositories {
dependencies { dependencies {
implementation("org.springframework.boot:spring-boot-starter-web") 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("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") 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") { testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine") exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
} }

View File

@ -1,24 +1,123 @@
package com.cubetiqs.demo.axon package com.cubetiqs.demo.axon
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.modelling.command.TargetAggregateIdentifier
import org.axonframework.spring.stereotype.Aggregate
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import java.io.Serializable
import java.math.BigDecimal
import java.util.UUID
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Table
@SpringBootApplication @SpringBootApplication
class AxonApplication class AxonApplication
fun main(args: Array<String>) { fun main(args: Array<String>) {
runApplication<AxonApplication>(*args) runApplication<AxonApplication>(*args)
} }
@RestController @RestController
@RequestMapping @RequestMapping
class DefaultController { class DefaultController {
@GetMapping @GetMapping
fun index(): ResponseEntity<Any> { fun index(): ResponseEntity<Any> {
return ResponseEntity.ok("ok") return ResponseEntity.ok("ok")
} }
} }
@Entity
@Table(name = "bank_accounts")
data class BankAccount(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
var id: UUID? = null,
var owner: String? = null,
var balance: BigDecimal? = null
) : Serializable
@Aggregate
class BankAccountAggregate(
@AggregateIdentifier
private var id: UUID? = null,
private var balance: BigDecimal? = null,
private var owner: String? = null
) {
@CommandHandler
constructor(command: CreateAccountCommand) {
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)
}
}
data class CreateAccountCommand(
@TargetAggregateIdentifier
val accountId: UUID? = null,
val initialBalance: BigDecimal? = null,
val owner: String? = null
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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() + "]"
)

View File

@ -0,0 +1,32 @@
server:
port: 8182
spring:
datasource:
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://${POSTGRES_HOST:192.168.0.202}:${POSTGRES_PORT:5432}/${POSTGRES_DB:axon_demo}
username: ${POSTGRES_USERNAME:cubetiq}
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

View File

@ -1,2 +1,3 @@
server: spring:
port: 8182 profiles:
active: dev