Kotlin Either: How To Avoid Exceptions Correctly 👋 » The Bored Dev

Kotlin Either: How To Avoid Exceptions Correctly 👋

Spread the love

In this article we’re going to go through one of the most common problems in exception handling in any JVM-language and what alternative can we use to achieve a clearer, cleaner and more maintainable code in terms of error handling. We will see how can we introduce Kotlin Either to achieve that.

If you would like to have a better background in Kotlin before reading this article, you can check our article “An intelligent bet in Kotlin“.

Introduction

In these recent few decades where we have seen a boom in the programming sector, it became a widespread habit to bubble up exceptions in our code and then do the exception handling at some hidden point in our code, a place very difficult to find for any developer that didn’t know the code well in most of the cases. This is the case of Spring’s exception handlers for example.

Unless you know of that specific class, you won’t find where the logic is being applied by just following the normal flow of the code. Keeping track of Exceptions and being able to follow the code becomes more difficult.

This is one of the problems, but also it can become messy and very hard to maintain and debug problems when we encounter error edge cases.

So what are the actual problems and why do we need to do something about it?

Why the current situation is not ideal

When we perform an operation, for instance a HTTP call, if things go well we can have a perfectly clean call and get our object back but what happens if our call fails? We have several options, let’s see how they would look like and their implications:

Throwing an exception

One of the things we could do is to check the response and throw an exception if it hasn’t been successful. For instance:

fun findEmployeeByName(name: String): Employee {
    val request = Request(Method.GET, "http://myservice.org/employees").query("name", name)
    val response: Response = client(request)
    if (response.status == Status.OK) {
        return Employee(response.bodyString())
    }
    throw RuntimeException("Could not find employee $name!")
}

In this example we’re returning our Employee object if our call has been successful, in the case of a failure we just throw an exception. This way of handling errors is ugly and messy as there’s no way to know for certain who and where is going to be catching that exception and do something about it. In many projects exceptions are bubbled up until a “magical” component handles exceptions transparently in a way which is quite hard for developers who are unfamiliar with the code to guess.

One of the main problems on solely relying on Exceptions and bubble them up from every place in our code is that it becomes very hard to understand where an exception comes from. It’s also a suboptimal way of communicating that an error is happening in a section of our code.

So we are not particularly happy with this option, let’s see what other options we have.

Return Response objects

Another option we have is to return the Response object provided by the HTTP library we use. For example:

fun findEmployeeByName(name: String): Response {
    val request = Request(Method.GET, "http://myservice.org/employees").query("name", name)
    return client(request)
}

This approach gives us the advantage of allowing any client using the service a full transparency about how the call went and the content we received in the response; however we have two main issues now:

  • Our code will be tightly coupled to the use of a certain HTTP library.
  • Every client would have to parse the response.

On top of that, let’s say that we’ve found a high security risk issue in our existing HTTP client and they won’t be releasing a fix in the next few weeks. We must switch to a new HTTP library but having their objects spread across all of our code made it much more difficult and cumbersome. This is just one of the many reasons why this approach wouldn’t be a good idea for us; we have to keep looking at other options then.

Custom response wrapper

One more option we have is to build a custom object around our responses to have the ability of getting a response easily and at the same time being able to check and handle response errors.

fun findEmployeeByName(name: String): EmployeeResponse {
    val request = Request(Method.GET, "http://myservice.org/employees").query("name", name)
    val response = client(request)
    return EmployeeResponse(Employee(response.bodyString()), response)
}

data class EmployeeResponse(
    val result: Employee?,
    val response: Response
)

Although this approach improves a few things, we still have some of the problems we’ve seen before apart from the extra work of implementing a custom solution; this solution is not ideal either.

So what can we do then? How can we achieve an easy and readable manner to handle responses?

Arrow Either to the rescue

The answer is using Either. Either is part of the Arrow library, a library that provides some nice “enhancements” to Kotlin to be used in our projects. Either is basically a wrapper object to hold a result of a computation which allows stopping computation as soon as the first error appears.

Although I have to admit that using Arrow library has a long learning curve and I’m not particularly happy with their documentation, the library provides a few concepts that are quite useful to achieve a more readable and maintainable code in Kotlin.

Kotlin Either - Arrow
Source: https://arrow-kt.io/

To make use of Arrow library you just have to import the dependency in the dependencies section of your Gradle project:

implementation("io.arrow-kt:arrow-core:1.0.0")

The way it works is that instead of returning a type as a result of executing one of our methods, we’ll be returning an Either object. This object will always contain either a result when things completed successfully or a “problem” when something didn’t go well and the request could not be completed.

If we try to apply this to our previous example we would have to define first of all what a “problem” is in our case:

interface Problem

data class HttpFailureProblem(val message: String): Problem

Once we have that our method would look like this:

fun findEmployeeByName(name: String): Either<Problem, Employee> {
    val request = Request(Method.GET, "http://myservice.org/employees").query("name", name)
    val response = client(request)
    return when (response.status) {
        Status.OK -> Employee(response.bodyString()).right()
        else -> HttpFailureProblem("Http call failed with status ${response.status} due to ${response.bodyString()}").left()
    }
}

As you can clearly see, this approach is much more cleaner and not only that, it’ll allow an easy and neat error handling by the clients using our code.

Probably it’ll help if you think about it as a pipe with two channels: Left and Right. We always use the Left channel to communicate errors and the Right channel to communicate results from successful computations. Kotlin extensions allows the provision of left() and right() methods in every object to be able to easily respond converting to an Either object, this is why Kotlin extensions are so powerful.

So now that clients receive an Either, what can they actually do with it? Let’s see an example by creating a fake “PayrollService” which uses our EmployeeService:

class PayrollService(private val employeeService: EmployeeService) {
    private val logger = LoggerFactory.getLogger(PayrollService::class.java)
    
    fun generatePayrollFor(employeeName: String): Either<Problem, Payroll> {
        return employeeService.findEmployeeByName(employeeName)
            .fold(
                { httpFailureProblem ->
                    logger.info("Could not fetch employee: ${httpFailureProblem.message()}")
                    httpFailureProblem.left()
                },
                { employee ->
                    buildPayrollForEmployee(employee)
                }
            )
    }

    data class Payroll(private val id: String,
                       private val employeeId: String,
                       private val salary: Float,
                       private val date: Date)
    
    private fun buildPayrollForEmployee(employee: Employee): Either<Problem, Payroll> {
        return Payroll(UUID.randomUUID().toString(), employee.id, employee.salary, Date()).right()
    }
}

As you’ll see in the example above, we make use of fold method to handle the response. This allows us a neat and clean handling of EmployeeService’s response and clearly see what are we going to do in both cases: if things go as expected and if things go wrong.

In this case we propagate the Problem to any client using PayrollService after logging the error, but we could also just transparently propagate the Problem without a dedicated handler for the error scenario. How can we do that? We can make use of “flatMap” method, let’s see how to use it:

fun generatePayrollFor(employeeName: String): Either<Problem, Payroll> {
    return employeeService.findEmployeeByName(employeeName)
        .flatMap { employee ->  
            buildPayrollForEmployee(employee)
        }
}

As you can see, this is even more concise and clean! There’s no need for Exceptions and ugly try catch blocks!. Arrow library gives us a lot of possibilities; there’s also a “map” method that we could use but the response we’d get would be something like:

Either<Problem, Either<Problem, Payroll>>

Why is that? That’s because we have two nested calls using Either in our PayrollService: EmployeeService.findByEmployeeName and the private method buildPayrollForEmployee in PayrollService. When we map the Either responses they will get chained, as we could get a failure for any of them. Nested calls is one of the complications in using Either, just to simplify things that is the reason why we use “flatMap” in our example.

If you are interested in knowing about Kotlin in more detail, we recommend the following books for your own reading:

Kotlin Either - Bring Peace
Kotlin Either – Photo by Fransiskus Filbert Mangundap on Unsplash

Conclusion

We have seen a completely different way of handling “problems” in our source code, this is provided by Arrow library in this case. Arrow library can be complex to understand and making the change of mindset to the new concepts it brings can take some time; in this article we have tried to transmit to you a very basic understanding of the concept of Either but there’s much more to know about it and things can get much more complicated when dealing with different data structures using Either.

In future articles we’ll go through some more complex examples to fill a gap that has been left at the moment; I have the feeling that Arrow library documentation is not enough and finding examples on the Internet is quite difficult. That’s the main reason why I’d like to introduce to you guys more of these concepts to make your daily work with Kotlin much easier.

That’s it for now! We really hope you’ve enjoyed this article and hopefully you’ve learned something useful today! Please subscribe if you’re interested in knowing more from us in the future.

Additionally, if you want to support theboreddev.com to keep their good work online please donate in the link on the right hand side of this page; we’d really appreciate it.

Looking forward to seeing you soon!

One comment

Leave a Reply