Advance Topic: Composite storage items
Content request
One of the new things that we have been experimenting with in the FRAME team is composite storage item. These are not a new abstraction that FRAME provides, but rather something that we have learned to do over time, and I think it is valuable for the community to also follow.
It goes like this: If you have multiple storage items that are highly inter-linked together, and updating one will need to trigger updates in other ones as well, it is highly advisable for you to:
- Make these storage items as private as possible.
- Make a contract with yourself that you are NEVER mutating them manually in your code.
- Write a wrapper struct for them, that manages these update for you.
This wrapper will:
- forces you to think about the entry points where you could potentially mutate these inter-linked data, and helps you make sure you don't miss anything.
Here's the only example of this that exists in FRAME now: https://github.com/paritytech/substrate/blob/c087bbedbde16711450c186518314903a2949cb3/frame/election-provider-multi-phase/src/signed.rs#L116
I personally consider this a low-priority work, since it is an experimental opinion about how to do things, not even a FRAME feature per-se.
Are you willing to help with this request?
Yes!
Great, thanks for surfacing this @kianenigma ! This sounds like it should be a short write-up in our best practices page. I'll start by writing something up and if its not too high priority we can include it in the new restructured docs and have it published there once it's ready.
A couple of questions:
- In what scenarios would someone want to have a composite storage item?
- What does this optimize?
- What are the risks involved if this recommendation is not followed?
- (this more for my understanding) Is this related to FRAME's
transactionalmacro at all? If yes how and if not how do they contrast in terms of usage and whats happening under the hood?
In what scenarios would someone want to have a composite storage item?
When you have multiple storage items that are interlinked. For example, where mutating one of them must always follow with mutating another one as well. A simple example of this would be a map with a counter, although now we have CountedMap as well.
What does this optimize?
It prevents developer errors and common pitfalls, otherwise performance wise it is the same.
What are the risks involved if this recommendation is not followed?
You might end up with inconsistent state.
Is this related to FRAME's transactional macro at all?
In my opinion they are fully unrelated.
Please let me know if my example of counter is sensible, that's an important foundation to get.
The example of a counter makes sense, yep. Thanks
has any progress been made on this? otherwise I'd like to try and write something myself.
has any progress been made on this? otherwise I'd like to try and write something myself.
No, not yet. Please go ahead ! This will be useful content in our soon-to-be "Design" section (under: Storage design decisions).