Skip to content

Instantly share code, notes, and snippets.

@raulraja
Last active December 15, 2018 01:23
Show Gist options
  • Save raulraja/e5f09c0cbd1392cf4eee5415150ebf76 to your computer and use it in GitHub Desktop.
Save raulraja/e5f09c0cbd1392cf4eee5415150ebf76 to your computer and use it in GitHub Desktop.
Tagless data source strategies with Arrow
import arrow.Kind
import arrow.core.Option
import arrow.core.left
import arrow.core.right
import arrow.effects.typeclasses.Async
import arrow.typeclasses.ApplicativeError
data class UserId(val value: String)
data class User(val userId: UserId)
data class Task(val value: String)
sealed class UserLookupError : RuntimeException() //assuming you are using exceptions
data class UserNotInLocalStorage(val user: User) : UserLookupError()
data class UserNotInRemoteStorage(val user: User) : UserLookupError()
data class UnknownError(val underlying: Throwable) : UserLookupError()
interface DataSource<F> {
fun allTasksByUser(user: User): Kind<F, List<Task>>
}
interface Repository<F> {
fun allTasksByUser(user: User): Kind<F, List<Task>>
}
class LocalDataSource<F>(A: ApplicativeError<F, Throwable>): DataSource<F>, ApplicativeError<F, Throwable> by A {
private val localCache: Map<User, List<Task>> =
mapOf(User(UserId("user1")) to listOf(Task("LocalTask asssigned to user1")))
override fun allTasksByUser(user: User): Kind<F, List<Task>> =
Option.fromNullable(localCache[user]).fold(
{ raiseError(UserNotInLocalStorage(user)) },
{ just(it) }
)
}
class RemoteDataSource<F>(A: Async<F>) : DataSource<F>, Async<F> by A {
private val internetStorage: Map<User, List<Task>> =
mapOf(User(UserId("user2")) to listOf(Task("Remote Task assigned to user2")))
override fun allTasksByUser(user: User): Kind<F, List<Task>> =
async { cb ->
//allows you to take values from callbacks and place them back in the context of `F`
Option.fromNullable(internetStorage[user]).fold(
{ cb(UserNotInRemoteStorage(user).left()) },
{ cb(it.right()) }
)
}
}
class DefaultRepository<F>(
val localDS: DataSource<F>,
val remoteDS: RemoteDataSource<F>,
AE: ApplicativeError<F, Throwable>) : Repository<F>, ApplicativeError<F, Throwable> by AE {
override fun allTasksByUser(user: User): Kind<F, List<Task>> =
localDS.allTasksByUser(user).handleErrorWith {
when (it) {
is UserNotInLocalStorage -> remoteDS.allTasksByUser(user)
else -> raiseError(UnknownError(it))
}
}
}
class Module<F>(val A: Async<F>) {
val localDataSource: LocalDataSource<F> = LocalDataSource(A)
val remoteDataSource: RemoteDataSource<F> = RemoteDataSource(A)
val repository: Repository<F> = DefaultRepository(localDataSource, remoteDataSource, A)
}
object test {
@JvmStatic
fun main(args: Array<String>): Unit {
val user1 = User(UserId("user1"))
val user2 = User(UserId("user2"))
val user3 = User(UserId("unknown user"))
val ioModule = Module(IO.async())
ioModule.run {
println(repository.allTasksByUser(user1).fix().attempt().unsafeRunSync())
//Right(b=[Task(value=LocalTask asssigned to user1)])
println(repository.allTasksByUser(user2).fix().attempt().unsafeRunSync())
//Right(b=[Task(value=Remote Task assigned to user2)])
println(repository.allTasksByUser(user3).fix().attempt().unsafeRunSync())
//Left(a=UserNotInRemoteStorage(user=User(userId=UserId(value=unknown user))))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment