As you know, the efficient way to store complex data in contracts is usually not the best way to get it back to the user.
Suppose I need a mapping that stores allowed addresses, but I can also remove them:
mapping(address => bool) private allowedAddresses;
// Add new address:
allowedAddresses[address] = true;
// Remove an addresses
allowedAddresses[address] = false
Although it is efficient to write, delete and check if a specific address is valid, I cannot return the list of valid addresses.
If I used an array instead, I would have to reorganize the array every time I remove an item, which in addition to making the code more complex, adds considerable gas cost.
The conclusion I've come to is that in these cases it doesn't make sense to have reading functions of type "getAll" in the contract, it would make more sense for the contract state to be synchronized on the client from the events, in the same way that we synchronize the main network.
The downside of this is that we would have to cross-reference the different add and remove events and the greater the number of events, the more resources we consume from the client.
After all, there is already a debate about this, who knows even a good recommended practice?