Two Ways to Remember That an Article Was Liked
The obvious way is a column. Add liked to the article row, set it true, done. It is one write, it is easy to query, and it is what most applications do. The other way is to append a record — at this instant, this article, the act of liking — to a file that is never edited, and to derive the boolean when anyone asks.
The column stores a conclusion. The log stores what happened. That difference sounds academic until the first time you need something the conclusion cannot answer: when was it liked, was it liked before or after it was read, was it ever unliked, how many things were liked last Tuesday, and what did the reader's taste look like a month ago. A column has thrown all of that away by design; the log never had to decide in advance which questions mattered.
The Vocabulary Is Small and Closed
An event vocabulary should be short enough to hold in your head — opened, liked, disliked, saved, shared, queued, and their reversals. Keeping it closed is what makes projections tractable: every consumer knows the complete set of things that can appear, and adding a verb is a deliberate act with a visible blast radius.
Note that reversals are events too. Un-saving is not the deletion of the save; it is a new fact that happened later. That is the whole discipline in one line — you never edit the past, you append the correction — and it is what lets a projection be a pure function of the log rather than of the order in which you happened to apply patches.
Why JSONL, and Why Beside the Database
The log wants properties a relational table is bad at: appends that cannot corrupt earlier lines, a format readable by anything, and durability that does not depend on the schema of a working set that gets rebuilt. One JSON object per line in a plain file gives all three. It survives a database rebuild, it can be replayed into a new schema, and when something looks wrong you can read it with your eyes.
The relational store still earns its place — it holds the article rows, the shelves, the caches, and the projections that need indexes. The split is deliberate: the durable spine is a file, the queryable working set is a database, and if the two ever disagree, the file wins.
The Test That Tells You It Is Real
There is one question that separates a genuine event log from a table with a timestamp column: could you delete the entire database and rebuild every shelf, score, and statistic from the log alone? If the answer is yes, projections are safe to change, because a wrong one is a bug you fix and recompute. If the answer is no, then somewhere a projection has become the only copy of a fact, and that fact is now as fragile as the code that wrote it.