Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Commit

Permalink
Draft of the Domain layer architecture (#3151)
Browse files Browse the repository at this point in the history
* Update the modules graph

* Add `NonZeroDouble`

* WIP: Re-work and draft the domain layer

* WIP: Draft stuff

* Model the domain

* Draft v0.0.1

* Fix Detekt

* Fix the unit tests
  • Loading branch information
ILIYANGERMANOV authored Apr 21, 2024
1 parent 7453ec5 commit 03638b8
Show file tree
Hide file tree
Showing 26 changed files with 1,272 additions and 839 deletions.
5 changes: 5 additions & 0 deletions all_modules.gv
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ digraph G {
":app" -> ":screen:budgets"
":app" -> ":screen:categories"
":app" -> ":screen:contributors"
":app" -> ":screen:disclaimer"
":app" -> ":screen:edit-transaction" [color=red style=bold]
":app" -> ":screen:exchange-rates"
":app" -> ":screen:features"
Expand Down Expand Up @@ -55,6 +56,9 @@ digraph G {
":screen:contributors" -> ":shared:domain"
":screen:contributors" -> ":shared:ui:core"
":screen:contributors" -> ":shared:ui:navigation"
":screen:disclaimer" -> ":shared:data:core"
":screen:disclaimer" -> ":shared:ui:core"
":screen:disclaimer" -> ":shared:ui:navigation"
":screen:edit-transaction" -> ":shared:base"
":screen:edit-transaction" -> ":shared:data:core"
":screen:edit-transaction" -> ":shared:domain"
Expand Down Expand Up @@ -202,5 +206,6 @@ digraph G {
":ci-actions:issue-assign" -> ":ci-actions:base"
":ci-actions:issue-create-comment" -> ":ci-actions:base"
":shared:data:core-testing" -> ":shared:data:core"
":shared:data:model-testing" -> ":shared:data:model"
":shared:ui:testing" -> ":shared:ui:core"
}
1,484 changes: 763 additions & 721 deletions docs/assets/modules-graph.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import com.ivy.data.model.Transaction
import com.ivy.data.model.TransactionId
import com.ivy.data.model.TransactionMetadata
import com.ivy.data.model.Transfer
import com.ivy.data.model.Value
import com.ivy.data.model.PositiveValue
import com.ivy.data.model.getFromAccount
import com.ivy.data.model.getToAccount
import com.ivy.data.model.primitive.NotBlankTrimmedString
Expand Down Expand Up @@ -44,7 +44,7 @@ class TransactionMapper @Inject constructor(
val accountId = AccountId(accountId)
val sourceAccount = accountRepository.findById(accountId)
ensureNotNull(sourceAccount) { "No source account for transaction: ${this@toDomain}" }
val fromValue = Value(
val fromValue = PositiveValue(
amount = PositiveDouble.from(amount).bind(),
asset = sourceAccount.asset
)
Expand Down Expand Up @@ -104,7 +104,7 @@ class TransactionMapper @Inject constructor(
"No destination account associated with transaction '${this@toDomain}'"
}

val toValue = Value(
val toValue = PositiveValue(
amount = toAmount?.let(PositiveDouble::from)?.getOrNull()
?: fromValue.amount,
asset = toAccount.asset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import com.ivy.data.model.Income
import com.ivy.data.model.TransactionId
import com.ivy.data.model.TransactionMetadata
import com.ivy.data.model.Transfer
import com.ivy.data.model.Value
import com.ivy.data.model.PositiveValue
import com.ivy.data.model.primitive.AssetCode
import com.ivy.data.model.primitive.AssetCode.Companion.EUR
import com.ivy.data.model.primitive.AssetCode.Companion.USD
Expand Down Expand Up @@ -78,7 +78,7 @@ class TransactionMapperTest {
),
lastUpdated = InstantNow,
removed = removed,
value = Value(
value = PositiveValue(
amount = PositiveDouble.unsafe(100.0),
asset = AssetCode.unsafe("NGN")
),
Expand Down Expand Up @@ -132,7 +132,7 @@ class TransactionMapperTest {
),
lastUpdated = Instant.EPOCH,
removed = removed,
value = Value(
value = PositiveValue(
amount = PositiveDouble.unsafe(100.0),
asset = AssetCode.unsafe("NGN")
),
Expand Down Expand Up @@ -186,12 +186,12 @@ class TransactionMapperTest {
),
lastUpdated = Instant.EPOCH,
removed = removed,
fromValue = Value(
fromValue = PositiveValue(
amount = PositiveDouble.unsafe(100.0),
asset = AssetCode.unsafe("NGN")
),
fromAccount = AccountId,
toValue = Value(
toValue = PositiveValue(
amount = PositiveDouble.unsafe(100.0),
asset = AssetCode.unsafe("NGN")
),
Expand Down Expand Up @@ -258,7 +258,7 @@ class TransactionMapperTest {
),
lastUpdated = Instant.EPOCH,
removed = removed,
value = Value(
value = PositiveValue(
amount = PositiveDouble.unsafe(100.0),
asset = EUR
),
Expand Down Expand Up @@ -384,7 +384,7 @@ class TransactionMapperTest {
),
lastUpdated = Instant.EPOCH,
removed = removed,
value = Value(
value = PositiveValue(
amount = PositiveDouble.unsafe(100.0),
asset = EUR
),
Expand Down Expand Up @@ -515,12 +515,12 @@ class TransactionMapperTest {
),
lastUpdated = Instant.EPOCH,
removed = removed,
fromValue = Value(
fromValue = PositiveValue(
amount = PositiveDouble.unsafe(50.0),
asset = EUR
),
fromAccount = AccountId,
toValue = Value(
toValue = PositiveValue(
amount = PositiveDouble.unsafe(55.0),
asset = USD
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.ivy.data.model.testing
import arrow.core.None
import arrow.core.Option
import arrow.core.getOrElse
import com.ivy.data.model.Value
import com.ivy.data.model.PositiveValue
import com.ivy.data.model.primitive.AssetCode
import com.ivy.data.model.primitive.PositiveDouble
import io.kotest.property.Arb
Expand All @@ -14,8 +14,8 @@ const val MaxArbValueAllowed = 999_999_999_999.0
fun Arb.Companion.value(
amount: Option<PositiveDouble> = None,
asset: Option<AssetCode> = None,
): Arb<Value> = arbitrary {
Value(
): Arb<PositiveValue> = arbitrary {
PositiveValue(
amount = amount.getOrElse { Arb.positiveDoubleExact(max = MaxArbValueAllowed).bind() },
asset = asset.getOrElse { Arb.assetCode().bind() }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import arrow.core.Some
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import com.ivy.data.model.AccountId
import com.ivy.data.model.Value
import com.ivy.data.model.PositiveValue
import com.ivy.data.model.primitive.AssetCode
import com.ivy.data.model.primitive.PositiveDouble
import io.kotest.matchers.shouldBe
Expand Down Expand Up @@ -59,7 +59,7 @@ class ArbTransactionTest {
income.settled shouldBe settled
income.removed shouldBe removed
income.time shouldBe now
income.value shouldBe Value(amount, asset)
income.value shouldBe PositiveValue(amount, asset)
}
}

Expand Down Expand Up @@ -103,7 +103,7 @@ class ArbTransactionTest {
expense.settled shouldBe settled
expense.removed shouldBe removed
expense.time shouldBe now
expense.value shouldBe Value(amount, asset)
expense.value shouldBe PositiveValue(amount, asset)
}
}

Expand Down Expand Up @@ -153,9 +153,9 @@ class ArbTransactionTest {
transfer.removed shouldBe removed
transfer.time shouldBe now
transfer.fromAccount shouldBe fromAccount
transfer.fromValue shouldBe Value(fromAmount, fromAsset)
transfer.fromValue shouldBe PositiveValue(fromAmount, fromAsset)
transfer.toAccount shouldBe toAccount
transfer.toValue shouldBe Value(toAmount, toAsset)
transfer.toValue shouldBe PositiveValue(toAmount, toAsset)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ data class Income(
override val lastUpdated: Instant,
override val removed: Boolean,
override val tags: List<TagId>,
val value: Value,
val value: PositiveValue,
val account: AccountId,
) : Transaction

Expand All @@ -48,7 +48,7 @@ data class Expense(
override val lastUpdated: Instant,
override val removed: Boolean,
override val tags: List<TagId>,
val value: Value,
val value: PositiveValue,
val account: AccountId,
) : Transaction

Expand All @@ -64,9 +64,9 @@ data class Transfer(
override val removed: Boolean,
override val tags: List<TagId>,
val fromAccount: AccountId,
val fromValue: Value,
val fromValue: PositiveValue,
val toAccount: AccountId,
val toValue: Value,
val toValue: PositiveValue,
) : Transaction

@Suppress("DataClassTypedIDs")
Expand All @@ -78,7 +78,7 @@ data class TransactionMetadata(
val loanRecordId: UUID?,
)

fun Transaction.getFromValue(): Value = when (this) {
fun Transaction.getFromValue(): PositiveValue = when (this) {
is Expense -> value
is Income -> value
is Transfer -> fromValue
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package com.ivy.data.model

import com.ivy.data.model.primitive.AssetCode
import com.ivy.data.model.primitive.NonZeroDouble
import com.ivy.data.model.primitive.PositiveDouble

/**
* Represents monetary value. (like 10 USD, 5 EUR, 0.005 BTC, 12 GOLD_GRAM)
*/
data class Value(
data class PositiveValue(
val amount: PositiveDouble,
val asset: AssetCode,
)

data class Value(
val amount: NonZeroDouble,
val asset: AssetCode,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ivy.data.model.primitive

import arrow.core.raise.Raise
import arrow.core.raise.ensure
import com.ivy.data.model.exact.Exact

@JvmInline
value class NonZeroDouble private constructor(val value: Double) {
companion object : Exact<Double, NonZeroDouble> {
override val exactName = "NonZeroDouble"

override fun Raise<String>.spec(raw: Double): NonZeroDouble {
ensure(raw != 0.0) { "$raw is zero! It should be non-zero" }
ensure(raw.isFinite()) { "Is not a finite number" }
return NonZeroDouble(raw)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.ivy.data.model.util

import com.ivy.data.model.Value
import com.ivy.data.model.PositiveValue
import com.ivy.data.model.primitive.PositiveDouble
import java.math.BigDecimal
import java.math.RoundingMode

fun Value.round(decimalPlaces: Int): Value = Value(
fun PositiveValue.round(decimalPlaces: Int): PositiveValue = PositiveValue(
amount = PositiveDouble.unsafe(amount.value.roundTo(decimalPlaces)),
asset = asset,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.ivy.data.model.primitive

import io.kotest.assertions.arrow.core.shouldBeLeft
import io.kotest.property.Arb
import io.kotest.property.arbitrary.double
import io.kotest.property.arbitrary.filter
import io.kotest.property.forAll
import kotlinx.coroutines.test.runTest
import org.junit.Test

class NonZeroDoubleTest {

@Test
fun `fails for zero`() {
NonZeroDouble.from(0.0).shouldBeLeft()
}

@Test
fun `fails for infinity`() {
NonZeroDouble.from(Double.POSITIVE_INFINITY).shouldBeLeft()
NonZeroDouble.from(Double.NEGATIVE_INFINITY).shouldBeLeft()
}

@Test
fun `property - valid for all non-zero finite doubles`() = runTest {
forAll(
Arb.double(includeNonFiniteEdgeCases = false)
.filter { it != 0.0 }
) { double ->
NonZeroDouble.from(double).getOrNull()?.value == double
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.ivy.data.model.Income
import com.ivy.data.model.TransactionId
import com.ivy.data.model.TransactionMetadata
import com.ivy.data.model.Transfer
import com.ivy.data.model.Value
import com.ivy.data.model.PositiveValue
import com.ivy.data.model.getFromAccount
import com.ivy.data.model.getFromValue
import com.ivy.data.model.getToAccount
Expand Down Expand Up @@ -144,7 +144,7 @@ class TransactionTest {
lastUpdated = Instant.EPOCH,
removed = false,
tags = listOf(),
value = Value(
value = PositiveValue(
amount = PositiveDouble.unsafe(1.0),
asset = AssetCode.EUR
),
Expand All @@ -166,7 +166,7 @@ class TransactionTest {
lastUpdated = Instant.EPOCH,
removed = false,
tags = listOf(),
value = Value(
value = PositiveValue(
amount = PositiveDouble.unsafe(1.0),
asset = AssetCode.EUR
),
Expand All @@ -189,11 +189,11 @@ class TransactionTest {
removed = false,
tags = listOf(),
fromAccount = AccountId,
fromValue = Value(
fromValue = PositiveValue(
amount = PositiveDouble.unsafe(1.0),
asset = AssetCode.EUR
),
toValue = Value(
toValue = PositiveValue(
amount = PositiveDouble.unsafe(1.0),
asset = AssetCode.EUR
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.ivy.data.model.util

import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import com.ivy.data.model.Value
import com.ivy.data.model.PositiveValue
import com.ivy.data.model.primitive.AssetCode
import com.ivy.data.model.primitive.PositiveDouble
import io.kotest.matchers.shouldBe
Expand Down Expand Up @@ -42,7 +42,7 @@ class ValueRoundingTest {
@TestParameter testCase: RoundingTestCase
) {
// given
val value = Value(
val value = PositiveValue(
amount = PositiveDouble.unsafe(testCase.double),
asset = AssetCode.EUR
)
Expand Down
17 changes: 0 additions & 17 deletions shared/domain/src/main/java/com/ivy/domain/model/AccountStats.kt

This file was deleted.

Loading

0 comments on commit 03638b8

Please sign in to comment.