Yet another short post using Quarkus. Quarkus is a full-stack, Kubernetes-native Java framework, but for this short post I’m using Kotlin for a change. The goal of this post is to show you how easy it is to use the circuit-breaker pattern to build fault tolerant services. For this we use the smallrye-fault-tolerance library, an implementation of the Eclipse MicroProfile Fault Tolerance.

Prerequisites

To be able to follow this how-to you’ll need the following:

  • An IDE (like IntelliJ IDEA)

  • JDK 11+

  • Maven 3.8.1+

  • Able to run gradle-7.3 (using the gradle wrapper)

Bootstrap your Quarkus app

So let’s kick-start our nice helloworld Kotlin based Quarkus application. Open a command-prompt or your terminal application and bootstrap it:

mvn io.quarkus.platform:quarkus-maven-plugin:2.7.5.Final:create  \
  -DprojectGroupId=JDriven \
  -DprojectArtifactId=helloWorld \
  -DbuildTool=gradle \
  -DprojectVersion=0.0.1 \
  -DclassName="com.jdriven.hello" \
  -Dextensions="kotlin,smallrye-fault-tolerance"

Now import the project in your favorite editor, and you should be good to go! == Dependencies Open the build.gradle file, and you notice that all the needed dependencies are there, since we told the Quarkus bootstrap command to add them for us:

    ...
    implementation 'io.quarkus:quarkus-smallrye-fault-tolerance'
    implementation 'io.quarkus:quarkus-kotlin'
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
    ...

Code - Retry → Fallback example

Because our example is small, we are able to remove all the example code from the project, since we’re not using it.

Then add a package where we’re going to put our services, for example com.jdriven.services.

In this folder we create our 'CanFailService' class. Let’s pretend this service dependents another external service. The service is not stable and fails a lot for any reason.

package com.jdriven.services

import javax.enterprise.context.ApplicationScoped

@ApplicationScoped
class CanFailService {

    // Do something that will occasionally fail.
    // e.g. Connect to sftp and write something over a very bad internet connection.
    fun unstable(i : Int) : Int = i

}

Next, we’ll create an executor service. This service wraps around the CanFailService, as it tries to execute the unstable method. When the unstable action fails a couple of times, it will use a fallback route.

Now lets try to call it 5 times. When it keeps on failing, we fall back to a stable path. The stable function needs to have the same signature as the unstable function. The Retry and Fallback are done by just adding the annotations provided by the faulttolerance package.

package com.jdriven.services

import org.eclipse.microprofile.faulttolerance.Fallback
import org.eclipse.microprofile.faulttolerance.Retry
import javax.enterprise.context.ApplicationScoped
import javax.inject.Inject

@ApplicationScoped
class ExecuteService {

    @Inject
    lateinit var canFailService: CanFailService

    @Retry(maxRetries = 4)
    @Fallback(fallbackMethod = "stable")
    fun executeMe(i : Int) : Int = canFailService.unstable(i)

    private fun stable(i: Int) : Int = 10

}

See the Retry → Fallback functionality in action

So let’s see this thing in action. To make sure it’s working as expected, we will create a simple test.

First add Mockito to our list of dependencies.

    ...
    testImplementation 'io.quarkus:quarkus-junit5'
    testImplementation 'io.rest-assured:rest-assured'
    testImplementation "io.quarkus:quarkus-junit5-mockito"

Refresh your gradle dependencies and add the test.

Have a look at the unhappy path. Here we see that our (mocked) service fails, so after the first time and the four retries, it will call the stable function that returns 10.

Now, look at the happy path. The service does not fail, so the service is called one time, and giving back the expected output.

package com.jdriven.services

import io.quarkus.test.junit.QuarkusTest
import io.quarkus.test.junit.mockito.InjectMock
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import javax.inject.Inject

@QuarkusTest
class ExecuteServiceTest {

    @InjectMock
    lateinit var canFailService: CanFailService

    @Inject
    lateinit var executeService: ExecuteService

    @Test
    fun testUnhappyRetry() {

        Mockito.doThrow(NullPointerException()).`when`(canFailService)?.unstable(1)
        val fallbackValue = executeService.executeMe(1)

        // Expecting to retry, so the unstable function should have been called 5 times:
        Mockito.verify(canFailService, Mockito.times(5)).unstable(1)

        // Should give fallback value 10:
        Assertions.assertEquals(10, fallbackValue)
    }

    @Test
    fun testHappyNoRetry() {

        Mockito.`when`(canFailService.unstable(2)).thenReturn(2)
        val happyValue = executeService.executeMe(2)

        // Expecting no retry needed, so the unstable function should have been called 1 times:
        Mockito.verify(canFailService, Mockito.times(1)).unstable(2)

        // Should give value 2:
        Assertions.assertEquals(2, happyValue)
    }
}

Code - Circuit breaker

In the next example we’re going to show you how easy it is to use the circuit breaker pattern. Please note that this blogpost does not have the intention to explain the circuit breaker pattern, there is already enough information on the topic.

In short, it is a wrapper around a function that monitors it for failures. Once the failures reach a certain threshold, it wil break the circuit (like a light switch). All further calls will return an error without calling the actual failing function again.

In this example we’ll create a not so stable ping service:

package com.jdriven.services

import org.eclipse.microprofile.faulttolerance.CircuitBreaker
import java.util.concurrent.atomic.AtomicInteger
import javax.enterprise.context.ApplicationScoped

@ApplicationScoped
class PingService {

    val pingCounter = AtomicInteger(0)

    @CircuitBreaker(requestVolumeThreshold = 4)
    fun ping(success: Boolean): String {

        pingCounter.incrementAndGet()
        if (success) {

            return "ok"
        }

        throw IllegalStateException()
    }
}

Now in this example, the circuit breaker should open after it failed 4 times. Bear in mind that the example only has a requestVolumeThreshold option now, that’s on purpose to keep the example as simple as possible.

See the circuit breaker in action

Again, to demonstrate this, we just add a test:

package com.jdriven.services

import io.quarkus.test.junit.QuarkusTest
import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.fail
import org.junit.jupiter.api.Test
import javax.inject.Inject

@QuarkusTest
class CircuitBreaker {

    @Inject
    lateinit var pingService: PingService

    @Test
    fun testCircuitBreakerOpens() {

        pingService.ping(true)

        for (i in 1 until 4) {
            try {
                pingService.ping(false)
                fail()
            } catch (expected: IllegalStateException) {

                println(expected.message)
            }
        }

        // Circuit should be open
        try {
            pingService.ping(true)
            fail()
        } catch (expected: CircuitBreakerOpenException) {
            println(expected.message)
        }

        assertEquals(4, pingService.pingCounter.get())
    }
}

As you hopefully agree, it is rather easy to implement the quarkus-smallrye-fault-tolerance extension. Even though some might argue that it is not needed anymore when you’re using a service mesh like Istio, I’m pretty sure not everyone has that in place. Also, this is so easy to implement, I would challenge you to use this in your other projects as well.

shadow-left