SDK Error Handling
Each of our SDKs has its own page in this documentation; you can navigate to them by clicking Develop
->SDKs
in the left navigation menu. On each of these pages, you'll find a link for "Taking your code to prod" which provides best practices for production-ready code in that language.
Momento SDKs are designed to behave well in production environments using the pre-built configuration objects provided, so most users can easily move from proof of concept to production with no changes in operating practices. This page shares some general notes on error handling in Momento SDKs, and why the default behaviors we've chosen are recommended for most customers.
Surfacing errors
Errors that occur in calls to CacheClient
methods (e.g. Get, Set, Increment) are surfaced to developers as part of the return values of the calls, as opposed to throwing exceptions. This makes errors more visible when you're writing your code, and allows your IDE to be more helpful in ensuring you've handled the errors you care about. For more on our philosophy about this, see our blog post on why Exceptions are bugs, and send us any feedback you have!
This also helps to ensure your application doesn't crash at runtime. Momento is a cache, so applications usually have an authoritative data source they from which they retrieve data if the cache is unavailable. Therefore, Momento SDKs are designed to avoid throwing exceptions so your app won't crash if you forget to add a try/catch block.
Instead, Momento response objects extend from a parent class, have child types such as Hit,
Miss,
and Error,
and are designed to be accessed via pattern matching. (In some languages, this concept is called "sealed classes"; in others, "algebraic data types". It is a flavor of polymorphism.) Your code checks whether the response is a Hit,
a Miss,
or an Error
, and then branches accordingly. Using this approach, you get a type-safe response object in the case of a cache hit, with value
properties that you can be assured at compile-time are not null.
If the cache read results in a Miss
or an Error,
you'll also get a type-safe object that you can use to get more information about what happened (with properties such as errorCode
that aren't present on a Hit
object).
Here's an example of how to do the pattern matching on a Hit
/Miss
/Error
response:
- JavaScript
- Java
- Kotlin
- Elixir
const result = await cacheClient.get(cacheName, 'test-key');
switch (result.type) {
case CacheGetResponse.Hit:
console.log(`Retrieved value for key 'test-key': ${result.valueString()}`);
break;
case CacheGetResponse.Miss:
console.log(`Key 'test-key' was not found in cache '${cacheName}'`);
break;
case CacheGetResponse.Error:
throw new Error(
`An error occurred while attempting to get key 'test-key' from cache '${cacheName}': ${result.errorCode()}: ${result.toString()}`
);
}
final GetResponse response = cacheClient.get("test-cache", "test-key").join();
if (response instanceof GetResponse.Hit hit) {
System.out.println("Retrieved value for key 'test-key': " + hit.valueString());
} else if (response instanceof GetResponse.Miss) {
System.out.println("Key 'test-key' was not found in cache 'test-cache'");
} else if (response instanceof GetResponse.Error error) {
throw new RuntimeException(
"An error occurred while attempting to get key 'test-key' from cache 'test-cache': "
+ error.getErrorCode(),
error);
}
when (val response = cacheClient.get("test-cache", "test-key")) {
is GetResponse.Hit -> println("Retrieved value for key 'test-key': ${response.value}")
is GetResponse.Miss -> println("Key 'test-key' was not found in cache 'test-cache'")
is GetResponse.Error -> throw RuntimeException(
"An error occurred while attempting to get key 'test-key' from cache 'test-cache': ${response.errorCode}",
response
)
}
case Momento.CacheClient.get(client, "test-cache", "test-key") do
{:ok, hit} ->
IO.puts("Retrieved value for key 'test-key': #{hit.value}")
:miss ->
IO.puts("Key 'test-key' was not found in cache 'test-cache'")
{:error, error} ->
IO.puts(
"An error occurred while attempting to get key 'test-key' from cache 'test-cache': #{error.error_code}"
)
raise error
end
Some APIs, such as write APIs (e.g. Set, Delete) only have Success
and Error
responses (as opposed to Hit
/Miss
). Here's an example of handling one of these responses:
- JavaScript
- Java
- Kotlin
- Elixir
const result = await cacheClient.set(cacheName, 'test-key', 'test-value');
switch (result.type) {
case CacheSetResponse.Success:
console.log("Key 'test-key' stored successfully");
break;
case CacheSetResponse.Error:
throw new Error(
`An error occurred while attempting to store key 'test-key' in cache '${cacheName}': ${result.errorCode()}: ${result.toString()}`
);
}
final SetResponse response = cacheClient.set("test-cache", "test-key", "test-value").join();
if (response instanceof SetResponse.Success) {
System.out.println("Key 'test-key' stored successfully");
} else if (response instanceof SetResponse.Error error) {
throw new RuntimeException(
"An error occurred while attempting to store key 'test-key' in cache 'test-cache': "
+ error.getErrorCode(),
error);
}
when (val response = cacheClient.set("test-cache", "test-key", "test-value")) {
is SetResponse.Success -> println("Key 'test-key' stored successfully")
is SetResponse.Error -> throw RuntimeException(
"An error occurred while attempting to store key 'test-key' in cache 'test-cache': ${response.errorCode}",
response
)
}
case Momento.CacheClient.set(client, "test-cache", "test-key", "test-value") do
{:ok, _} ->
IO.puts("Key 'test-key' stored successfully")
{:error, error} ->
IO.puts(
"An error occurred while attempting to store key 'test-key' in cache 'test-cache': #{error.error_code}"
)
raise error
end
In all cases, your IDE will be able to give you hints as to which types of responses are available for a given API, and what properties/methods are available on each of the response types.
For an example of how the error response works and how to use it, watch this video:
Retrying calls that returned an error
The general pattern of behavior you can expect from Momento SDKs when it comes to retrying a call after an error response is as follows:
Momento SDKs do not retry throttled requests (limits exceeded). For other errors, the SDK will not retry if the operation requested is not idempotent. For example, if you are incrementing a counter and receive an error response, the SDK will not retry on your behalf (because this could result in over-counting). In the case of non-idempotent operations, it is safer to let the developer choose whether to retry.