Also, we are using Arrow in Spring and a hexagonal...
# arrow
j
Also, we are using Arrow in Spring and a hexagonal (Ports&Adapters) Architecture and I'm really wondering, where we best draw the line between "Here Spring gouverns dependency injection" and "Here we do DI the Arrow way (with receivers)". Currently our team is leaning towards "Use Spring everywhere down to the service layer" but I would be very interested if someone already has some experience with this..?
๐Ÿ‘ 2
r
In this case I would ask myself what kind of injection strategy do you need that Spring covers that Kotlin dos not already support with receivers and soon multiple receivers and what code style does a typical small application look like. I can see choosing Spring injectiion if the rest of the teams knows Spring better than Kotlin receivers or the use case is already coded following some of the Spring injection scope strategies. Other than that if you can encode it with Kotlin and receivers it will be leaner as that is verified at compile time without additional annotation rounds or any other overhead the spring framework would add on top.
o
I found myself thinking "But I need Spring for DI...Do I?" quite often in recent time. Unfortunately my Kotlin is not good enough to see how I could go without Spring or Koin or whatever. ๐Ÿ˜• @raulraja would you mind to show how I`d inject a Repository into a Service class with bare Kotlin means? (I've seen a pattern like "Use class to implement a function" - is this what you mean maybe)?
๐Ÿ‘† 1
s
๐Ÿ‘ 3
Here is another explanation about IO vs suspend, and how functional are translated in idiomatic Kotlin with Arrow
o
Thank you very much! That gist was really helpful for me, since it showed a more complete picture - no scary stuff hidden in the implementation. I really appreciate you guys, explaining things to people that are a bit slow like me. ๐Ÿค— Have a nice sunday evening and thx again!
Just to be shure: I guess the companion object is there, because you'd need it for @optics or @product?
s
Yes, the companion object is a habit ๐Ÿ˜… Itโ€™s not needed in this example.
Youโ€™re very welcome!
๐Ÿ˜€ 1
o
Would you mind to tell the trade-offs between implementing the DataLayer (overriding fetch and process) and instancing the DataModule class. In my eyes that both does the same, I don't see the pro & cons for this or that design.
s
Hey @Oliver Eisenbarth, Of course! It wasn't documented or explained in the gist, but perhaps I should make the gist a bit larger with a counterexample. This approach easily leads to having a god-like top module that holds all dependencies. Some people don't like to use such god-like objects all over their codebase, so this example shows how you can define separate modules for several layers of your architecture and provide them through the most top module.
o
Hi Simon, thanks a lot for replying so fast! I'm still struggling a bit to see the pros & cons, sorry. What exactly do you mean by "providing through most top module"? Implementing Datalayer and override the methods? Like this? ๐Ÿ‘‡
Copy code
fun mockDataLayer() : DataLayer = object : DataLayer { ... }
s
Hey @Oliver Eisenbarth, Sorry for the slow reply. I updated the gist with some more explanations: https://gist.github.com/nomisRev/1f91710ebec1709d4ce8059812482624 No, with the god class I meant that for very big programs your code for an end-point or a screen could look like this, or potentially even bigger. This is not a problem perse, and there are different code styles you could use for this.
Copy code
suspend fun <R> R.processUser(userId: Long): Unit
   where R : Persistence<User>,
         R : Repo<User>,
         R : Logger,
         R : Config,
         R : Processor<User> /*, R : ... */ {
  val user = userForId(userId)
  log("user for id: $user")
  val info = fetchInfoForUser(user)
  process(config, user, info)
}
So to satisfies this you need a larger composition of interfaces, which is often referred to as "god-class" or "god-dependency". This is not actually an issue for this style of programming because the function and the "god-class" are completely decoupled and completely unaware of each other. Meaning that it automatically is also 100% testable with whatever techniques you prefer.
o
Hi @simon.vergauwen, no problem - thx for replying! It's much clearer to me now. ๐Ÿ™ ๐Ÿ‘
Came back to this after reading a Scala articles about functional dependency injection with Reader and Kleisli and looking for those classes in Arrow: https://arrow-kt.io/docs/effects/io/ xD
r
Hi @Oliver Eisenbarth, Reader and Kleisli for IO in Arrow is just any suspend extension fun. We got rid of those data types as they provided no value and just extra overhead compared to ext funs where the contextual dependencies are simply the receiver. Reader:
(A) -> B
is
fun A.whatever(): B
Kleisli :
(A) -> F<B>
is
suspend fun A.whatever() : B
o
Hi Raul, thank you for explaining why you got rid of them. Maybe I should just stop comparing Scala to Arrow for a while. ๐Ÿ™‚
Played around with this some more and put a project on Github - just in case if someone want's to change s.t. here or there and see if it works w/o having to set things up: https://github.com/Alfhir/DI-Test