Hi everyone, I just publish a post in medium about...
# feed
m
Hi everyone, I just publish a post in medium about some idea (maybe crazy... maybe not) and I'll be grateful if you can give me feedback. Thanks you, https://engineering.etermax.com/kotlin-receivers-to-limit-the-use-of-a-method-db267f17b100
t
I don't quite understand what you are trying to achieve. Is this not a case for the decorator pattern (or AOP if you really want to)? With both options in the article it is not guaranteed that the function doSomething is only executed by the SomeService. It just means an instance needs to be passed. Thus whether otherThing runs or not is still up to the caller. The only solution I can think of is never providing an instance of ServiceImpl to the outside world and just use a decorator
m
Yes, but I want to have a jar with the interface of the service and adding different implementations via other jars that implements the ServiceImpl* interface. This way anyone can add a new implementations. And I don't understand when you say "It just means an instance needs to be passed", can you give me an example?
t
Copy code
val a = SomeImplementationOfServiceImpl2()
val b = SomeService(a)
// what I assume you want -> calls otherThing
b.something("test")
// what also works -> does not call otherThing
a.doSomething("test")(b)
Thinking about it more it definitely is an interesting approach to force users to use the SomeService class. However, I think I would still prefer just documenting it.
m
Ok, thank you! I didn't know that I can execute the function passing
SameService
instance as argument, I assumed that kotlin will force the calling as the first example. This is because the java interop? Or I'm missing something about the kotlin receivers mechanism?
e
if you make
doSomething
an extension function of
SomeService
it can only be called in instances of the
SomeService
.
Copy code
interface ServiceImpl {
    fun SomeService.doSomething(param: String): Int
}
You can still do:
Copy code
val a = ServiceImpl()
val b = SomeService(a)
with(a) {
    b.doSomething(test)
}
But is a lot harder the end user will end up doing this by accident
m
With your idea I can create another option like this:
Copy code
class SomeService(private val impl: ServiceImpl4) {
    fun something(param: String): Int {
        otherThing(param)
        with(impl) {
            return this@SomeService.doSomething(param)
        }
    }

    private fun otherThing(param: String): Unit = TODO()
}

interface ServiceImpl4 {
    fun SomeService.doSomething(param: String): Int
}
and the end user wouldn't notice. Thanks @Eric Martori, @Timmy what do you think about this option?
t
I don't know. Without context its hard to recommend it simply because it adds this unexpected parameter (the receiver) only, I assume, to guide the user towards the decorator class. The key question I think is how likely is it that the user will use the interface implementation directly. This depends on documentation, how often the implementation needs to be wrapped, what the functions actually do (something and doSomething are quite similar names, but different), what goes wrong when it is used directly (explodes with an error vs silently fails to do something). If the answer is the user is likely to do it wrong and won't notice, then sure, this solution is maybe acceptable. Otherwise just document it. I was trying to think of other examples where this mandatory decorator is used, but I only came up with JDBC where the drivers are loaded via reflection from class names.
m
I was doing a client for trying different event technologies (ActiveMQ, SNS+SQS of AWS, Kafka, etc.) to decide which one to choose. The client also sends some metrics of the events sents and received, in order to have an accurate measure of event processing. This was the context where this comes up because the interface of the client and the implementations are the same (publish an event a process it). After that it become more a theoretical example of what can I do with kotlin than a real issue. 😁.