Can someone explain the advantages of dependency i...
# announcements
h
Can someone explain the advantages of dependency injection in kotlin as opposed to just using an object(/singleton)?
n
whether in kotlin or any other language one word: ability to unit test the class
☝️ 2
h
a unit test is simply a comprehensive test of the endpoints of a class using a constant state, right, so why can't that be performed on a kotlin
object
?
n
it’s not about the kotlin
object
it’s about users of it
you cannot test them independently
a
if the kotlin
object
itself is stateful you can't provide a constant state to things that consume it, you're at the mercy of its current internal state
☝️ 1
n
if you’re intested, i’ve written a piece https://blog.frankel.ch/basics-dependency-injection/
h
Oh, I suppose I understand. A DI class can be acquired stateless at any time. An object is stateful, and various unit tests run using it would make it very difficult to ensure it is in a constant state.
n
di means you can inject an object with the state you want and thus can test other objects that use it with a known state
a
DI doesn't solve the problem of making things testable, DI solves a problem that is often created by certain patterns of making things testable: configuring complex object graphs
n
di solve the problem of making classes being testable in isolation
a
no, interfaces solve that. 🙂 DI solves the problem of having to construct and configure too many interface-implementing objects along the way to getting a concrete instance of an object with a complex dependency graph.
Having that second problem is not a foregone conclusion when you're making things testable, but if you do have it, DI is a good solution to it.
n
no, interfaces solve that
no i write about it in my post i’m too lazy to rewrite it here... it you’ re too lazy to read, that’s fine too 😉
a
I read it 🙂
👍 1
n
then you understood the gist is: the problem lies within instantiation interfaces don’t solve that issue
you need to remove the instantiation from inside the class
there are ways to do that di is one of them
a factory based on environment variables/system properties/etc. is another one
a
it sounds like we agree then 🙂
n
then i don’t understand your point 😞
a
sorry, my phrasing was perhaps confusing
providing dependencies using interfaces helps make things testable, but in itself it is not, "dependency injection" in the sense of an injection framework that resolves dependency graphs for you to create object instances, which is what most people think of when they say, "DI."
n
good point in my post, i try to explain that di is not dependent on frameworks you can di yourself
a
at that point it's not really "injection," it's just, "providing arguments to functions" 🙂 - though it makes for a good teaser description for a blog post that people will find in search!
n
and about interfaces, you don’t need them for di (not saying interfaces are not useful) the important part is to create and configure the dependent object outside of the dependable object (not sure dependable is the correct word sorry, english is not my native language)
a
no worries, I'm also being sloppy with my language on holiday here as well 🙂
n
at that point it’s not really “injection,” it’s just, “providing arguments to functions”
imho it is with a nuance, it’s providing arguments to constructors
enjoy your holidays 🎉
a
thanks!
a constructor is just a special kind of function; especially in kotlin where you see patterns like factory functions named after the type name they return so that they look like a constructor invocation at a call site
n
yes, but a function is not a special kind of constructor
a
how would you consider a factory object?
n
what do you mean? for me, it’s another way to do inversion of control
a
drawing a parallel between a factory object and a factory function and that they're all facets of the same idea of bringing control out and away from an object implementation; constructors aren't special in particular for inverting control
n
constructors aren’t special in particular for inverting control
agreed
but so far, imho, they have proven to be the easiest way to do that
but this is getting far from kotlin at this point
a
to bring it back to Kotlin then, consider DSL functions and builders such as
flow {}
and similar. The receiver object (which might be an interface) is provided to a block of code supplied by the caller, and that block of code is supplied to the function. If that block is a suspending lambda, it allows the caller to supply a complex policy for behavior over time with a single block of sequential code. DI frameworks and constructor injection may result in a much harder to follow solution in such cases, and both can achieve testability.
Kotlin opens a lot of exciting doors for inversion of control! 🙂
n
it would help if you provided a snippet 😅
a
I'll see if I can extract one from something later
👍 1
here's a rough snippet from a hobby project:
Copy code
mqttClient(
    mqttDialer(hostname),
    clientId = clientId,
    keepalive = 300,
    will = Message(statusTopic, "disconnected", retain = true)
) {
    send(statusTopic, "connected", retain = true, qos = Qos.AtLeastOnce)
Managing a connection to an MQTT server is wrapped up into a single suspend function call. The "dialer" function called in the first parameter returns a factory that sets up a connection and returns a socket-like object with a pair of okio sink/source objects as properties. Easy to fake a server with for testing using plain okio
Buffer
objects. The trailing lambda parameter is a suspending function with a receiver interface type that implements
CoroutineScope
and also offers methods to send mqtt messages (shown) and to get `Flow`s for mqtt topic subscriptions (not shown). When the body block completes and any child coroutine jobs join, the connection is torn down and the function returns. Unrecoverable errors or cancellation throw. Any part of it can be broken down pretty easily for testing and testability, and the dependencies are either provided as regular parameters or they're captured as part of the lambda body from the surrounding lexical scope.
n
interesting! di for functions 🙂 now i understand your point about constructors
a
🙂 This is also why I disagree with the use of the term, "DI" to describe any sort of providing a nontrivial policy object as a parameter to a function and prefer to reserve the term for describing the use of DI frameworks - once you explore the space a bit, if you apply it to any parameter passing then it becomes so widely scoped as to be kind of meaningless. Plus, given the widespread knowledge across the web about DI how-to, overgeneralizing it tends to confuse newcomers. "Manual DI" is a nice, snappy term to demystify the inner workings and concepts of DI frameworks, but without the, "injection" part being performed by such a system, it's hard to call it DI. "Inversion of control"/IoC seems sufficient as a term to describe the more general pattern at play.
And I've come to like the pattern from the snippet above a lot. As I've used more function-forward designs like that, I've found my code becomes easier to read both in implementation and in use, easier to test without building complex test harnesses or configurations, and it has fewer bugs the first time
I think I ended up bringing some of this thinking to kotlin after writing a bunch of go, you can see a lot of similarities especially in the way coroutines evolved, e.g. the
go
statement and kotlin's
launch
, channels, etc. When you have tools like
suspend
to help represent scoped behavior over time, you don't need a graph full of objects and callbacks and strategies and factories in the same way
n
I disagree with the use of the term, “DI” to describe any sort of providing a nontrivial policy object as a parameter to a function
it’s hard to change the semantics of a agreed-upon term i think you should come up with your own
if we both use the same word with different semantics it’s hard to understand each other
a
I feel the same way about your usage of it 😄
but you're not the only one to use it that way and by now I've resigned myself to grumble a bit like the above and then agree to disagree
but since there is disagreement in the community, documentation, and search results across the web we both need to clarify which definition we're using for our audiences
n
but you’re not the only one to use it that way
that’s my point it’s agreed upon by many easier if you come up with a term that describes your meaning
a
the definition I use is also agreed upon by many 🙂
😮 1
n
would be interested in more info on that