Implementation of the Android + Web (Angular) + Firebase application, which has a many-to-many relationship: User ↔ Widget (widgets can be accessed by several users).
Questions:
- List of all widgets that the user has.
- The user can only see widgets that are available to him.
- Be able to see all the Users who are provided with this Widget.
- One widget can be owned / managed by several users with equal rights (modify the widget and change to whom it is shared). Just like Google Drive makes access to specific users.
One approach to implementing join-style is to go for this tip: https://www.firebase.com/docs/android/guide/structuring-data.html (" Joining Flattened Data ") through multiple listeners. However, I doubt this approach because I found that loading data would be alarmingly slow (at least on Android). I asked about this in another question - Firebase Android: slow "join" the use of many listeners seems to contradict the documentation .
So, this question concerns a different approach: for all copies of all widgets that the user has. As used in the Firebase + Udacity tutorial "ShoppingList ++" ( https://www.firebase.com/blog/2015-12-07-udacity-course-firebase-essentials.html ).
Their structure looks like this:
In particular, this part is userLists :
"userLists" : { " abc@gmail ,com" : { "-KBt0MDWbvXFwNvZJXTj" : { "listName" : "Test List 1 Rename 2", "owner" : " xyz@gmail ,com", "timestampCreated" : { "timestamp" : 1456950573084 }, "timestampLastChanged" : { "timestamp" : 1457044229747 }, "timestampLastChangedReverse" : { "timestamp" : -1457044229747 } } }, " xyz@gmail ,com" : { "-KBt0MDWbvXFwNvZJXTj" : { "listName" : "Test List 1 Rename 2", "owner" : " xyz@gmail ,com", "timestampCreated" : { "timestamp" : 1456950573084 }, "timestampLastChanged" : { "timestamp" : 1457044229747 }, "timestampLastChangedReverse" : { "timestamp" : -1457044229747 } }, "-KByb0imU7hFzWTK4eoM" : { "listName" : "List2", "owner" : " xyz@gmail ,com", "timestampCreated" : { "timestamp" : 1457044332539 }, "timestampLastChanged" : { "timestamp" : 1457044332539 }, "timestampLastChangedReverse" : { "timestamp" : -1457044332539 } } } },
As you can see, copies of the shopping list "Test List 1 Rename 2" info are displayed in two places (for 2 users).
And here is the rest for completeness:
{ "ownerMappings" : { "-KBt0MDWbvXFwNvZJXTj" : " xyz@gmail ,com", "-KByb0imU7hFzWTK4eoM" : " xyz@gmail ,com" }, "sharedWith" : { "-KBt0MDWbvXFwNvZJXTj" : { " abc@gmail ,com" : { "email" : " abc@gmail ,com", "hasLoggedInWithPassword" : false, "name" : "Agenda TEST", "timestampJoined" : { "timestamp" : 1456950523145 } } } }, "shoppingListItems" : { "-KBt0MDWbvXFwNvZJXTj" : { "-KBt0heZh-YDWIZNV7xs" : { "bought" : false, "itemName" : "item", "owner" : " xyz@gmail ,com" } } }, "uidMappings" : { "google:112894577549422030859" : " abc@gmail ,com", "google:117151367009479509658" : " xyz@gmail ,com" }, "userFriends" : { " xyz@gmail ,com" : { " abc@gmail ,com" : { "email" : " abc@gmail ,com", "hasLoggedInWithPassword" : false, "name" : "Agenda TEST", "timestampJoined" : { "timestamp" : 1456950523145 } } } }, "users" : { " abc@gmail ,com" : { "email" : " abc@gmail ,com", "hasLoggedInWithPassword" : false, "name" : "Agenda TEST", "timestampJoined" : { "timestamp" : 1456950523145 } }, " xyz@gmail ,com" : { "email" : " xyz@gmail ,com", "hasLoggedInWithPassword" : false, "name" : "Karol Depka", "timestampJoined" : { "timestamp" : 1456952940258 } } } }
However, before moving on to implementing a similar structure in my application, I would like to clarify a few doubts.
Here are my related questions:
- In their ShoppingList ++ application, they only allow one “owner” assigned in the
ownerMappings node. Thus, no one can rename the shopping list. I would like to have several "owners" / administrators with equal rights. Will such a keep-copy-per-user structure still work for multiple owner / admin users without the risk of data corruption / “desync” or “pranks”? - Data corruption may occur in such scenarios: User1 goes offline, renames Widget1 to Widget1Prim. Although User1 is disabled, User2 is split from Widget1 to User3 (a copy of User3 will not yet know about renaming). User1 goes to the Internet and sends information about renaming Widget1 (only for its own and copies of User2, whose client code was known at the time of renaming, and not for updating a copy of User3). Now, in a naive implementation, User3 will have the old name, and the rest will have a new name. This will probably be rare, but still a little worrying.
- Can / lead to a data corruption scenario in paragraph "2." (e.g. on AppEngine), listening to changes and ensuring proper distribution to all user copies?
- And / or may / should lead the data corruption scenario to paragraph "2." be solved by introducing redundant listening to changes in exchange and renaming and distributing changes in copies for each user to handle a special case? In most cases, this would not be necessary, so this could lead to a performance / bandwidth penalty and complex code. Is it worth it?
- Going forward, as soon as we have several versions deployed "in the wild", will the development of the scheme become cumbersome, given what part of the responsibility for data processing lies with the code in the clients? For example, if we add new relationships that older versions of clients do not yet know about, do they not seem fragile? Then return to server-side server-side process, for example. AppEngine (described in question "3.")?
- It would seem a good idea to also have a "master reference copy" of each widget / shopping list to give a good "source of truth" for any type of syncer-ensurerer operation that is updated for every -ser copy?
- Any special considerations / traps / blockers regarding the rules.json / rules.bolt permissions for data structured in this way (redundant) way?
PS: I know about atomic multi-path updates through updateChildren() - I would definitely use them.
Any other hints / observations are appreciated. TIA.