Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CreditLine #6

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,32 @@ data class CloseAccount(
val description: String? = null,
override val timestamp: Instant = Instant.now()
): AccountCommand

data class OpenCreditLine(
@TargetAggregateIdentifier override val accountId: AccountId,
val amount: Double,
override val timestamp: Instant = Instant.now()
): AccountCommand

data class IncreaseCreditLine(
@TargetAggregateIdentifier override val accountId: AccountId,
val amount: Double,
override val timestamp: Instant = Instant.now()
): AccountCommand

data class DecreaseCreditLine(
@TargetAggregateIdentifier override val accountId: AccountId,
val amount: Double,
override val timestamp: Instant = Instant.now()
): AccountCommand

data class CloseCreditLine(
@TargetAggregateIdentifier override val accountId: AccountId,
override val timestamp: Instant = Instant.now()
): AccountCommand

data class AmountBorrowed(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event in the commands?

@TargetAggregateIdentifier override val accountId: AccountId,
val amount: Double,
override val timestamp: Instant = Instant.now()
): AccountCommand
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ fun <T> invalidAccountNumber() = BadInput<T>("invalid account number")
fun <T> accountAlreadyExists() = BadRequest<T>("account already exists")
fun <T> accountClosed() = BadRequest<T>("account closed")
fun <T> notEnoughCredit() = BadRequest<T>("no enough credit")
fun <T> creditLineAlreadyOpened() = BadRequest<T>("credit line already opened")
fun <T> creditLineNotOpened() = BadRequest<T>("credit line not opened")
fun <T> creditLineNotReimbursed() = BadRequest<T>("credit line not reimbursed")
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,26 @@ data class AccountClosed(
val description: String? = null,
override val timestamp: Instant = Instant.now()
): AccountEvent

data class CreditLineOpened(
override val accountId: AccountId,
val amount: Double,
override val timestamp: Instant = Instant.now()
): AccountEvent

data class CreditLineIncreased(
override val accountId: AccountId,
val amount: Double,
override val timestamp: Instant = Instant.now()
): AccountEvent

data class CreditLineDecreased(
override val accountId: AccountId,
val amount: Double,
override val timestamp: Instant = Instant.now()
): AccountEvent

data class CreditLineClosed(
override val accountId: AccountId,
override val timestamp: Instant = Instant.now()
): AccountEvent
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ internal class Account() {

private var balance: Double = 0.0
private var closed: Boolean = false
private var creditLineOpened: Boolean = false
private var creditLineAmount: Double = 0.0

@CommandHandler
@CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING)
Expand Down Expand Up @@ -49,7 +51,7 @@ internal class Account() {
@CommandHandler
fun handle(command: WithdrawAmount): Status<Unit> = when {
closed -> accountClosed()
balance < command.amount -> notEnoughCredit()
balance + creditLineAmount < command.amount -> notEnoughCredit()
else -> Status.of<Unit> {
AggregateLifecycle.apply(
AmountWithdrew(
Expand All @@ -75,6 +77,67 @@ internal class Account() {
)
}

@CommandHandler
fun handle(command: OpenCreditLine): Status<Unit> = when {
closed -> accountClosed()
creditLineOpened -> creditLineAlreadyOpened()
else -> Status.of<Unit> {
AggregateLifecycle.apply(
CreditLineOpened(
accountId = accountId,
amount = command.amount,
timestamp = command.timestamp
)
)
}
}

@CommandHandler
fun handle(command: IncreaseCreditLine): Status<Unit> = when {
closed -> accountClosed()
!creditLineOpened -> creditLineNotOpened()
else -> Status.of<Unit> {
AggregateLifecycle.apply(
CreditLineIncreased(
accountId = accountId,
amount = command.amount,
timestamp = command.timestamp
)
)
}
}

@CommandHandler
fun handle(command: DecreaseCreditLine): Status<Unit> = when {
closed -> accountClosed()
!creditLineOpened -> creditLineNotOpened()
balance < 0.0 && -balance > creditLineAmount - command.amount -> creditLineNotReimbursed()
else -> Status.of<Unit> {
AggregateLifecycle.apply(
CreditLineDecreased(
accountId = accountId,
amount = command.amount,
timestamp = command.timestamp
)
)
}
}

@CommandHandler
fun handle(command: CloseCreditLine): Status<Unit> = when {
closed -> accountClosed()
!creditLineOpened -> creditLineNotOpened()
balance < 0.0 -> creditLineNotReimbursed()
else -> Status.of<Unit> {
AggregateLifecycle.apply(
CreditLineClosed(
accountId = accountId,
timestamp = command.timestamp
)
)
}
}

@EventSourcingHandler
fun on(event: NewAccountOpened) {
accountId = event.accountId
Expand All @@ -97,4 +160,26 @@ internal class Account() {
closed = true
}

@EventSourcingHandler
fun on(event: CreditLineOpened) {
creditLineOpened = true
creditLineAmount = event.amount
}

@EventSourcingHandler
fun on(event: CreditLineIncreased) {
creditLineAmount += event.amount
}

@EventSourcingHandler
fun on(event: CreditLineDecreased) {
creditLineAmount -= event.amount
}

@EventSourcingHandler
fun on(event: CreditLineClosed) {
creditLineOpened = false
creditLineAmount = 0.0
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ package org.karamelsoft.axon.bank.interfaces.http.accounts
import org.axonframework.eventsourcing.eventstore.EventStore
import org.axonframework.extensions.reactor.commandhandling.gateway.ReactorCommandGateway
import org.karamelsoft.axon.bank.interfaces.http.handleStatus
import org.karamelsoft.axon.bank.services.accounts.api.AccountId
import org.karamelsoft.axon.bank.services.accounts.api.DepositAmount
import org.karamelsoft.axon.bank.services.accounts.api.WithdrawAmount
import org.karamelsoft.axon.bank.services.accounts.api.*
import org.karamelsoft.research.axon.libraries.artifacts.api.Status
import org.karamelsoft.research.axon.libraries.artifacts.module.readEvents
import org.springframework.web.bind.annotation.*
Expand Down Expand Up @@ -45,6 +43,49 @@ class AccountController(
).handleStatus()
}

@PostMapping("/{accountId}/creditLine")
fun openCreditLine(@PathVariable accountId: String, @RequestBody action: CreditLineOpening): Mono<Unit> {
return commandGateway.send<Status<Unit>>(
OpenCreditLine(
accountId = AccountId(accountId),
amount = action.amount,
timestamp = action.timestamp
)
).handleStatus()
}

@DeleteMapping("/{accountId}/creditLine")
fun closeCreditLine(@PathVariable accountId: String, @RequestBody action: CreditLineClosing): Mono<Unit> {
return commandGateway.send<Status<Unit>>(
CloseCreditLine(
accountId = AccountId(accountId),
timestamp = action.timestamp
)
).handleStatus()
}

@PostMapping("/{accountId}/creditLine/increase")
fun increaseCreditLine(@PathVariable accountId: String, @RequestBody action: CreditLineIncrease): Mono<Unit> {
return commandGateway.send<Status<Unit>>(
IncreaseCreditLine(
accountId = AccountId(accountId),
amount = action.amount,
timestamp = action.timestamp
)
).handleStatus()
}

@PostMapping("/{accountId}/creditLine/decrease")
fun decreaseCreditLine(@PathVariable accountId: String, @RequestBody action: CreditLineDecrease): Mono<Unit> {
return commandGateway.send<Status<Unit>>(
DecreaseCreditLine(
accountId = AccountId(accountId),
amount = action.amount,
timestamp = action.timestamp
)
).handleStatus()
}

@GetMapping("/{accountId}/history")
fun getAccountHistory(@PathVariable("accountId") accountId: String): Flux<AccountFrame> {
return eventStore.readEvents(AccountId(accountId))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package org.karamelsoft.axon.bank.interfaces.http.accounts

import org.karamelsoft.axon.bank.interfaces.http.Event
import org.karamelsoft.axon.bank.services.accounts.api.AccountClosed
import org.karamelsoft.axon.bank.services.accounts.api.AmountDeposited
import org.karamelsoft.axon.bank.services.accounts.api.AmountWithdrew
import org.karamelsoft.axon.bank.services.accounts.api.NewAccountOpened
import org.karamelsoft.axon.bank.services.accounts.api.*
import java.time.Instant

interface AccountAction {
Expand All @@ -25,6 +22,25 @@ data class AmountWithdrawal(
override val timestamp: Instant = Instant.now()
) : AccountAction

data class CreditLineOpening(
val amount: Double,
override val timestamp: Instant = Instant.now()
) : AccountAction

data class CreditLineIncrease(
val amount: Double,
override val timestamp: Instant = Instant.now()
) : AccountAction

data class CreditLineDecrease(
val amount: Double,
override val timestamp: Instant = Instant.now()
) : AccountAction

data class CreditLineClosing(
override val timestamp: Instant = Instant.now()
) : AccountAction

data class AccountFrame(
val event: Event,
val state: AccountState
Expand All @@ -44,22 +60,26 @@ data class AccountFrame(

data class AccountState(
val balance: Double,
val closed: Boolean
val closed: Boolean,
val creditLineOpened: Boolean,
val creditLineAmount: Double
) {
companion object {
fun empty() = AccountState(0.0, false)
fun empty() = AccountState(0.0, false, false , 0.0)
}

fun handle(event: Any): AccountState = when (event) {
is Event -> handle(event.payload)
is NewAccountOpened -> empty()
is AmountDeposited -> deposited(event.amount)
is AmountWithdrew -> withdrew(event.amount)
is CreditLineOpened -> creditLineOpened(event.amount)
is AccountClosed -> closed()
else -> throw IllegalStateException("unknown event: ${event.javaClass.canonicalName}")
}

private fun deposited(amount: Double) = copy(balance = balance + amount)
private fun withdrew(amount: Double) = copy(balance = balance - amount)
private fun creditLineOpened(amount: Double) = copy(creditLineOpened = true, creditLineAmount = amount)
private fun closed() = copy(closed = true)
}
43 changes: 38 additions & 5 deletions tests/accounts.http
Original file line number Diff line number Diff line change
@@ -1,28 +1,61 @@
###
# @name = Deposit Amount
POST http://localhost:8081/service/interfaces/http/accounts/BE44585299287074/deposit
POST http://localhost:8081/service/interfaces/http/accounts/BE45640225317327/deposit
Content-Type: application/json

{
"amount": 28.43,
"from": "BE12345678901234",
"from": "BE45640225317327",
"description": "For the drinks at the bar"
}

###
# @name = Withdraw Amount
POST http://localhost:8081/service/interfaces/http/accounts/BE44585299287074/withdraw
POST http://localhost:8081/service/interfaces/http/accounts/BE45640225317327/withdraw
Content-Type: application/json

{
"amount": 15.78,
"to": "Myself",
"to": "BE45640225317327",
"description": "For the amazing party"
}

###
# @name = Open Credit Line
POST http://localhost:8081/service/interfaces/http/accounts/BE45640225317327/creditLine
Content-Type: application/json

{
"amount": 50.03
}

###
# @name = Increase Credit Line
POST http://localhost:8081/service/interfaces/http/accounts/BE45640225317327/creditLine/increase
Content-Type: application/json

{
"amount": 100.0
}

###
# @name = Decrease Credit Line
POST http://localhost:8081/service/interfaces/http/accounts/BE45640225317327/creditLine/decrease
Content-Type: application/json

{
"amount": 25.0
}

###
# @name = Close Credit Line
DELETE http://localhost:8081/service/interfaces/http/accounts/BE45640225317327/creditLine
Content-Type: application/json


###
# @name = Get Account History
GET http://localhost:8081/service/interfaces/http/accounts/BE44585299287074/history
GET http://localhost:8081/service/interfaces/http/accounts/BE45640225317327/history
Content-Type: application/json

###
Expand Down