‘Errors’ will always be an issue when dealing with an external saving service. This is unavoidable.
The easiest way to deal with these errors is to handle them in a new API layer made specifically for the purpose of handling whatever data it will handle.
- The suggestion in the original post suggests a “per-player” API. I think if this is done it should be called something like
PlayerDataStore
in order to clearly and distinctly separate it from general-purpose DataStore methods. This prevents confusion and shows a clear purpose.
Regardless of if ROBLOX makes a player-focused DataStore API, the general-purpose DataStore API needs improvements. Us developers need to know and understand what errors we might receive. Right now, it’s not clear. In order to effectively and easily make the API abstractions we need to properly manage our data, we need that “overglorified pcall”.
We need to know all of the failures we can receive. We need set codes (Enums) for failures, not unclear, undocumented strings.
This provides us with clear, set documentation and expected behavior for all possible results of a DataStore call.
Extra note: If we had Enums for datastore result cases, one could be CachedResponse
to make it clear when a GetAsync
returns cached results.
Reasoning
It is my understanding that errors are meant for *unexpected behavior*:
- Strings make for bad programmatic error indicators. Strings do not have to be documented. Strings are not always consistent. Dynamic strings are more difficult to check against.
- Given the previous statement, Lua does not lend itself well to the try-catch method of programmatic error handling, and following that does not lend itself to using errors for programmatic handling of expected failure states. Lua does not have well documented, built-in programmatic error cases and error case checking, and neither does ROBLOX. Other languages have documented
Exception
classes (and similar) that can be used to programmatically handle (and document) errors.
- This means that if something is expected behavior with proper use, it should not be an error. If it is an error then it will be more difficult than necessary to programmatically check its state/result. It is easier to use values provided in an API of some sort than to parse error codes, and using errors rather than failure codes does not separate expected failures from improper use. As previously mentioned, DataStores failing during proper use is unavoidable, so DataStore failures are expected behavior.
-
pcall
does not provide “error cases”, and the errors are not “well documented”. pcall
provides string errors messages, not well-defined error cases. Errors are also not clearly well-documented and clear in programmatic checking for them. Other languages offer this through exception case checking APIs (which also provide a place for documentation) that can catch and check for specific error cases without having to string match undocumented error messages.
If DataStore failures are expected behavior, and Lua does not have easy-to-use, well-documented errors and error handling, then something as critical as DataStores that requires programmatic failure checking should not use errors for describing failures except for when used improperly.
As an example of how using undocumented strings instead of Enum failure codes negatively affects developer understanding, here is what I know about DataStore failures:
-
*Async*
methods throw errors when they DataStore method limits are used up. I don’t know what this error is; I cannot check for it.
-
*Async*
methods throw errors when they cannot connect to the external DataStore service properly. This means that data is inaccessible and may be for a long period of time. I don’t know what this error is; I cannot check for it.
-
:GetDataStore
might error when the external DataStore service is down. I don’t know if it errors. I don’t know what the error is. I cannot properly check for it. (An error may be my own fault due to me using DataStores wrong, or it might be due to connection issues with the external service).
- Not an error, but I don’t have any idea when I’m close to my own request limits. I cannot prioritize requests or leave room in the limits for essential functions of the game.
If DataStore call failures are to be expected, but developers don’t know how to check for such failures properly, then how are we meant to handle the intended, expected behavior of DataStores? DataStores should only error when the programmer has made a mistake. DataStores should provide well documented, error codes/cases/objects/enums to pass the state of DataStore operations to Lua.
Another feature that would be beneficial would be exposing read-only values to developers’ DataStore limits.
Exposing real-time request limits would:
- Make the understanding of request limits more clear in developers new to DataStores
- Give developers an understanding of how many requests they use as well as how many they could use
- Allow for better prioritization of requests in order to take up the allowed requests per limit
It would also help to have up-to-date, ideally in-engine information on how often request limits are ‘expanded’ and what they are expanded by. If it takes 5 minutes to get more requests then I might want to throttle the amount of requests I’m making.
In conclusion:
- Handling data with an external service requires handling failures.
- Handling failures is best done in custom API layers above the general-purpose DataStore API
- There is no problem with ROBLOX making one of these API layers, but the general-purpose API needs some improvement and custom API layers should clearly not be general-purpose API.
- Failures should be provided in a well-documented, easy-to-handle-programmatically way.
- I suggest an Enum.
- Enums can be made mostly-clear by name.
- Enum types are easy to programmatically check for.
- Enums are already documented as existing since they appear even in the auto-generated API docs.
- Further documentation can be provided on its wiki page.
- Documentation of an enum’s existence cannot lag behind. Developers will at the least be aware of its existence prior to complete documentation.
- Enums are perfect when there need to be multiple, pre-set values. Developers need access to a list of all possible response states and be able to easily check for them in order to properly and easily use the DataStore.
- String error messages do not achieve this.
- String error messages have no set place for documentation.
- String error messages do not even have required documentation of their existence (unlike enums).
- Documentation for string error messages, including their existence, can lag behind their implementation.
- String error messages do not have set values. Developers may think they have to program for the case of ‘unknown’, undocumented values.
- Request limits should be exposed
- Developers will have a better understanding of how request limits work
- Developers will have a better understanding of how many requests they use
- Saving/Loading prioritization can be implemented in order to efficiently use remaining requests at any given time
- Exposing how often request limits are renewed and how much they are renewed by per time period has similar advantages to exposing request limits.