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. A blocked interaction will not execute again for that recipe instance until it is unblocked. Blocked interactions can be unblocked by calling the baker.retryInteraction or baker.resolveInteraction methods.
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 and Block
This option is the equivalent of a try-catch
in code. When an exception occurs an event is fired.
The interaction is still put in the blocked state. A blocked interaction will not execute again for that recipe instance until it is unblocked.
Blocked interactions can be unblocked by calling the baker.retryInteraction or baker.resolveInteraction methods.
package examples.java.recipes;
import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;
public class RecipeFireEventAndBlock {
public final static Recipe recipe = new Recipe("example")
.withDefaultFailureStrategy(
InteractionFailureStrategy.FireEventAndBlock("MyEvent")
);
}
package examples.kotlin.recipes
import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe
@ExperimentalDsl
object RecipeFireEventAndBlock {
val recipe = recipe("example") {
defaultFailureStrategy = fireEventAndBlock("MyEvent")
}
}
package examples.scala.recipes
import com.ing.baker.recipe.common.InteractionFailureStrategy
import com.ing.baker.recipe.scaladsl.Recipe
object RecipeFireEventAndBlock {
val recipe: Recipe = Recipe("example")
.withDefaultFailureStrategy(
InteractionFailureStrategy.FireEventAndBlock(Some("MyEvent"))
)
}
Fire event and Resolve
When an exception occurs an event is fired. This is seen as if the interaction returned this event. In this case the interaction is not put in the blocked state and can be executed again if the preconditions are met. Since the interaction is not put in the blocked state the baker.retryInteraction or baker.resolveInteraction methods cannot be called for the interaction.
package examples.java.recipes;
import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;
public class RecipeFireEventAndResolve {
public final static Recipe recipe = new Recipe("example")
.withDefaultFailureStrategy(
InteractionFailureStrategy.FireEventAndResolve("MyEvent")
);
}
package examples.kotlin.recipes
import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe
@ExperimentalDsl
object RecipeFireEventAndResolve {
val recipe = recipe("example") {
defaultFailureStrategy = fireEventAndResolve("MyEvent")
}
}
package examples.scala.recipes
import com.ing.baker.recipe.common.InteractionFailureStrategy
import com.ing.baker.recipe.scaladsl.Recipe
object RecipeFireEventAndResolve {
val recipe: Recipe = Recipe("example")
.withDefaultFailureStrategy(
InteractionFailureStrategy.FireEventAndResolve(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.
FireEventAndBlock and FireEventAndResolve
If an interaction keeps failing longer then the defined retry duration, the retry is exhausted and the interaction becomes blocked. If you want you can configure either the FireEventAndBlock or FireEventAndResolve as a followup retry strategy.
package examples.java.recipes;
import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;
import java.time.Duration;
public class RecipeRetryWithBackOfWithFireEventAndBlock {
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))
.withFireEventAndBlock("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 RecipeRetryWithBackOfWithFireEventAndBlock {
val recipe = recipe("example") {
defaultFailureStrategy = retryWithIncrementalBackoff {
initialDelay = 100.milliseconds
backoffFactor = 2.0
maxTimeBetweenRetries = 10.seconds
until = deadline(24.hours)
fireEventAndBlock = "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 RecipeRetryWithBackOfWithFireEventAndBlock {
val recipe: Recipe = Recipe("example")
.withDefaultFailureStrategy(
RetryWithIncrementalBackoff
.builder()
.withInitialDelay(100.milliseconds)
.withBackoffFactor(2.0)
.withMaxTimeBetweenRetries(Some(10.minutes))
.withUntil(Some(UntilDeadline(24.hours)))
.withFireEventAndBlock(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")
}
}