Open Bastian opened 3 years ago
From https://github.com/discord/discord-api-docs/issues/2184#issuecomment-816353472:
[...] It is not feasible to serve the entire member list of every guild to bots, and as guild sizes continue to grow it is unlikely we will support this operation over gateway forever. If you haven't already, I would recommend taking steps to move to persisting and caching data. Having stale data is generally considered OK, and you can update data as you need to instead.
This makes this issue even more important. We should expect, that one day, Discord will remove or at least severely limit the options to request all members of a server. When this happens, we should provide a way for users to have a (semi-)persistent cache for users.
However, since this suggestion was not well received because of the immutability, this issue proposes a mutable external cache that also allows sharing data between multiple shards and allows for different data stores like Redis.
I think an immutable cache is still possible. If I am not wrong with this cache design it should be possible to have a mutable or immutable cache without the need to have a more complex code base:
private void refreshData(long id) {
if(api.isImmutableCache){
return;
}
List<DataEntity> entities = dataStore.get(
ServerTextChannelData.class, "ID", String.valueOf(id)
);
if (!entities.isEmpty()) {
data = (ServerTextChannelData) entities.get(0);
}
}
With a simple flag that can be set we can decide whether we want to actually refresh the data from the cache or not. This results in either a mutable or immutable enity. Having this immutable behaviour should also be very performant as we do not refresh the data from the cache everytime a method is called and at the same time still allows us to share the cache among other bot instances.
This results in either a mutable or immutable enity.
This will not work for collections (e.g., Server#getChannels()
) though. These are always fetched from the cache and the cache itself is mutable.
Then this will result in a sort of semi immutable. But once you get the channels from the cache they are immutable entities which you can store in a variable to process them in your task. I think that's a good trade off though.
I've already suggested a design for an external cache module in https://github.com/Javacord/Javacord/issues/454#issuecomment-832957623. However, since this suggestion was not well received because of the immutability, this issue proposes a mutable external cache that also allows sharing data between multiple shards and allows for different data stores like Redis.
Similar to the immutable cache, the mutable cache holds simple immutable and serializable POJOs that do not contain references to other entities except by their ids.
Since the module is independent of Javacord, we can use another JVM language like Kotlin to reduce the necessary boilerplate.
Storing data objects
The implementation of the data store is extremely simple and can look like this for a classic in-memory store:
Since all entities can be serialized (e.g. to JSON), it's easy to create other data stores like Redis. A mixture of different data stores for different data classes (e.g. only users/members in Redis) is also possible and easy to implement.
Using the data objects
We don't want to expose the data objects themselves but keep our current API. The current entities can be constructed as needed:
The two important differences to the previously suggested immutable cache are, that
#getServer()
are called. The other direction is no problem. For example, the deletion of a server would only result in an empty list when#getChannels()
is called.#getName()
, we refresh the cache. This makes sure that we always return the latest data. For data that cannot change (e.g. the id), a refresh is not necessary.Updating the cache
Updating the cache is very straightforward:
Since entities do not store references to other objects, it should be possible to simply map Discord's gateway entities to the data objects and replace them in our cache with very little effort. E.g. for a channel creation or deletion, we can simply add/remove the channel from the cache without having to update any references in the server object (like we currently have to).
Considerations
#refresh()
method that can be called. Another option that would not require any actions from the user is by introducing a caching layer in the store implementation that keeps frequently used entities in local memory.-
in-between. This is probably secure (since we control both the entity name and the key name) but it might be worth giving it a second thought.CompletableFuture
. I believe that this is an acceptable compromise. Especially, if you consider that in practice, you don't want every entity to be in a non-memory cache but probably only users and maybe messages. Users that require an external cache should be able to deal with these trade-offs.Server
andServerTextChannel
) in the examples do not use interfaces. This would not be the case in the final implementation, but I wanted to keep the examples simple for demonstration purposes