Notes from this podcast/transcript.
I’m interested in the topic b/c “feeds” & “versioning” & “(comments | copying | etc) on anything” are common features are many CRUD/enterprise spreadsheet apps.
-
Events are “any write that needs to show up in the feed”- This is the driving use case: paginate “all changes/writes in this Basecamp project across all entity types”
- And show the change as an “old => new” diff
-
All
Events are “recording x now points to recordable y”- I.e.
recording:1gets mutated from pointing atmessage:1(its recordable) tomessage:2(new recordable) - Anything that will be “in the feed” must a
Recordingand use theRecordablepattern - It’s like CTI but the “base type” changes “which subtype row” it’s pointing at (versioning)
- The
recording:1is similar to our “identity”: it’s the stable identity that logically persists over time
- I.e.
-
Recordingis like a base type, almost like anEntitytype- Not literally every entity/table in the system is a
Recording, only the “content” entities the user is interacting with—almost like a CMS - You end up with billions of rows in
recording, b/c every message/comment/etc is owned/wrapped by arecording
- Not literally every entity/table in the system is a
-
Recordableis a term for “bag of primitives” that is the “current value” (and type) of any givenRecording- Afaict
Recordableshave no FKs—all FKs are onRecording - Seems weird, how do you show/version FK changes? I.e.
message.category_idchanging fromc1toc2— maybemessage.category_nameis materialized in theMessagerecordable, so it’s a dumb/static value to diff?
- Afaict
-
Recordings are a tree of entities
- I.e. a
ProjectownsMessageBoardownsMessages, andMessages ownComments are all actually nestedRecordings with their respective subtypes - Because all FKs are on the
Recording(weird?) really “anyRecordingcan own any child”, but onlyMessageBoardopts in to “owningMessages” andMessageopts in to “owningComments”- I.e. if you want
Messages (a recordable) to be categorized, then all recordings are now “Categorizable” (can point to aCategory), but only theMessagesubtype opts in - Opt in is probably something cute like a Ruby
include HasMessages
- I.e. if you want
- I.e. a
-
The
Recordings mean you can/must use polymorphism for everything- I.e. a single
copycontroller, a singlearchivecontroller, a singlemovecontroller that all accept aRecordingid and “just work” for allRecordables - Each
Recordablelikely has to implement it’s owndoCopy()logic? - Makes it very easy to add new entities, like “we need a
HillChart”, oh and just “turn on” comments, “turn on” etc.
- I.e. a single
-
Buckets are at the top of the tree (?) and act as a unit of permissions- Moving/copying between buckets requires the
movecontroller
- Moving/copying between buckets requires the
Relating to Homebound:
- We’ve had
Reference/Referencable - Also
CollaborationandCommentable - I wonder how/if they show trees/parent+child changes as “a single row” in their feeds—they probably don’t & just “say no”
Global undo pattern:
- Should every mutation be a
Command, andEventknow “what command they were” - Then each
Commandimplements its ownundo()method?- Seems tedious 👎
- A universal
undoas “just put the recordings back to the old recordables” seems nice- Any
undomust fundamentally capture/know the old values
- Any
- Would auto-save create a diluge of
Events? Maybe “same user/within edit window” actually mutates the current recordables?- Only if same mutation, i.e.
saveUserthen immediatecopyUsershould not combine
- Only if same mutation, i.e.
- Each low-level
Eventof recording x -> y is ideally grouped into aTransactions /Actions that can be undone as a whole. - Basically creating an application-level WAL log
- …every “content” table has a
first_id/final_idpattern… - This would 2x the number of tables across the app
- …every “content” table has a
- Eventually prune/age off
Transactions?