dave08
10/30/2024, 12:30 PM@JvmInline
value class Incomplete<T>(val value: T)
class FooRepo {
suspend fun getFoo(...) : Incomplete<Foo> ... // Since the db doesn't have all the info to fill in all the Foo fields
}
class FooDecorator(val fooRepo...) {
suspend fun getFoo(...) : Foo // get icomplete foo from fooRepo and fill in what's missing from other sources...
}
phldavies
10/30/2024, 12:34 PMT
would need to account for being both fully and partially populated (either with default values on some properties, or making them nullable/Option to allow for missing values)
you may be better off having an explicit variants "DbT" and "T" allowing T to be type-safe on complete data, and DbT to not worry about being partial.dave08
10/30/2024, 12:35 PMdave08
10/30/2024, 12:41 PMfun DbFoo(/* only the db fields */) = Foo(
... // fill in bogus field values
)
fun Incomplete<Foo>.complete(
// all the bogus fields need to be filled in
) = Foo(...)
and those mappers? Anyways the incomplete Foo is marked with Incomplete @phldaviesdave08
10/30/2024, 12:41 PMdave08
10/30/2024, 12:42 PMphldavies
10/30/2024, 12:42 PMval value: T
as internal/private it's possible someone could unwrap the Foo
and treat is as completedave08
10/30/2024, 12:43 PMdave08
10/30/2024, 12:46 PMcomplete
functions on the domain layer gradle module and declare them as internal then...dave08
10/30/2024, 12:47 PMdave08
10/30/2024, 12:50 PMphldavies
10/30/2024, 12:51 PMdave08
10/30/2024, 12:56 PMphldavies
10/30/2024, 1:01 PMdave08
10/30/2024, 3:59 PMdave08
10/30/2024, 4:31 PM@JvmInline
value class Incomplete<T>(private val value: T) {
// Intermediate transformer that operates on an Incomplete and returns another Incomplete
fun interface IntermediateTransformer<T> {
suspend fun invoke(input: T): T
}
// Final transformer that operates on an Incomplete and returns a complete value
fun interface FinalTransformer<T, R> {
suspend fun invoke(input: T): R
}
// Intermediate transformation that returns a new Incomplete
suspend fun map(transformer: IntermediateTransformer<T>): Incomplete<T> {
val transformedValue = transformer.invoke(value)
return Incomplete(transformedValue)
}
// Terminal transformation to produce a final value of type T
suspend fun complete(transformer: FinalTransformer<T, T>): T {
return transformer.invoke(value)
}
// Terminal transformation to produce a final value of type R
suspend fun <R> completeTo(transformer: FinalTransformer<T, R>): R {
return transformer.invoke(value)
}
}
fun <T> T.asIncomplete() = Incomplete(this)
// then we could have:
fun fooTransformer(
...
): Foo = Incomplete.FinalTransformer { foo -> ... }
...
// in controller -- enforcing specific transformations to get to different state:
getFoo(...).complete(fooTransformer(...))
This is a Monoid isn't it?
There's still something wrong with this though... since the scope is being used only to make a T, but a user could pass it.copy(...)
down just as easily... I wonder if there's a better way... at least, the usage is marked and the user is making a conscious decision to mess things up...