Skip to content

Instantly share code, notes, and snippets.

@fteychene
Last active July 29, 2020 06:42
Show Gist options
  • Save fteychene/8e525d4e7e44838ce366d7852e97e1e7 to your computer and use it in GitHub Desktop.
Save fteychene/8e525d4e7e44838ce366d7852e97e1e7 to your computer and use it in GitHub Desktop.
Hexagonal architecture as polymorphic monad stack in Kotlin with Arrow
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.3.21"
kotlin("kapt") version "1.3.21"
}
repositories {
mavenCentral()
}
val arrowVersion = "0.9.0"
dependencies {
implementation(kotlin("stdlib"))
compile("io.arrow-kt:arrow-core-data:$arrowVersion")
compile("io.arrow-kt:arrow-core-extensions:$arrowVersion")
compile("io.arrow-kt:arrow-syntax:$arrowVersion")
compile("io.arrow-kt:arrow-typeclasses:$arrowVersion")
compile("io.arrow-kt:arrow-extras-data:$arrowVersion")
compile("io.arrow-kt:arrow-extras-extensions:$arrowVersion")
kapt("io.arrow-kt:arrow-meta:$arrowVersion")
compile("io.arrow-kt:arrow-effects-data:$arrowVersion")
compile("io.arrow-kt:arrow-effects-extensions:$arrowVersion")
compile("io.arrow-kt:arrow-effects-io-extensions:$arrowVersion")
compile("io.arrow-kt:arrow-optics:$arrowVersion")
compile("io.arrow-kt:arrow-effects-rx2-data:$arrowVersion")
compile("io.arrow-kt:arrow-effects-rx2-extensions:$arrowVersion")
compile("io.reactivex.rxjava2:rxjava:2.2.8")
compile("io.arrow-kt:arrow-effects-reactor-data:$arrowVersion")
compile("io.arrow-kt:arrow-effects-reactor-extensions:$arrowVersion")
compile("io.projectreactor:reactor-core:3.2.8.RELEASE")
testCompile("junit", "junit", "4.12")
}
configure<JavaPluginConvention> {
sourceCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
import arrow.core.Either
import arrow.core.extensions.id.applicative.just
import arrow.core.right
import arrow.data.EitherT
import arrow.data.extensions.eithert.monad.monad
import arrow.data.fix
import arrow.effects.IO
import arrow.effects.extensions.io.monadDefer.monadDefer
import arrow.effects.fix
import arrow.effects.reactor.FluxK
import arrow.effects.reactor.MonoK
import arrow.effects.reactor.ForMonoK
import arrow.effects.reactor.extensions.fluxk.monadDefer.monadDefer
import arrow.effects.reactor.extensions.monok.monadDefer.monadDefer
import arrow.effects.reactor.fix
import arrow.effects.reactor.k
import arrow.effects.rx2.ObservableK
import arrow.effects.rx2.SingleK
import arrow.effects.rx2.extensions.observablek.monadDefer.monadDefer
import arrow.effects.rx2.extensions.singlek.monadDefer.monadDefer
import arrow.effects.rx2.fix
import arrow.effects.typeclasses.MonadDefer
import reactor.core.publisher.Mono
/**
* Domain
*/
sealed class Error
class Domain<F>(val monadDefer: MonadDefer<F>,
val dataFetcher: DataFetcher<F> = GenericDataFetcher(monadDefer),
val repository: Repository<F> = GenericRepository(monadDefer),
val logger: Logger<F> = ConsoleLogger(monadDefer))
fun <F> domainLogic(domain: Domain<F>, initialValue: Int): EitherT<F, Error, String> =
domain.run {
EitherT.monad<F, Error>(monadDefer).run {
binding {
logger.log("Load value").bind()
val result = dataFetcher.loadValues(initialValue)
.map { it * 2 }.bind()
logger.log("Insert in database").bind()
repository.persist(result).bind()
}.fix()
}
}
/**
* Ports
*/
interface Logger<F> {
fun log(message: String): EitherT<F, Error, Unit>
}
interface DataFetcher<F> {
fun loadValues(id: Int): EitherT<F, Error, Int>
}
interface Repository<F> {
fun persist(value: Int): EitherT<F, Error, String>
}
/**
* Infra
*/
class ConsoleLogger<F>(MDF: MonadDefer<F>) : Logger<F>, MonadDefer<F> by MDF {
override fun log(message: String): EitherT<F, Error, Unit> =
EitherT.liftF(this, delay { println(message) })
}
class GenericDataFetcher<F>(MDF: MonadDefer<F>) : DataFetcher<F>, MonadDefer<F> by MDF {
override fun loadValues(id: Int): EitherT<F, Error, Int> =
EitherT.just(this, id)
}
class GenericRepository<F>(MDF: MonadDefer<F>) : Repository<F>, MonadDefer<F> by MDF {
override fun persist(value: Int): EitherT<F, Error, String> =
EitherT.just(this, value.toString())
}
class SpecificReactorMonoRepository : Repository<ForMonoK> {
override fun persist(value: Int): EitherT<ForMonoK, Error, String> =
EitherT(Mono.just(value.right() as Either<Error, String>).k())
}
/**
* Program
*/
fun main() {
println("Run with Arrow IO")
val io = domainLogic(Domain(IO.monadDefer()), 60).value().fix()
io.attempt().unsafeRunAsync { println(it) }
println()
println("Run with RxJava Single")
val single = domainLogic(Domain(SingleK.monadDefer()), 60).value().fix()
single.single.subscribe(::println, ::println)
println()
println("Run with RxJava Observable")
val observable = domainLogic(Domain(ObservableK.monadDefer()), 60).value().fix()
observable.observable.subscribe(::println, ::println)
println()
println("Run with Reactor Mono")
val mono = domainLogic(Domain(MonoK.monadDefer(), repository = SpecificReactorMonoRepository()), 60).value().fix()
mono.mono.subscribe(::println, ::println)
println()
println("Run with Reactor Flux")
val flux = domainLogic(Domain(FluxK.monadDefer()), 60).value().fix()
flux.flux.subscribe(::println, ::println)
println()
}
Run with Arrow IO
Load value
Insert in database
Right(b=Right(b=Right(b=120)))
Run with RxJava Single
Load value
Insert in database
Right(b=120)
Run with RxJava Observable
Load value
Insert in database
Right(b=120)
Run with Reactor Mono
Load value
Insert in database
Right(b=120)
Run with Reactor Flux
Load value
Insert in database
Right(b=120)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment