An invariant is a condition that holds true no matter what during the execution of a phase of a computer program.
For instance, in a classical for loop,
for (i = 0; i <= 10; i++) an invariant would be that the value of
i is always between 0 and 10 (beside explicit changes of
Trying to keep as many invariant as possible internally our software help the development. But we can use invariant also as interface, and it helps immensely consuming our software.
I was hit by this problem when I started to actually stress my own software, it took a while to admit that I did a sub-optimal choice in one of the main interface of RediSQL.
RediSQL is a Redis module that allow users to send SQL commands to a Redis. The memory space between Redis and RediSQL are separated so you can’t query Redis with SQL, but you can create your own table and use those. RediSQL is based on SQLite.
One of the main interface of RediSQL is the
REDISQL.EXEC command, that execute a raw SQL statement against one SQLite database.
Upon executing a SQL command, SQLite can return three different values:
- DONE, and the number of rows modified
- A result consisting of more than one row
I implemented the
REDISQL.EXEC command to return, respectively:
- The string “OK”
- An array containing the string “DONE” and one integer
- An array of array containing the result of a query
Moreover, a query that returns no rows, will return
DONE not an empty result.
While this seems a reasonable interface when used in the CLI, it is very difficult to use programmatically.
The user needs to test if the result is a string, and then make sure that the string is actually “OK” to match the first case.
If it is not a string, it must be an array, so the user will need to check if it is an array of length 2, and if the first elements of the array is the string “DONE” to match the second case.
Finally to match a query result, the user need to make sure that the result is an array of array, and now it can consume the result.
This is very cumbersome and tedious to implement, especially in statically typed languages like go(lang) and java. In those languanges the result is consumed as a generic
Object and parsed in something more safe. It is a little better is dynamically typed languages.
While developing SimpleSQL on top of RediSQL, I understood that this was a real problem and I decide to create a
v2 for RediSQL, fixing several other design mistake I did the first time.
The new interface exploit exactly the concept of Invariant as Interface. Now the
REDISQL.EXEC always returns an array of array and in the first array, as first elements there is always a string. Either: “OK”, “DONE” or “RESULT”.
Consuming this API is much simpler, the user know that it will always receive an array of array, and that in the first position there will be a tag to indicate how the rest of the result should be interpreted and used.
Then the same concept was exported to SimpleSQL creating an API simple to consume.