Best practices for Kotlin coroutine/async library functions

I’ve started a spike with Kotlin coroutines and am quite loving it so far, a lot of things that we’re doing now with tools like CompletableFuture or RxJava are becoming a lot simpler. However, I’m trying to understand what’s seen as best practice when it comes to exposing non blocking library functions.

Say I have an interface that represents a client for making non blocking network calls. I can see a number of ways of modelling this:

Modelling the call as a normal function returning a Deferred

fun makeCall(): Deferred<Response>

This makes it clear that the operation is non blocking, but presumably now my implementation will need to use its own scope/dispatcher - either hard coded or injected into the implementation - and thus the deferred would not be automatically cancelled if the parent task was.

Modelling the call as a suspend function

suspend fun makeCall(): Response

This makes it clear that the method should be called from another suspend function (so will have an implicit scope), but in this model, how does one launch child tasks? https://youtrack.jetbrains.com/issue/KT-17609 is talking about giving access to the current scope which would be used to launch other tasks, but it’s not clear how one is meant to do this. Is the right approach to essentially always use withContext() inside the implementation? Or does it make sense for the implementation to be a CoroutineScope of its own?

Alternatively, one could model it as an extension method on a scope

suspend fun CoroutineScope.makeCall(): Response

The implementation will now have implicit access to a scope so can use launch etc, although actually using this would be “interesting”.

Is there a resource describing best practices for this type of thing?

Cheers,
Kristian

tldr;

https://twitter.com/relizarov/status/1088372857766326272

This makes it clear that the operation is non blocking

makeCall is not suspending, and hopefully not blocking, consider the name makeCallAsyncfor a deferred task.

Is the right approach to essentially always use withContext() inside the implementation?

You can create a coroutine scope with coroutineScope https://kotlinlang.org/docs/reference/coroutines/basics.html#scope-builder

uspend fun CoroutineScope.makeCall(): Response = coroutineScope { TODO() }

Is there a resource describing best practices for this type of thing?

I hope this help, but it is an introduction

https://medium.com/@elizarov/structured-concurrency-722d765aa952

1 Like