Coroutines

Now, let's rework our example with coroutines:

import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking

class
CoroutineUserService(private val userClient: UserClient,
private val factClient: FactClient,
private val userRepository: UserRepository,
private val factRepository: FactRepository) : UserService {
override fun getFact(id: UserId): Fact = runBlocking {
val user = async { userRepository.getUserById(id) }.await()
if (user == null) {
val userFromService = async { userClient.getUser(id) }.await()
launch { userRepository.insertUser(userFromService) }
getFact(userFromService)
} else {
async { factRepository.getFactByUserId(id) ?: getFact(user) }.await()
}
}

private suspend fun getFact(user: User):Fact {
val fact: Deferred<Fact> = async { factClient.getFact(user) }
launch { factRepository.insertFact(fact.await()) }
return fact.await()
}
}

Our code is more straightforward than our Future example, getting very close to our synchronous code. We covered runBlocking and launch in the previous section, but a new coroutine builder is introduced here, async.

The async coroutine builder takes a block of code and executes it asynchronously, returning Deferred<T>. A Deferred is a Future with an await method that blocks the coroutine until completion but not the thread; Deferred also extends from Job so inherits all its methods, such as join.

Coroutine code feels natural yet it is explicit when we are using asynchronous code, but due to the low cost on resources, we can use as many coroutines as we want in our code; for example, CoroutineUserService uses less than half of threads and memory than any other implementation.

Now that we have all implementations, we can compare code complexity and resource consumption:

Code complexity

Resource consumption

Synchronous

There is very low code complexity.

The resource consumption is very low with slow performance.

Callbacks

Very high adapters are needed; duplication is expected; nested callbacks are hard to read; and there are various hacks.

The resource consumption is high. It could improve using a shared Executor, but it will add more code complexity.

Futures

Code complexity is medium. Executors and get() are noisy but it is still readable.

Resource consumption is high, but it can be fine-tuned using different Executor implementations and sharing executors but this adds code complexity.

Promises

Code complexity is medium using promise style (then, success). Using a futures style (get), it can be as slick as coroutines without affecting performance.

Resource consumption is very high, with top performance, but it can be fine-tuned without altering the code.

Coroutines

Code complexity is low; it's the same size as synchronous style with explicit blocks for asynchronous operations.

Resource consumption is low, with top performance out of the box.

 

Overall, coroutines are a clear winner, with Kovenant promises coming in a close second.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.70.185