Skip to content

Commit 5d89984

Browse files
authored
Merge pull request #3 from lmirabal/account-operations
Account operations
2 parents 0f18f16 + b15318b commit 5d89984

21 files changed

Lines changed: 441 additions & 27 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ This example mainly focuses in the contract defining how the business behaves as
3131
approach, but the same strategy can be applied to any other port. For examples, if there were multiple ways to
3232
store/query accounts, a single test could be defined to test all of them.
3333

34+
## How to run
35+
36+
The full stack can be run in a single jvm for demonstration purposes using `Main.kt` in the `web` project:
37+
```shell
38+
./gradlew :web:run
39+
```
40+
3441
## Technology
3542

3643
The intention is to have as simple as possible code so very few external dependencies are used. It's implemented in

domain/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ plugins {
33
}
44

55
dependencies {
6+
api 'dev.forkhandles:result4k:1.6.0.0'
7+
68
testFixturesImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
79
testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
810
testFixturesImplementation 'com.natpryce:hamkrest:1.8.0.1'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package lmirabal.bank
22

3+
import dev.forkhandles.result4k.Result
4+
import lmirabal.bank.model.Amount
35
import lmirabal.bank.model.BankAccount
6+
import lmirabal.bank.model.BankAccountId
7+
import lmirabal.bank.model.NotEnoughFunds
48

59
interface Bank {
610
fun createAccount(): BankAccount
711
fun listAccounts(): List<BankAccount>
12+
fun deposit(id: BankAccountId, amount: Amount): BankAccount
13+
fun withdraw(id: BankAccountId, amount: Amount): Result<BankAccount, NotEnoughFunds>
814
}

domain/src/main/kotlin/lmirabal/bank/BankService.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package lmirabal.bank
22

3+
import dev.forkhandles.result4k.Result
4+
import dev.forkhandles.result4k.peek
35
import lmirabal.bank.data.BankAccountRepository
46
import lmirabal.bank.model.Amount
57
import lmirabal.bank.model.BankAccount
68
import lmirabal.bank.model.BankAccountId
9+
import lmirabal.bank.model.NotEnoughFunds
710

811
class BankService(
912
private val accountRepository: BankAccountRepository,
@@ -15,4 +18,16 @@ class BankService(
1518
}
1619

1720
override fun listAccounts(): List<BankAccount> = accountRepository.list()
21+
22+
override fun deposit(id: BankAccountId, amount: Amount): BankAccount {
23+
val account = accountRepository.list().first { it.id == id }
24+
return account.deposit(amount)
25+
.also { updatedAccount -> accountRepository.update(updatedAccount) }
26+
}
27+
28+
override fun withdraw(id: BankAccountId, amount: Amount): Result<BankAccount, NotEnoughFunds> {
29+
val account = accountRepository.list().first { it.id == id }
30+
return account.withdraw(amount)
31+
.peek { updatedAccount -> accountRepository.update(updatedAccount) }
32+
}
1833
}

domain/src/main/kotlin/lmirabal/bank/data/BankAccountRepository.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ import lmirabal.bank.model.BankAccount
44

55
interface BankAccountRepository {
66
fun add(account: BankAccount)
7+
fun update(account: BankAccount)
78
fun list(): List<BankAccount>
89
}
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package lmirabal.bank.data
22

33
import lmirabal.bank.model.BankAccount
4+
import lmirabal.bank.model.BankAccountId
45

56
class InMemoryBankAccountRepository : BankAccountRepository {
6-
private val accounts: MutableList<BankAccount> = arrayListOf()
7+
private val accounts = mutableMapOf<BankAccountId, BankAccount>()
78

89
override fun add(account: BankAccount) {
9-
accounts.add(account)
10+
accounts[account.id] = account
1011
}
1112

12-
override fun list(): List<BankAccount> = accounts
13+
override fun update(account: BankAccount) {
14+
add(account)
15+
}
16+
17+
override fun list(): List<BankAccount> = accounts.values.toList()
1318
}

domain/src/main/kotlin/lmirabal/bank/model/Amount.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
package lmirabal.bank.model
22

3-
data class Amount(val minorUnits: Long) {
3+
data class Amount(val minorUnits: Long) : Comparable<Amount> {
4+
init {
5+
require(minorUnits >= 0)
6+
}
7+
8+
operator fun plus(other: Amount): Amount {
9+
return Amount(minorUnits + other.minorUnits)
10+
}
11+
12+
operator fun minus(other: Amount): Amount {
13+
return Amount(minorUnits - other.minorUnits)
14+
}
15+
16+
override fun compareTo(other: Amount): Int {
17+
return minorUnits.compareTo(other.minorUnits)
18+
}
419

520
companion object {
621
val ZERO = Amount(0)
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
package lmirabal.bank.model
22

3+
import dev.forkhandles.result4k.Failure
4+
import dev.forkhandles.result4k.Result
5+
import dev.forkhandles.result4k.Success
36
import java.util.UUID
47

5-
data class BankAccount(val id: BankAccountId, val balance: Amount)
8+
data class BankAccount(val id: BankAccountId, val balance: Amount) {
9+
fun deposit(amount: Amount): BankAccount {
10+
return copy(balance = balance + amount)
11+
}
12+
13+
fun withdraw(amount: Amount): Result<BankAccount, NotEnoughFunds> {
14+
return if (amount <= balance) Success(copy(balance = balance - amount))
15+
else Failure(NotEnoughFunds(id, balance, amount - balance))
16+
}
17+
}
618

719
data class BankAccountId(val value: UUID) {
820

921
companion object {
1022
fun random() = BankAccountId(UUID.randomUUID())
1123
}
1224
}
25+
26+
data class NotEnoughFunds(val id: BankAccountId, val balance: Amount, val additionalFundsRequired: Amount)

domain/src/test/kotlin/lmirabal/bank/BankServiceTest.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,26 @@ import lmirabal.bank.model.BankAccountId
99
import org.junit.jupiter.api.Test
1010

1111
class BankServiceTest : BankTest() {
12-
private val id = BankAccountId.random()
13-
private val idFactory = { id }
12+
private val idFactory = RecordingIdFactory()
1413
override val bank = BankService(InMemoryBankAccountRepository(), idFactory)
1514

1615
@Test
1716
fun createsAnAccount() {
1817
val account = bank.createAccount()
1918

19+
val id = idFactory.last()
2020
assertThat(account, equalTo(BankAccount(id, Amount.ZERO)))
2121
}
22+
23+
class RecordingIdFactory : () -> BankAccountId {
24+
private val values = mutableListOf<BankAccountId>()
25+
26+
override fun invoke(): BankAccountId {
27+
return BankAccountId.random().also { values.add(it) }
28+
}
29+
30+
fun last(): BankAccountId {
31+
return values.last()
32+
}
33+
}
2234
}

domain/src/test/kotlin/lmirabal/bank/data/InMemoryBankAccountRepositoryTest.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,25 @@ class InMemoryBankAccountRepositoryTest {
1111
private val repository = InMemoryBankAccountRepository()
1212

1313
@Test
14-
fun persistsAccounts() {
14+
fun `persists accounts`() {
1515
val account1 = BankAccount(BankAccountId.random(), Amount(10))
1616
repository.add(account1)
1717
val account2 = BankAccount(BankAccountId.random(), Amount(20))
1818
repository.add(account2)
1919

2020
assertThat(repository.list(), equalTo(listOf(account1, account2)))
2121
}
22+
23+
@Test
24+
fun `updates existing accounts`() {
25+
val account1 = BankAccount(BankAccountId.random(), Amount(10))
26+
repository.add(account1)
27+
val account2 = BankAccount(BankAccountId.random(), Amount(20))
28+
repository.add(account2)
29+
30+
val updatedAccount1 = BankAccount(account1.id, Amount(50))
31+
repository.update(updatedAccount1)
32+
33+
assertThat(repository.list(), equalTo(listOf(updatedAccount1, account2)))
34+
}
2235
}

0 commit comments

Comments
 (0)