So basically the solution to this is Dependency inversion principle - Wikipedia. This use case is the core reason Athena provides a bunch of interface modules. It allows Athena to base its implementation on the interface itself, and not a specific concrete implementation. I.e. If you wanted to customize some specific part of it, you can define a custom implementation, include the module, and tell it to use your version and everything continues to work. Assuming you implemented the interface correctly.
Another good example are the DB drivers in core Crystal org. I.e. there is crystal-db
that defines the “interfaces” for the various common parts of DB system. Like the connection, the driver, the query methods etc. Then each specific DB shard, requires this lib as a dependency, extending/including its interfaces to provide driver specific implementations.
Because each specific DB uses the same interfaces, they are all interchangeable (at least in regards to the abstractions setup within crystal-db
). Some DBs have unique features that would be ofc limited to that specific implementation.
For the most part in Crystal, there is no dedicated “interface” type. The best option that works essentially just as good, is a module with abstract defs. E.g.
module RedisInterface
abstract def hget
abstract def hset
# ...
end
Another option would be to go a step further, and maybe don’t have an interface for redis itself, but for a higher level feature like CacheInterface
that would allow you to use redis, or memcached, or even a DB or the file system. I’d also checkout [RFC] Standardized Abstractions, as that is focused on the same sort of problem, and shows some examples of how PHP handles it. Doing something similar for Crystal may also be beneficial.
EDIT: I missed the core question. The solution to “handle any redis” is to ultimately have just a single robust redis shard everyone uses, versus some people using shard A and other B, and even others C. A workaround for this would be to have your lib depend on RedisInterface
that it defines, then create adapters for each redis shard that implement that interface. Then the user would have to require whatever adapter they’re using (or maybe you could detect it somehow via a macro and require it for them).
@crimson-knight Went through a similar exercise, so he can prob provide some more specific insight as well.