diff --git a/demo-transactions/pom.xml b/demo-transactions/pom.xml
index 44189cb..3edae55 100644
--- a/demo-transactions/pom.xml
+++ b/demo-transactions/pom.xml
@@ -73,9 +73,9 @@
test
- com.nhaarman.mockitokotlin2
- mockito-kotlin
- ${mockito-kotlin.version}
+ io.mockk
+ mockk-jvm
+ ${mockk.version}
test
@@ -251,7 +251,6 @@
-
org.apache.maven.plugins
maven-compiler-plugin
@@ -296,6 +295,32 @@
com.mysql.jdbc.Driver
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ ${maven-failsafe-plugin.version}
+
+
+ -Dspring.profiles.active=test
+
+ ${project.build.outputDirectory}
+
+
+ ${basedir}/target/classes
+
+
+
+
+
+ integration-test
+ verify
+
+
+
+
\ No newline at end of file
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/GlobalExceptionHandler.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/GlobalExceptionHandler.kt
new file mode 100644
index 0000000..e5ba1e1
--- /dev/null
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/GlobalExceptionHandler.kt
@@ -0,0 +1,23 @@
+package io.holixon.cqrshexagonaldemo.demoparent.transactions.adapter
+
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.exception.AccountNotFoundException
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.exception.InvalidInputException
+import jakarta.servlet.http.HttpServletRequest
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.ExceptionHandler
+import org.springframework.web.bind.annotation.RestControllerAdvice
+
+@RestControllerAdvice
+class GlobalExceptionHandler {
+
+ @ExceptionHandler(AccountNotFoundException::class)
+ fun handleException(request: HttpServletRequest, exception: AccountNotFoundException): ResponseEntity {
+ return ResponseEntity.notFound().build()
+ }
+
+ @ExceptionHandler(InvalidInputException::class)
+ fun handleException(request: HttpServletRequest, exception: InvalidInputException): ResponseEntity {
+ return ResponseEntity.badRequest().build()
+ }
+
+}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/inbound/account/CreateAccountRestInAdapter.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/inbound/account/AccountRestInAdapter.kt
similarity index 59%
rename from demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/inbound/account/CreateAccountRestInAdapter.kt
rename to demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/inbound/account/AccountRestInAdapter.kt
index 443718a..305bac2 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/inbound/account/CreateAccountRestInAdapter.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/inbound/account/AccountRestInAdapter.kt
@@ -4,17 +4,24 @@ import io.holixon.cqrshexagonaldemo.demoparent.transactions.adapter.inbound.Acco
import io.holixon.cqrshexagonaldemo.demoparent.transactions.adapter.inbound.account.mapper.toAccountCreatedResponseDto
import io.holixon.cqrshexagonaldemo.demoparent.transactions.adapter.inbound.dto.AccountCreatedResponseDto
import io.holixon.cqrshexagonaldemo.demoparent.transactions.adapter.inbound.dto.CreateAccountRequestDto
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.adapter.inbound.dto.DepositRequestDto
import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.inbound.account.CreateAccountInPort
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.inbound.account.DepositAccountInPort
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Amount
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Iban
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.customer.CustomerNumber
import io.holixon.cqrshexagonaldemo.demoparent.transactions.framework.InAdapter
+import jakarta.transaction.Transactional
import mu.KLogging
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RestController
+import java.math.BigDecimal
@RestController
@InAdapter
-class CreateAccountRestInAdapter(
- private val createAccountInPort: CreateAccountInPort
+open class AccountRestInAdapter(
+ private val createAccountInPort: CreateAccountInPort,
+ private val depositAccountInPort: DepositAccountInPort
) : AccountApiDelegate {
companion object : KLogging()
@@ -23,4 +30,11 @@ class CreateAccountRestInAdapter(
val createdAccount = createAccountInPort.createAccount(CustomerNumber(createAccountRequestDto.customerNumber))
return ResponseEntity.ok(createdAccount.toAccountCreatedResponseDto())
}
+
+ @Transactional
+ override fun deposit(depositRequestDto: DepositRequestDto): ResponseEntity {
+ depositAccountInPort.deposit(
+ Iban(depositRequestDto.iban), Amount(BigDecimal.valueOf(depositRequestDto.amount)))
+ return ResponseEntity.noContent().build()
+ }
}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/AccountOutAdapter.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/AccountOutAdapter.kt
index 0db6c50..cdacb81 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/AccountOutAdapter.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/AccountOutAdapter.kt
@@ -2,9 +2,12 @@ package io.holixon.cqrshexagonaldemo.demoparent.transactions.adapter.outbound.ac
import io.holixon.cqrshexagonaldemo.demoparent.transactions.adapter.outbound.account.jpa.mapper.AccountEntityMapper
import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.account.AccountOutPort
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.exception.AccountNotFoundException
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Account
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Amount
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Iban
import io.holixon.cqrshexagonaldemo.demoparent.transactions.framework.OutAdapter
+import mu.KLogging
@OutAdapter
class AccountOutAdapter(
@@ -12,18 +15,30 @@ class AccountOutAdapter(
private val accountEntityMapper: AccountEntityMapper
) : AccountOutPort {
- override fun findAccount(iban: Iban): Account? {
- val entity = jpaAccountOutAdapter.findById(iban.value)
- if (!entity.isPresent) return null
+ companion object : KLogging()
- return accountEntityMapper.toDomain(entity.get())
+ override fun findAccount(iban: Iban): Account {
+ val entity = jpaAccountOutAdapter.findById(iban.value).orElseThrow {
+ AccountNotFoundException("Account not found for Iban: ${iban.value}")
+ }
+
+ return accountEntityMapper.toDomain(entity)
}
override fun createAccount(account: Account): Account {
val toEntity = accountEntityMapper.toEntity(account)
-// toEntity.updated = Instant.now()
val savedAccount = jpaAccountOutAdapter.save(toEntity)
return accountEntityMapper.toDomain(savedAccount)
}
+
+ override fun deposit(account: Account, amount: Amount): Account {
+ account.deposit(amount)
+
+ jpaAccountOutAdapter.save(accountEntityMapper.toEntity(account))
+
+
+ return account
+ }
+
}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/entity/AccountEntity.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/entity/AccountEntity.kt
index c12b226..0c02b44 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/entity/AccountEntity.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/entity/AccountEntity.kt
@@ -6,13 +6,10 @@ import jakarta.persistence.Table
import java.math.BigDecimal
@Entity
-@Table(
- name = "account",
- schema = "transactions"
-)
+@Table(name = "account")
class AccountEntity(
- @Id
- var iban: String = "",
- var customerNumber: String = "",
- var balance: BigDecimal = BigDecimal.ZERO
+ @Id
+ var iban: String = "",
+ var customerNumber: String = "",
+ var balance: BigDecimal = BigDecimal.ZERO
)
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/mapper/AccountEntityMapper.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/mapper/AccountEntityMapper.kt
index 6bd89e8..135b276 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/mapper/AccountEntityMapper.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/account/jpa/mapper/AccountEntityMapper.kt
@@ -2,6 +2,7 @@ package io.holixon.cqrshexagonaldemo.demoparent.transactions.adapter.outbound.ac
import io.holixon.cqrshexagonaldemo.demoparent.transactions.adapter.outbound.account.jpa.entity.AccountEntity
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Account
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Amount
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Iban
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Money
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.customer.CustomerNumber
@@ -13,7 +14,7 @@ class AccountEntityMapper {
return AccountEntity(
customerNumber = account.customerNumber.value,
iban = account.iban.value,
- balance = account.balance.amount
+ balance = account.balance.amount.value
)
}
@@ -21,7 +22,7 @@ class AccountEntityMapper {
return Account(
customerNumber = CustomerNumber(accountEntity.customerNumber),
iban = Iban(accountEntity.iban),
- balance = Money(accountEntity.balance)
+ balance = Money(Amount(accountEntity.balance))
)
}
}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/customer/entity/CustomerEntity.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/customer/entity/CustomerEntity.kt
index d326888..9de4efa 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/customer/entity/CustomerEntity.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/adapter/outbound/customer/entity/CustomerEntity.kt
@@ -6,12 +6,9 @@ import jakarta.persistence.Table
@Entity
-@Table(
- name = "customer",
- schema = "transactions"
-)
+@Table(name = "customer")
class CustomerEntity(
- @Id
- var customerNumber: String = "", //Default constructor needed for JPA
- var customerName: String = ""
+ @Id
+ var customerNumber: String = "", //Default constructor needed for JPA
+ var customerName: String = ""
)
\ No newline at end of file
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/port/inbound/account/DepositAccountInPort.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/port/inbound/account/DepositAccountInPort.kt
new file mode 100644
index 0000000..555dca7
--- /dev/null
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/port/inbound/account/DepositAccountInPort.kt
@@ -0,0 +1,8 @@
+package io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.inbound.account
+
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Amount
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Iban
+
+interface DepositAccountInPort {
+ fun deposit(iban: Iban, amount: Amount)
+}
\ No newline at end of file
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/port/outbound/account/AccountOutPort.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/port/outbound/account/AccountOutPort.kt
index dd386bc..1754d87 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/port/outbound/account/AccountOutPort.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/port/outbound/account/AccountOutPort.kt
@@ -1,9 +1,11 @@
package io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.account
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Account
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Amount
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Iban
interface AccountOutPort {
- fun findAccount(iban: Iban): Account?
+ fun findAccount(iban: Iban): Account
fun createAccount(account: Account): Account
+ fun deposit(account: Account, amount: Amount): Account
}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateAccountUsecase.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateAccountUsecase.kt
index 41047fc..3a6bfa9 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateAccountUsecase.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateAccountUsecase.kt
@@ -11,6 +11,7 @@ import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.event.A
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.service.CustomerAccountVerificationService
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.service.IbanCreationService
import io.holixon.cqrshexagonaldemo.demoparent.transactions.framework.Usecase
+import mu.KLogging
@Usecase
open class CreateAccountUsecase(
@@ -21,7 +22,10 @@ open class CreateAccountUsecase(
private val eventingOutAdapter: EventingOutAdapter
) : CreateAccountInPort {
+ companion object : KLogging()
+
override fun createAccount(customerNumber: CustomerNumber): Account {
+ logger().info { "create new account for customer $customerNumber" }
val customer = customerOutPort.findCustomer(customerNumber) ?: throw RuntimeException("no customer")
@@ -40,6 +44,7 @@ open class CreateAccountUsecase(
val accountCreatedEvent = AccountCreatedEvent(customerNumber, createdAccount.iban)
eventingOutAdapter.publishEvent(accountCreatedEvent)
+ logger().info { "newly created account: $createdAccount" }
return createdAccount
}
}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateCustomerUsecase.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateCustomerUsecase.kt
index 016389e..7b9aba2 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateCustomerUsecase.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateCustomerUsecase.kt
@@ -8,6 +8,7 @@ import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.custome
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.event.CustomerCreatedEvent
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.service.CustomerNumberCreationService
import io.holixon.cqrshexagonaldemo.demoparent.transactions.framework.Usecase
+import mu.KLogging
@Usecase
open class CreateCustomerUsecase(
@@ -16,7 +17,10 @@ open class CreateCustomerUsecase(
private val eventingOutAdapter: EventingOutAdapter
) : CreateCustomerInPort {
+ companion object : KLogging()
+
override fun createCustomer(customerName: Name): Customer {
+ logger().info { "register new customer ${customerName.value}" }
val customer = Customer(
customerNumber = customerNumberCreationService.generateNextCustomerNumber(),
name = customerName)
@@ -25,6 +29,7 @@ open class CreateCustomerUsecase(
val customerCreatedEvent = CustomerCreatedEvent(customerName = customerName, customerNumber = createdCustomer.customerNumber)
eventingOutAdapter.publishEvent(customerCreatedEvent)
+ logger().info { "newly registered customer: $createdCustomer" }
return createdCustomer
}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/DepositUsecase.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/DepositUsecase.kt
new file mode 100644
index 0000000..c1afa53
--- /dev/null
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/DepositUsecase.kt
@@ -0,0 +1,36 @@
+package io.holixon.cqrshexagonaldemo.demoparent.transactions.application.usecase
+
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.inbound.account.DepositAccountInPort
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.account.AccountOutPort
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.eventing.EventingOutAdapter
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Amount
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Iban
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.event.MoneyDepositedEvent
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.framework.Usecase
+import mu.KLogging
+
+@Usecase
+class DepositUsecase(
+ private val accountOutPort: AccountOutPort,
+ private val eventingOutAdapter: EventingOutAdapter
+) : DepositAccountInPort {
+
+ companion object : KLogging()
+
+ override fun deposit(iban: Iban, amount: Amount) {
+ logger().info { "deposit $amount to account ${iban.value}" }
+
+ val account = accountOutPort.findAccount(iban)
+
+ val updatedAccount = accountOutPort.deposit(account, amount)
+
+ eventingOutAdapter.publishEvent(MoneyDepositedEvent(
+ updatedAccount.iban,
+ updatedAccount.balance,
+ System.currentTimeMillis()
+ ))
+
+ logger().info { "updated account $updatedAccount" }
+ }
+
+}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/exception/AccountNotFoundException.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/exception/AccountNotFoundException.kt
new file mode 100644
index 0000000..3269724
--- /dev/null
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/exception/AccountNotFoundException.kt
@@ -0,0 +1,3 @@
+package io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.exception
+
+class AccountNotFoundException(message: String) : RuntimeException(message)
\ No newline at end of file
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/exception/InvalidInputException.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/exception/InvalidInputException.kt
new file mode 100644
index 0000000..6d0c1ab
--- /dev/null
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/exception/InvalidInputException.kt
@@ -0,0 +1,3 @@
+package io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.exception
+
+class InvalidInputException(message: String) : RuntimeException(message)
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Account.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Account.kt
index 3af696e..16111f7 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Account.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Account.kt
@@ -6,4 +6,10 @@ data class Account(
var customerNumber: CustomerNumber,
var iban: Iban,
var balance: Money
-)
+) {
+ fun deposit(amount: Amount) {
+ // TODO: better immutable?
+ this.balance.amount += amount
+ }
+
+}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Amount.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Amount.kt
new file mode 100644
index 0000000..432ab79
--- /dev/null
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Amount.kt
@@ -0,0 +1,23 @@
+package io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account
+
+import java.math.BigDecimal
+
+/**
+ * For now this class can be value class in future when currency is introduced must be revised to support currency
+ */
+@JvmInline
+value class Amount(val value: BigDecimal) {
+ init {
+ require(value >= BigDecimal.ZERO){
+ "Amount must be positive"
+ }
+ }
+
+ operator fun plus(value2: Amount) : Amount {
+ return Amount(this.value.plus(value2.value))
+ }
+
+ override fun toString(): String {
+ return value.toString()
+ }
+}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Iban.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Iban.kt
index 0a7189a..471a5fb 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Iban.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Iban.kt
@@ -1,4 +1,9 @@
package io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account
@JvmInline
-value class Iban(val value: String)
+value class Iban(val value: String) {
+
+ override fun toString(): String {
+ return value
+ }
+}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Money.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Money.kt
index 832f23b..fbe6047 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Money.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/account/Money.kt
@@ -2,8 +2,8 @@ package io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.accoun
import java.math.BigDecimal
-data class Money(var amount: BigDecimal) {
+data class Money(var amount: Amount) {
companion object {
- val ZERO = Money(BigDecimal.ZERO)
+ val ZERO = Money(Amount(BigDecimal.ZERO))
}
}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/common/Name.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/common/Name.kt
index deed333..0c0672b 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/common/Name.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/common/Name.kt
@@ -1,4 +1,8 @@
package io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.common
@JvmInline
-value class Name(val value: String)
+value class Name(val value: String) {
+ override fun toString(): String {
+ return value
+ }
+}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/customer/CustomerNumber.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/customer/CustomerNumber.kt
index f2df9c0..55ccf43 100644
--- a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/customer/CustomerNumber.kt
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/customer/CustomerNumber.kt
@@ -1,4 +1,8 @@
package io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.customer
@JvmInline
-value class CustomerNumber(val value: String)
+value class CustomerNumber(val value: String) {
+ override fun toString(): String {
+ return value
+ }
+}
diff --git a/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/event/MoneyDepositedEvent.kt b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/event/MoneyDepositedEvent.kt
new file mode 100644
index 0000000..c141f41
--- /dev/null
+++ b/demo-transactions/src/main/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/domain/model/event/MoneyDepositedEvent.kt
@@ -0,0 +1,6 @@
+package io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.event
+
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Iban
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Money
+
+data class MoneyDepositedEvent(val iban: Iban, val balance: Money, val timeStamp: Long) : Event
diff --git a/demo-transactions/src/main/resources/api/api.yaml b/demo-transactions/src/main/resources/api/api.yaml
index 797975f..689335e 100644
--- a/demo-transactions/src/main/resources/api/api.yaml
+++ b/demo-transactions/src/main/resources/api/api.yaml
@@ -72,6 +72,34 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/AccountCreatedResponse'
+ /api/v1/account/deposit:
+ post:
+ tags:
+ - account
+ summary: >
+ Deposit money
+ description: >
+ Deposit money for a specified account
+ operationId: deposit
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ properties:
+ iban:
+ type: string
+ amount:
+ type: number
+ format: double
+ required:
+ - iban
+ - amount
+ responses:
+ '204':
+ description: Money Deposited
+ '404':
+ description: No Account found
components:
schemas:
AccountCreatedResponse:
diff --git a/demo-transactions/src/main/resources/application-IT.yaml b/demo-transactions/src/main/resources/application-IT.yaml
index adbab8c..2192af8 100644
--- a/demo-transactions/src/main/resources/application-IT.yaml
+++ b/demo-transactions/src/main/resources/application-IT.yaml
@@ -1,8 +1,4 @@
spring:
- flyway:
- user: SA
- password: Aut0mat0rs!
- url: jdbc:mysql://localhost:3306/transactions
jpa:
hibernate:
ddl-auto: create-drop
\ No newline at end of file
diff --git a/demo-transactions/src/main/resources/application-local.yaml b/demo-transactions/src/main/resources/application-local.yaml
index 33bf262..730c078 100644
--- a/demo-transactions/src/main/resources/application-local.yaml
+++ b/demo-transactions/src/main/resources/application-local.yaml
@@ -1,9 +1,5 @@
spring:
datasource:
- url: jdbc:mysql://localhost:3306/transactions
+ url: jdbc:mysql://localhost:3306/cqrs-meets-hexagonal
username: SA
password: Aut0mat0rs!
- flyway:
- user: SA
- password: Aut0mat0rs!
- url: jdbc:mysql://localhost:3306/transactions
diff --git a/demo-transactions/src/main/resources/application.yaml b/demo-transactions/src/main/resources/application.yaml
index 82fdcfb..eef4966 100644
--- a/demo-transactions/src/main/resources/application.yaml
+++ b/demo-transactions/src/main/resources/application.yaml
@@ -31,18 +31,11 @@ spring:
hibernate:
ddl-auto: none
generate_statistics: true
-
database-platform: org.hibernate.dialect.MySQL8Dialect
- properties:
- hibernate:
- default_schema: transactions
flyway:
enabled: true
- user: ${FLYWAY_USER}
- password: ${FLYWAY_PASSWORD}
- url: ${FLYWAY_URL}
- schemas:
- - transactions
- - query
+ user: ${spring.datasource.username}
+ password: ${spring.datasource.password}
+ url: ${spring.datasource.url}
locations:
- classpath:db
diff --git a/demo-transactions/src/main/resources/db/V0_0_1__create_tables_account_customer.sql b/demo-transactions/src/main/resources/db/V0_0_1__create_tables_account_customer.sql
index af09e9f..9e2006e 100644
--- a/demo-transactions/src/main/resources/db/V0_0_1__create_tables_account_customer.sql
+++ b/demo-transactions/src/main/resources/db/V0_0_1__create_tables_account_customer.sql
@@ -1,4 +1,4 @@
-CREATE TABLE transactions.customer
+CREATE TABLE customer
(
customer_number VARCHAR(255) NOT NULL,
customer_name VARCHAR(255) NOT NULL,
@@ -6,7 +6,7 @@ CREATE TABLE transactions.customer
CONSTRAINT PK_CUSTOMER_ID PRIMARY KEY (customer_number)
);
-CREATE TABLE transactions.account
+CREATE TABLE account
(
iban VARCHAR(255) NOT NULL,
customer_number VARCHAR(255) NOT NULL,
diff --git a/demo-transactions/src/main/resources/db/V0_0_2__add_balance_account.sql b/demo-transactions/src/main/resources/db/V0_0_2__add_balance_account.sql
index 550c759..155d365 100644
--- a/demo-transactions/src/main/resources/db/V0_0_2__add_balance_account.sql
+++ b/demo-transactions/src/main/resources/db/V0_0_2__add_balance_account.sql
@@ -1,2 +1,2 @@
-ALTER TABLE transactions.account
-ADD COLUMN balance DECIMAL NOT NULL DEFAULT 0;
+ALTER TABLE account
+ADD COLUMN balance DECIMAL(10, 2) NOT NULL DEFAULT 0;
diff --git a/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateAccountUsecaseTest.kt b/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateAccountUsecaseTest.kt
index ff9047f..43903e3 100644
--- a/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateAccountUsecaseTest.kt
+++ b/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateAccountUsecaseTest.kt
@@ -1,8 +1,5 @@
package io.holixon.cqrshexagonaldemo.demoparent.transactions.application.usecase
-import com.nhaarman.mockitokotlin2.any
-import com.nhaarman.mockitokotlin2.verify
-import com.nhaarman.mockitokotlin2.whenever
import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.account.AccountOutPort
import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.customer.CustomerOutPort
import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.eventing.EventingOutAdapter
@@ -15,34 +12,35 @@ import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.custome
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.event.AccountCreatedEvent
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.service.CustomerAccountVerificationService
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.service.IbanCreationService
+import io.mockk.every
+import io.mockk.impl.annotations.InjectMockKs
+import io.mockk.impl.annotations.MockK
+import io.mockk.impl.annotations.RelaxedMockK
+import io.mockk.junit5.MockKExtension
+import io.mockk.verify
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
-import org.mockito.InjectMocks
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.junit.jupiter.MockitoExtension
-
-@ExtendWith(MockitoExtension::class)
+@ExtendWith(MockKExtension::class)
class CreateAccountUsecaseTest {
- @Mock
+ @MockK
private lateinit var accountOutPort: AccountOutPort
- @Mock
+ @MockK
private lateinit var customerOutPort: CustomerOutPort
- @Mock
+ @MockK
private lateinit var customerAccountVerificationService: CustomerAccountVerificationService
- @Mock
+ @MockK
private lateinit var ibanCreationService: IbanCreationService
- @Mock
+ @RelaxedMockK
private lateinit var eventingOutAdapter: EventingOutAdapter
- @InjectMocks
+ @InjectMockKs
private lateinit var createAccountUsecase: CreateAccountUsecase
@Test
@@ -50,21 +48,24 @@ class CreateAccountUsecaseTest {
// given
val customerNumber = CustomerNumber("123456789")
val customer = Customer(customerNumber, Name("Dagobert Duck"))
- whenever(customerOutPort.findCustomer(customerNumber)).thenReturn(customer)
- whenever(customerAccountVerificationService.canAccountBeCreatedForCustomer(customer)).thenReturn(true)
+ every { customerOutPort.findCustomer(customerNumber) } returns customer
+ every { customerAccountVerificationService.canAccountBeCreatedForCustomer(customer) } returns true
+
val iban = Iban("DE00123456789")
- whenever(ibanCreationService.generateNextIban()).thenReturn(iban)
+
+ every { ibanCreationService.generateNextIban() } returns iban
val balance = Money.ZERO
val account = Account(customerNumber, iban, balance)
- whenever(accountOutPort.createAccount(any())).thenReturn(account)
+
+ every { accountOutPort.createAccount(this.any()) } returns account
// when
val createdAccount = createAccountUsecase.createAccount(customerNumber)
// then
Assertions.assertThat(createdAccount).isNotNull
- verify(accountOutPort).createAccount(account)
- Mockito.verify(eventingOutAdapter).publishEvent(AccountCreatedEvent(customerNumber, iban))
+ verify { accountOutPort.createAccount(account) }
+ verify { eventingOutAdapter.publishEvent(AccountCreatedEvent(customerNumber, iban)) }
}
}
diff --git a/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateCustomerUsecaseTest.kt b/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateCustomerUsecaseTest.kt
index 9492ded..0ae712e 100644
--- a/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateCustomerUsecaseTest.kt
+++ b/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/CreateCustomerUsecaseTest.kt
@@ -1,8 +1,5 @@
package io.holixon.cqrshexagonaldemo.demoparent.transactions.application.usecase
-import com.nhaarman.mockitokotlin2.any
-import com.nhaarman.mockitokotlin2.verify
-import com.nhaarman.mockitokotlin2.whenever
import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.customer.CustomerOutPort
import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.eventing.EventingOutAdapter
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.common.Name
@@ -10,43 +7,48 @@ import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.custome
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.customer.CustomerNumber
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.event.CustomerCreatedEvent
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.service.CustomerNumberCreationService
+import io.mockk.every
+import io.mockk.impl.annotations.InjectMockKs
+import io.mockk.impl.annotations.MockK
+import io.mockk.impl.annotations.RelaxedMockK
+import io.mockk.junit5.MockKExtension
+import io.mockk.verify
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
-import org.mockito.InjectMocks
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.junit.jupiter.MockitoExtension
-@ExtendWith(MockitoExtension::class)
+@ExtendWith(MockKExtension::class)
class CreateCustomerUsecaseTest {
- @Mock
+ @MockK
private lateinit var customerOutPort: CustomerOutPort
- @Mock
+
+ @MockK
private lateinit var customerNumberCreationService: CustomerNumberCreationService
- @Mock
+
+ @RelaxedMockK
private lateinit var eventingOutAdapter: EventingOutAdapter
- @InjectMocks
+ @InjectMockKs
private lateinit var createCustomerUsecase: CreateCustomerUsecase
@Test
fun `should create a new customer`() {
- // angenommen
+ // angenommen
val customerNumber = CustomerNumber("000123")
- whenever(customerNumberCreationService.generateNextCustomerNumber()).thenReturn(customerNumber)
+ every { customerNumberCreationService.generateNextCustomerNumber() } returns customerNumber
val customerName = Name("Richi Rich")
val customer = Customer(customerNumber, customerName)
- whenever(customerOutPort.createCustomer(any())).thenReturn(customer)
- // wenn
+ every { customerOutPort.createCustomer(this.any()) } returns customer
+
+ // wenn
val createdCustomer = createCustomerUsecase.createCustomer(customerName)
// dann
Assertions.assertThat(createdCustomer).isNotNull
- verify(customerOutPort).createCustomer(customer)
- Mockito.verify(eventingOutAdapter).publishEvent(CustomerCreatedEvent(customerName, customerNumber))
+ verify { customerOutPort.createCustomer(customer) }
+ verify { eventingOutAdapter.publishEvent(CustomerCreatedEvent(customerName, customerNumber)) }
}
}
\ No newline at end of file
diff --git a/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/DepositUsecaseTest.kt b/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/DepositUsecaseTest.kt
new file mode 100644
index 0000000..6c385c1
--- /dev/null
+++ b/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/application/usecase/DepositUsecaseTest.kt
@@ -0,0 +1,100 @@
+package io.holixon.cqrshexagonaldemo.demoparent.transactions.application.usecase
+
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.account.AccountOutPort
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.eventing.EventingOutAdapter
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.exception.AccountNotFoundException
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Account
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Amount
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Iban
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Money
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.customer.CustomerNumber
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.event.MoneyDepositedEvent
+import io.mockk.every
+import io.mockk.impl.annotations.InjectMockKs
+import io.mockk.impl.annotations.MockK
+import io.mockk.impl.annotations.RelaxedMockK
+import io.mockk.junit5.MockKExtension
+import io.mockk.verify
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.api.extension.ExtendWith
+import java.math.BigDecimal
+
+@ExtendWith(MockKExtension::class)
+class DepositUsecaseTest {
+
+ @MockK
+ private lateinit var accountOutPort: AccountOutPort
+
+ @RelaxedMockK
+ private lateinit var eventingOutAdapter: EventingOutAdapter
+
+ @InjectMockKs
+ private lateinit var depositUsecase: DepositUsecase
+
+ private val validIban = Iban("DE00123456789")
+
+ @Test
+ fun `should deposit on valid account`() {
+ //Arrange
+ val account = Account(
+ CustomerNumber("000123"),
+ validIban,
+ Money(Amount(BigDecimal.valueOf(123.45)))
+ )
+
+ every {
+ accountOutPort.findAccount(any())
+ } returns account
+
+ every {
+ accountOutPort.deposit(any(), Amount(BigDecimal.ONE))
+ } returns Account(
+ account.customerNumber,
+ validIban,
+ Money(Amount(BigDecimal.valueOf(124.45)))
+ )
+
+ //Act
+ depositUsecase.deposit(validIban, Amount(BigDecimal.ONE))
+
+ //Assert
+ verify {
+ eventingOutAdapter.publishEvent(any(MoneyDepositedEvent::class))
+ }
+ }
+
+ @Test
+ fun `should not deposit with non existent account`() {
+ //Arrange
+ every {
+ accountOutPort.deposit(any(Account::class), Amount(BigDecimal.ONE))
+ } throws AccountNotFoundException("Account not found")
+
+ every {
+ accountOutPort.findAccount(any())
+ } returns Account(CustomerNumber("abc"), validIban, Money(Amount(BigDecimal.ONE)))
+
+ // Act and Assert
+ assertThrows {
+ depositUsecase.deposit(validIban, Amount(BigDecimal.ONE))
+ }
+
+ verify(exactly = 0) {
+ eventingOutAdapter.publishEvent(any(MoneyDepositedEvent::class))
+ }
+ }
+
+ @Test
+ fun `should not deposit with negative deposit value`() {
+ //Assert
+ assertThrows {
+ depositUsecase.deposit(validIban, Amount(BigDecimal.ONE.negate()))
+ }
+
+ verify(exactly = 0) {
+ eventingOutAdapter.publishEvent(any(MoneyDepositedEvent::class))
+ }
+ }
+
+}
diff --git a/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/integrationtest/GlobalIT.kt b/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/integrationtest/GlobalIT.kt
index fa94f07..420340c 100644
--- a/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/integrationtest/GlobalIT.kt
+++ b/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/integrationtest/GlobalIT.kt
@@ -1,7 +1,12 @@
package io.holixon.cqrshexagonaldemo.demoparent.transactions.integrationtest
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.inbound.account.CreateAccountInPort
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.inbound.account.DepositAccountInPort
import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.inbound.customer.CreateCustomerInPort
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.application.port.outbound.account.AccountOutPort
+import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.account.Amount
import io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.model.common.Name
+import org.assertj.core.api.Assertions
import org.hamcrest.CoreMatchers.containsString
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
@@ -9,12 +14,22 @@ import org.springframework.http.MediaType
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import java.math.BigDecimal
class GlobalIT : IntegrationTextBase() {
@Autowired
private lateinit var createCustomerInPort: CreateCustomerInPort
+ @Autowired
+ private lateinit var createAccountInPort: CreateAccountInPort
+
+ @Autowired
+ private lateinit var depositAccountInPort: DepositAccountInPort
+
+ @Autowired
+ private lateinit var accountOutPort: AccountOutPort
+
@Test
fun `should create customer`() {
// given
@@ -26,15 +41,16 @@ class GlobalIT : IntegrationTextBase() {
// when
val response = this.mockMvc.perform(
- post("/api/v1/customer/create")
- .accept(MediaType.APPLICATION_JSON)
- .contentType(MediaType.APPLICATION_JSON)
- .content(payload))
+ post("/api/v1/customer/create")
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(payload)
+ )
// then
response.andExpect(status().isOk)
- .andExpect(content().string(containsString("\"name\":\"Dagobert Duck\"")))
- .andExpect(content().string(containsString("\"customerNumber\":")))
+ .andExpect(content().string(containsString("\"name\":\"Dagobert Duck\"")))
+ .andExpect(content().string(containsString("\"customerNumber\":")))
}
@Test
@@ -50,14 +66,44 @@ class GlobalIT : IntegrationTextBase() {
// when
val response = this.mockMvc.perform(
- post("/api/v1/account/create")
- .accept(MediaType.APPLICATION_JSON)
- .contentType(MediaType.APPLICATION_JSON)
- .content(payload))
+ post("/api/v1/account/create")
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(payload)
+ )
// then
response.andExpect(status().isOk)
- .andExpect(content().string(containsString("\"customerNumber\":\"${customer.customerNumber.value}\"")))
- .andExpect(content().string(containsString("\"iban\":")))
+ .andExpect(content().string(containsString("\"customerNumber\":\"${customer.customerNumber.value}\"")))
+ .andExpect(content().string(containsString("\"iban\":")))
+ }
+
+
+ @Test
+ fun `should deposit money on existing account`() {
+ // given
+ val customer = createCustomerInPort.createCustomer(Name("Charles Montgomery Burns"))
+ val account = createAccountInPort.createAccount(customer.customerNumber)
+ depositAccountInPort.deposit(account.iban, Amount(BigDecimal.valueOf(123.45)))
+
+ val payload = """
+ {
+ "iban": "${account.iban}",
+ "amount": 111.11
+ }
+ """.trimIndent()
+
+ // when
+ val response = this.mockMvc.perform(
+ post("/api/v1/account/deposit")
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(payload)
+ )
+
+ // then
+ response.andExpect(status().isNoContent)
+ val currentAccount = accountOutPort.findAccount(account.iban)
+ Assertions.assertThat(currentAccount.balance.amount).isEqualTo(Amount(BigDecimal.valueOf(234.56)))
}
}
\ No newline at end of file
diff --git a/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/integrationtest/testcontainers/MySqlTestContainerExtension.kt b/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/integrationtest/testcontainers/MySqlTestContainerExtension.kt
index 9833a36..aca7526 100644
--- a/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/integrationtest/testcontainers/MySqlTestContainerExtension.kt
+++ b/demo-transactions/src/test/kotlin/io/holixon/cqrshexagonaldemo/demoparent/transactions/integrationtest/testcontainers/MySqlTestContainerExtension.kt
@@ -16,9 +16,9 @@ class MySqlTestContainerExtension : BeforeAllCallback, AfterAllCallback {
override fun beforeAll(p0: ExtensionContext?) {
mysql = MySQLContainer(CONTAINER_IMAGE_TAG)
- .withDatabaseName("cqrs-meets-hexagonal")
- .withUsername("SA")
- .withPassword("Aut0mat0rs!")
+ .withDatabaseName("cqrs-meets-hexagonal-test")
+ .withUsername("dbuser")
+ .withPassword("password")
mysql.start()
diff --git a/docker/azure-sql-edge/data/.gitkeep b/docker/azure-sql-edge/data/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docker/azure-sql-edge/docker-compose.yaml b/docker/azure-sql-edge/docker-compose.yaml
deleted file mode 100644
index df73973..0000000
--- a/docker/azure-sql-edge/docker-compose.yaml
+++ /dev/null
@@ -1,55 +0,0 @@
-version: "3.5"
-name: cqrs-meets-hexagonal
-
-services:
- database:
- image: mcr.microsoft.com/azure-sql-edge
- container_name: cqrs-meets-hexagonal-azure-sql
- environment:
- ACCEPT_EULA: Y
- MSSQL_SA_PASSWORD: Aut0mat0rs!
- TZ: UTC
- ports:
- - "1434:1433"
- volumes:
- - ./data:/var/opt/mssql/data
- - ./log:/var/opt/mssql/log
- - ./secrets:/var/opt/mssql/secrets
-
- databaseSetup:
- image: mcr.microsoft.com/mssql/server:2019-latest
- container_name: cqrs-meets-hexagonal-azure-sql-setup
- depends_on:
- - database
- restart: "no"
- environment:
- TZ: UTC
- volumes:
- - ./local:/opt/startup-scripts
- entrypoint: [ "bash", "-c", "while ! /opt/mssql-tools/bin/sqlcmd -S cqrs-meets-hexagonal-azure-sql -U SA -P 'Aut0mat0rs!' -Q \"$$(cat /opt/startup-scripts/local-db-setup.sql)\"; do sleep 5; done" ]
-
- zookeeper:
- image: confluentinc/cp-zookeeper:latest
- environment:
- ZOOKEEPER_CLIENT_PORT: 2182
- ZOOKEEPER_TICK_TIME: 2000
- ports:
- - 22182:2182
-
- kafka:
- image: confluentinc/cp-kafka:latest
- depends_on:
- - zookeeper
- ports:
- - 29093:29092
- environment:
- KAFKA_BROKER_ID: 1
- KAFKA_ZOOKEEPER_CONNECT: zookeeper:2182
- KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
- KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
- KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
- KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
-networks:
- cqrs-meets-hexagonal:
- external: true
diff --git a/docker/azure-sql-edge/local/local-db-setup.sql b/docker/azure-sql-edge/local/local-db-setup.sql
deleted file mode 100644
index 67bc883..0000000
--- a/docker/azure-sql-edge/local/local-db-setup.sql
+++ /dev/null
@@ -1,3 +0,0 @@
-CREATE DATABASE [cqrs-meets-hexagonal];
-ALTER DATABASE [cqrs-meets-hexagonal] SET READ_COMMITTED_SNAPSHOT ON;
-ALTER DATABASE [cqrs-meets-hexagonal] SET ALLOW_SNAPSHOT_ISOLATION ON;
\ No newline at end of file
diff --git a/docker/azure-sql-edge/local/rename-dbo-schema.sql b/docker/azure-sql-edge/local/rename-dbo-schema.sql
deleted file mode 100644
index 03e95e7..0000000
--- a/docker/azure-sql-edge/local/rename-dbo-schema.sql
+++ /dev/null
@@ -1,4 +0,0 @@
-CREATE SCHEMA [transactions];
-GO;
-CREATE SCHEMA [query];
-GO;
diff --git a/docker/azure-sql-edge/log/.gitkeep b/docker/azure-sql-edge/log/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docker/azure-sql-edge/run.sh b/docker/azure-sql-edge/run.sh
deleted file mode 100755
index 5a6cb7b..0000000
--- a/docker/azure-sql-edge/run.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-colima start --cpu 2 --memory 4 --disk 10
-docker-compose up
diff --git a/docker/azure-sql-edge/secrets/.gitkeep b/docker/azure-sql-edge/secrets/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docker/mysql/docker-compose.yaml b/docker/mysql/docker-compose.yaml
index 631ab74..3202977 100644
--- a/docker/mysql/docker-compose.yaml
+++ b/docker/mysql/docker-compose.yaml
@@ -12,8 +12,6 @@ services:
MYSQL_ROOT_PASSWORD: 'Aut0mat0rs!'
ports:
- '3306:3306'
- volumes:
- - ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
data:
diff --git a/docker/mysql/init.sql b/docker/mysql/init.sql
deleted file mode 100644
index c9ace02..0000000
--- a/docker/mysql/init.sql
+++ /dev/null
@@ -1,7 +0,0 @@
-CREATE SCHEMA IF NOT EXISTS `transactions`;
-CREATE SCHEMA IF NOT EXISTS `query`;
-
-GRANT ALL PRIVILEGES ON transactions.* TO 'SA'@'%';
-GRANT ALL PRIVILEGES ON query.* TO 'SA'@'%';
-FLUSH PRIVILEGES;
-
diff --git a/pom.xml b/pom.xml
index 266b1dc..adc92bd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,5 +36,6 @@
2.4.0
1.19.7
2.0.1.Final
+ 1.13.10