Last active
September 13, 2021 22:48
-
-
Save nomisRev/1f91710ebec1709d4ce8059812482624 to your computer and use it in GitHub Desktop.
Kotlin DI with receivers & interface delegation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import arrow.core.* | |
import memeid.UUID | |
data class User(val email: String, val name: String) { | |
companion object | |
} | |
data class ProcessedUser(val id: UUID, val email: String, val name: String) { | |
companion object | |
} | |
object ProcessingError | |
interface Repo { | |
suspend fun fetchUsers(): List<User> | |
} | |
fun MockRepo(): Repo = object : Repo { | |
override suspend fun fetchUsers(): List<User> = | |
listOf( | |
User("[email protected]", "Simon"), | |
User("[email protected]", "Raul"), | |
User("[email protected]", "Jorge") | |
) | |
} | |
interface Persistence { | |
suspend fun User.process(): Either<ProcessingError, ProcessedUser> | |
suspend fun List<User>.process(): Either<ProcessingError, List<ProcessedUser>> = | |
traverseEither { | |
it.process() | |
} | |
} | |
fun MockPersistence(): Persistence = object : Persistence { | |
override suspend fun User.process(): Either<ProcessingError, ProcessedUser> = | |
if (email.contains(Regex("^(.+)@(.+)$"))) Either.Right(ProcessedUser(UUID.V4.squuid(), email, name)) | |
else Either.Left(ProcessingError) | |
} | |
/* | |
* When using the generic method as shown below you define top-level functions | |
* which could be considered a "Service" which is typically composed by repos | |
* from the network or persistence layer. | |
* These functions are your business logic, which are in turn typically called by | |
* your routes or controllers. | |
* | |
* The biggest advantage of this approach is that this function explicitly defines | |
* everything that it uses in its signature. | |
* Because of that you can call if from anywhere that satisfies those constraints. | |
*/ | |
/** | |
* The generic top-level function | |
* Enables the [getProcessUsers] function (or syntax) when the [Persistence] & [Repo] constraints are met. | |
*/ | |
suspend fun <R> R.getProcessUsers(/* add any arguments as needed */): Either<ProcessingError, List<ProcessedUser>> | |
where R : Repo, | |
R : Persistence = | |
fetchUsers().process() | |
/** | |
* We define a class that satisfies both [Persistence] & [Repo] such that the | |
* [getProcessUsers] function automatically gets added through the top-level definition. | |
* | |
* We use Kotlin's interface delegation system to automatically implement the interfaces | |
* by passing runtime representations of them. | |
*/ | |
class DataModule( | |
persistence: Persistence, | |
repo: Repo | |
) : Persistence by persistence, Repo by repo | |
suspend fun main(): Unit { | |
// This is your router { get { } } router definition or | |
// your Android launch { } or compose function. | |
// Generic top-level function automatically got enabled | |
val processedUsers = DataModule(MockPersistence(), MockRepo()).getProcessUsers() | |
println(processedUsers) | |
// Call the alternative approach | |
val processedUsers2 = DataModule2(MockPersistence(), MockRepo()).getProcessUsers2() | |
println(processedUsers2) | |
} | |
/** | |
* Another approach to defining [DataModule] could be using an interface and a function. | |
*/ | |
interface DataModule2 : Persistence, Repo | |
fun DataModule2(persistence: Persistence, repo: Repo): DataModule2 = | |
object : DataModule2, Repo by repo, Persistence by persistence {} | |
/** | |
* When using this style you could also consider an alternative way of defining [getProcessUsers] | |
*/ | |
suspend fun DataModule2.getProcessUsers2(/* add any arguments as needed */): Either<ProcessingError, List<ProcessedUser>> = | |
fetchUsers().process() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Discussion on Kotlin Slack concerning some of this:
https://kotlinlang.slack.com/archives/C5UPMM0A0/p1624026498160200