Error handling
An interaction can fail to achieve its intended purpose. Baker categorizes failures in technical failures and functional failures.
Technical failures are characterized by the possibility of being retried and eventually succeeding. Examples of technical failures include timeouts due to an unreliable network, temporary unavailability of an external system, and receiving an unexpected response from an external system. These failures are unexpected and are handled by throwing an exception from the interaction.
Functional failures cannot be resolved by retrying the interaction. Examples of functional failures include cases where there is insufficient stock to ship the order, or there is insufficient credit to perform a transfer. These failures are anticipated and considered as potential outcomes of the interaction. They are handled by returning an event from the interaction.
Failure strategies
Baker offers multiple mitigation strategies for technical failures.
Tip
The examples in this section all set the defaultFailureStrategy
on recipe
level. The same strategies are also
available for the failureStrategy
on interaction
level.
Block interaction
This is the default failure strategy. When an exception occurs the interaction is blocked. This option is suitable for non-idempotent interactions that cannot be retried.
package examples.java.recipes;
import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;
public class RecipeBlockInteraction {
public final static Recipe recipe = new Recipe("example")
.withDefaultFailureStrategy(
InteractionFailureStrategy.BlockInteraction()
);
}
package examples.kotlin.recipes
import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe
@ExperimentalDsl
object RecipeBlockInteraction {
val recipe = recipe("example") {
defaultFailureStrategy = blockInteraction()
}
}
package examples.scala.recipes
import com.ing.baker.recipe.common.InteractionFailureStrategy
import com.ing.baker.recipe.scaladsl.Recipe
object RecipeBlockInteraction {
val recipe: Recipe = Recipe("example")
.withDefaultFailureStrategy(
InteractionFailureStrategy.BlockInteraction()
)
}
Fire event
This option is the equivalent of a try-catch
in code. When an exception occurs an event is fired. Instead of failing,
the process continues.
package examples.java.recipes;
import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;
public class RecipeFireEvent {
public final static Recipe recipe = new Recipe("example")
.withDefaultFailureStrategy(
InteractionFailureStrategy.FireEvent("MyEvent")
);
}
package examples.kotlin.recipes
import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe
@ExperimentalDsl
object RecipeFireEvent {
val recipe = recipe("example") {
defaultFailureStrategy = fireEventAfterFailure("MyEvent")
}
}
package examples.scala.recipes
import com.ing.baker.recipe.common.InteractionFailureStrategy
import com.ing.baker.recipe.scaladsl.Recipe
object RecipeFireEvent {
val recipe: Recipe = Recipe("example")
.withDefaultFailureStrategy(
InteractionFailureStrategy.FireEventAfterFailure(Some("MyEvent"))
)
}
Retry with incremental back-off
Incremental back-off allows you to configure a retry mechanism that exponentially increases the time between each retry. You retry quickly at first, but slower over time. Retry with incremental back-off keeps retrying until a set deadline or the maximum amount of retries is reached.
Until deadline
package examples.java.recipes;
import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;
import java.time.Duration;
public class RecipeRetryWithBackOffUntilDeadline {
public final static Recipe recipe = new Recipe("example")
.withDefaultFailureStrategy(
new InteractionFailureStrategy.RetryWithIncrementalBackoffBuilder()
.withInitialDelay(Duration.ofMillis(100))
.withBackoffFactor(2.0)
.withMaxTimeBetweenRetries(Duration.ofSeconds(100))
.withDeadline(Duration.ofHours(24))
.build()
);
}
package examples.kotlin.recipes
import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@ExperimentalDsl
object RecipeRetryWithBackOffUntilDeadline {
val recipe = recipe("example") {
defaultFailureStrategy = retryWithIncrementalBackoff {
initialDelay = 100.milliseconds
backoffFactor = 2.0
maxTimeBetweenRetries = 10.seconds
until = deadline(24.hours)
}
}
}
package examples.scala.recipes
import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff
import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff.UntilDeadline
import com.ing.baker.recipe.scaladsl.Recipe
import scala.concurrent.duration.DurationInt
object RecipeRetryWithBackOffUntilDeadline {
val recipe: Recipe = Recipe("example")
.withDefaultFailureStrategy(
RetryWithIncrementalBackoff
.builder()
.withInitialDelay(100.milliseconds)
.withBackoffFactor(2.0)
.withMaxTimeBetweenRetries(Some(10.minutes))
.withUntil(Some(UntilDeadline(24.hours)))
.build()
)
}
Until maximum retries
package examples.java.recipes;
import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;
import java.time.Duration;
public class RecipeRetryWithBackOffUntilMaxRetries {
public final static Recipe recipe = new Recipe("example")
.withDefaultFailureStrategy(
new InteractionFailureStrategy.RetryWithIncrementalBackoffBuilder()
.withInitialDelay(Duration.ofMillis(100))
.withBackoffFactor(2.0)
.withMaxTimeBetweenRetries(Duration.ofSeconds(100))
.withMaximumRetries(200)
.build()
);
}
package examples.kotlin.recipes
import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@ExperimentalDsl
object RecipeRetryWithBackOffUntilMaxRetries {
val recipe = recipe("example") {
defaultFailureStrategy = retryWithIncrementalBackoff {
initialDelay = 100.milliseconds
backoffFactor = 2.0
maxTimeBetweenRetries = 10.seconds
until = maximumRetries(200)
}
}
}
package examples.scala.recipes
import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff
import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff.UntilMaximumRetries
import com.ing.baker.recipe.scaladsl.Recipe
import scala.concurrent.duration.DurationInt
object RecipeRetryWithBackOffUntilMaxRetries {
val recipe: Recipe = Recipe("example")
.withDefaultFailureStrategy(
RetryWithIncrementalBackoff
.builder()
.withInitialDelay(100.milliseconds)
.withBackoffFactor(2.0)
.withMaxTimeBetweenRetries(Some(10.minutes))
.withUntil(Some(UntilMaximumRetries(200)))
.build()
)
}
name | meaning |
---|---|
initialDelay |
The delay before retrying for the first time. |
backoffFactor |
The backoff factor for the delay, defaults to 2 |
maxTimeBetweenRetries |
The maximum time between retries. |
deadLine |
The total amount of time spend retrying. |
maximumRetries |
The maximum amount of retries. |
Our example results in a retry pattern off:
100 millis -> 200 millis -> 400 millis -> ... -> 100 seconds -> 100 seconds
.
Which can be visualized like this:
Note
Delays do not include the interaction execution time. If the first retry takes 5 seconds (and fails), the second retry will be triggered after:
(100 millis + 5 seconds + 200 millis) = 5.3 seconds
The deadline also does not consider interaction execution time. Keep this in mind when setting the deadline value.
Retry exhaustion
If an interaction keeps failing, the retry is exhausted and the interaction becomes blocked. If you don't want the interaction to block after exhausting all the retries, you can continue the process with a predefined event.
package examples.java.recipes;
import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;
import java.time.Duration;
public class RecipeRetryExhaustedEvent {
public final static Recipe recipe = new Recipe("example")
.withDefaultFailureStrategy(
new InteractionFailureStrategy.RetryWithIncrementalBackoffBuilder()
.withInitialDelay(Duration.ofMillis(100))
.withBackoffFactor(2.0)
.withMaxTimeBetweenRetries(Duration.ofSeconds(100))
.withDeadline(Duration.ofHours(24))
.withFireRetryExhaustedEvent("RetriesExhausted")
.build()
);
}
package examples.kotlin.recipes
import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@ExperimentalDsl
object RecipeRetryExhaustedEvent {
val recipe = recipe("example") {
defaultFailureStrategy = retryWithIncrementalBackoff {
initialDelay = 100.milliseconds
backoffFactor = 2.0
maxTimeBetweenRetries = 10.seconds
until = deadline(24.hours)
fireRetryExhaustedEvent = "RetriesExhausted"
}
}
}
package examples.scala.recipes
import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff
import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff.UntilDeadline
import com.ing.baker.recipe.scaladsl.Recipe
import scala.concurrent.duration.DurationInt
object RecipeRetryExhaustedEvent {
val recipe: Recipe = Recipe("example")
.withDefaultFailureStrategy(
RetryWithIncrementalBackoff
.builder()
.withInitialDelay(100.milliseconds)
.withBackoffFactor(2.0)
.withMaxTimeBetweenRetries(Some(10.minutes))
.withUntil(Some(UntilDeadline(24.hours)))
.withFireRetryExhaustedEvent(Some("RetriesExhausted"))
.build()
)
}
Manual intervention
Baker allows you to resolve blocked interactions, and to stop retrying interactions via manual intervention.
Force a retry
This method retries a given interaction a single time. If it succeeds, your process continues. If it fails, the interaction stays blocked.
package examples.java.application;
import com.ing.baker.runtime.javadsl.Baker;
public class ManualRetryInteraction {
public void retryExample(Baker baker, String recipeInstanceId) {
baker.retryInteraction(recipeInstanceId, "ShipOrder");
}
}
package examples.kotlin.application
import com.ing.baker.runtime.kotlindsl.Baker
suspend fun retryExample(baker: Baker, recipeInstanceId: String) {
baker.retryInteraction(recipeInstanceId, "ShipOrder")
}
package examples.scala.application
import com.ing.baker.runtime.scaladsl.Baker
class ManualRetryInteraction {
def retryExample(baker: Baker, recipeInstanceId: String): Unit = {
baker.retryInteraction(recipeInstanceId, "ShipOrder")
}
}
Resolve interaction
This method resolves a blocked interaction by firing an event.
package examples.java.application;
import com.ing.baker.runtime.javadsl.Baker;
import com.ing.baker.runtime.javadsl.EventInstance;
public class ManualResolveInteraction {
public void resolveExample(Baker baker, String recipeInstanceId) {
baker.resolveInteraction(
recipeInstanceId,
"ShipOrder",
EventInstance.from("ShippingFailed")
);
}
}
package examples.kotlin.application
import com.ing.baker.runtime.javadsl.EventInstance
import com.ing.baker.runtime.kotlindsl.Baker
suspend fun resolveExample(baker: Baker, recipeInstanceId: String) {
baker.resolveInteraction(
recipeInstanceId,
"ShipOrder",
EventInstance.from("ShippingFailed")
)
}
package examples.scala.application
import com.ing.baker.runtime.scaladsl.{Baker, EventInstance}
class ManualResolveInteraction {
def resolveExample(baker: Baker, recipeInstanceId: String): Unit = {
baker.resolveInteraction(
recipeInstanceId,
"ShipOrder",
EventInstance("ShippingFailed")
)
}
}
Stop retrying
This method halts the retry process of a failing interaction by blocking the interaction.
package examples.java.application;
import com.ing.baker.runtime.javadsl.Baker;
import com.ing.baker.runtime.javadsl.EventInstance;
public class ManualStopInteraction {
public void stopExample(Baker baker, String recipeInstanceId) {
baker.stopRetryingInteraction(recipeInstanceId, "ShipOrder");
}
}
package examples.kotlin.application
import com.ing.baker.runtime.kotlindsl.Baker
suspend fun stopExample(baker: Baker, recipeInstanceId: String) {
baker.stopRetryingInteraction(recipeInstanceId, "ShipOrder")
}
package examples.scala.application
import com.ing.baker.runtime.scaladsl.Baker
class ManualStopInteraction {
def retryExample(baker: Baker, recipeInstanceId: String): Unit = {
baker.stopRetryingInteraction(recipeInstanceId, "ShipOrder")
}
}