Skip to content

Commit

Permalink
Merge pull request #39 from holixon/19-deposit-money-to-bank-account
Browse files Browse the repository at this point in the history
19 - deposit money to bank account
  • Loading branch information
mmiikkkkaa authored Apr 19, 2024
2 parents 815e7e0 + dfe8a86 commit 4047281
Show file tree
Hide file tree
Showing 42 changed files with 457 additions and 186 deletions.
33 changes: 29 additions & 4 deletions demo-transactions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.nhaarman.mockitokotlin2</groupId>
<artifactId>mockito-kotlin</artifactId>
<version>${mockito-kotlin.version}</version>
<groupId>io.mockk</groupId>
<artifactId>mockk-jvm</artifactId>
<version>${mockk.version}</version>
<scope>test</scope>
</dependency>

Expand Down Expand Up @@ -251,7 +251,6 @@
</dependency>
</dependencies>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
Expand Down Expand Up @@ -296,6 +295,32 @@
<driver>com.mysql.jdbc.Driver</driver>
</configuration>
</plugin>
<plugin>
<!-- plugin for running integration tests -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
<configuration>
<!-- all tests should run with test profile to bind application-test.yaml -->
<argLine>-Dspring.profiles.active=test</argLine>
<!-- the repackaging done by spring-boot-maven-plugin places the classes and resources
of your application in an unusual location in the JAR, see
https://stackoverflow.com/questions/42082131/maven-verify-classnotfoundexception-for-class-of-spring-boot-application -->
<classesDirectory>${project.build.outputDirectory}</classesDirectory>
<!-- see https://sandor.tech/2017/10/16/integration-tests-with-spring-boot-classnotfoundexception.html -->
<additionalClasspathElements>
<additionalClasspathElement>${basedir}/target/classes</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -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<Unit> {
return ResponseEntity.notFound().build()
}

@ExceptionHandler(InvalidInputException::class)
fun handleException(request: HttpServletRequest, exception: InvalidInputException): ResponseEntity<Unit> {
return ResponseEntity.badRequest().build()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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<Void> {
depositAccountInPort.deposit(
Iban(depositRequestDto.iban), Amount(BigDecimal.valueOf(depositRequestDto.amount)))
return ResponseEntity.noContent().build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,43 @@ 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(
private val jpaAccountOutAdapter: JpaAccountOutAdapter,
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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -13,15 +14,15 @@ class AccountEntityMapper {
return AccountEntity(
customerNumber = account.customerNumber.value,
iban = account.iban.value,
balance = account.balance.amount
balance = account.balance.amount.value
)
}

fun toDomain(accountEntity: AccountEntity): Account {
return Account(
customerNumber = CustomerNumber(accountEntity.customerNumber),
iban = Iban(accountEntity.iban),
balance = Money(accountEntity.balance)
balance = Money(Amount(accountEntity.balance))
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
)
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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")

Expand All @@ -40,6 +44,7 @@ open class CreateAccountUsecase(
val accountCreatedEvent = AccountCreatedEvent(customerNumber, createdAccount.iban)
eventingOutAdapter.publishEvent(accountCreatedEvent)

logger().info { "newly created account: $createdAccount" }
return createdAccount
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
Expand All @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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" }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.exception

class AccountNotFoundException(message: String) : RuntimeException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.holixon.cqrshexagonaldemo.demoparent.transactions.domain.exception

class InvalidInputException(message: String) : RuntimeException(message)
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

}
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading

0 comments on commit 4047281

Please sign in to comment.