Skip to content
C.W.K.
Lesson 01 of 06 · published

The Concept Table Becomes a Schema

~15 min · domain-model, schema, sqlite, object-orientation

Level 0Mise en Place
0 XP0/43 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Six rows, written before any code

Sizzle's architecture doc has a section called Domain Model. I proposed it at the start of the brainstorm, and Dad agreed to it with the rest of the design on the day the repository was born. Its core is a table of six rows. Each row pairs a word from object orientation with a cooking object and says what that object holds. The engine was written after the table, and you can still read the table straight off the schema.

Here it is again, with a fourth column the doc does not have: where each row lands in backend/store.py, the SQLite mirror the engine rebuilds from its journal on every start.

OO wordSizzle objectWhat it holdsWhere it lands
Abstract classPrincipleWhat a technique does, why it works, the ingredient roles it needs, a method skeletonA classes row with kind principle: summary, why, roles, method
Concrete classDishA named dish realizing one or more principles, with the role bindings that make it this dishA classes row with kind dish: bindings, plus one class_parents row per principle
Subclass with overridesVariantA dish derived from another dish, with named overrides and the reason for eachA classes row with kind variant: overrides, plus one parent row
ObjectEncounterOne real plate: photos, when and where, how Dad met it, his note, its threadAn encounters row, its photos rows, and links rows to the classes it shows
InterfaceRolesThe slots a principle needs, with example membersNo table of its own: the roles JSON column on the principle's row
Private membersParametersExact amounts, times and temperaturesThe parameters text column on the class's row
A Sizzle principle page: PRINCIPLE above a Korean name for searing a browned crust over high heat, a Proposed badge with Adopt into my tree, a one-line summary, WHY IT WORKS, INTERFACE — THE ROLES A DISH MUST FILL with three roles, a four-step METHOD SKELETON, and DISHES THAT INHERIT IT listing grilled meat and squid dishes from several cuisines with Eaten or Pippa's inference badges.
The table's abstract class on screen: a principle's summary, why it works, the roles of its interface and its method skeleton, with the dishes that inherit it underneath.

One table for three kinds of class

Principle, dish and variant could have been three tables. They are one. A kind column says which level of the hierarchy a row sits on, and the domain service accepts exactly three values for it, CLASS_KINDS in backend/domain.py. Columns that belong to another level keep their empty defaults: a principle's bindings stays an empty list, and so does a dish's method.

The single table buys two things. A plate's link, a parent edge or a thread can point at any class by one id without asking which table it lives in. And matching a name the reading returns is one search, because find_dish looks through dishes and variants together.

Four columns hold structure as JSON text: roles, method, bindings and overrides (CLASS_JSON_FIELDS). The store decodes them back into lists when it reads a class. The rest are plain text: name, gloss, cuisine, summary, why, parameters, verdict. Two more columns describe the record itself: status (proposed or adopted, Lesson 5) and origin (a reading, an inferred sibling, or Dad).

Is JSON text acceptable where a purist would give roles their own table? Here it is, because no query ever looks inside a role. Roles are read whole, shown whole and handed to Pippa whole; matching a pantry against them is her work in the kitchen conversation, done on that data. And the mirror has no migrations. It is deleted and replayed from the journal on every start, so reshaping it later costs one restart. Track 5 of this quest shows that rebuild.

Two different kinds of kind

The same word, kind, sits on two tables and answers two questions. On a class it names the level: principle, dish or variant. On an encounter it says where Dad met the plate: out, home, market or other. Keep them apart now, because Lesson 4 joins them. A class's evidence is computed from the encounter kinds of the plates linked to it, and home is the one that makes a class cooked.

The object is the only thing captured

The doc's Encounter row ends with the whole data model in one sentence: the encounter is the only thing Sizzle captures, and everything else is read from encounters or grown in conversation. The encounters table shows it. A plate carries when it was captured, its local date and time zone, its kind, Dad's note, a place, and three columns about its reading. Nothing on it names a dish. The dish lives in classes, and the links table ties the two, with a source of reading or dad. The plate is the object. What it is an instance of is a claim kept beside it.

Inheritance is a row as well. class_parents holds one (child, parent) pair per edge, and the pair is its primary key, so the same edge cannot be stored twice. Nothing is copied down that edge. Lesson 3 is about why that matters.

The same vocabulary, cut along a different line

If you have walked the OO Quest, you have already seen cooking as object orientation. OO Quest Track 4, Cooking: A Flavor Control Panel is the idea Sizzle grew from, and this quest will not teach it again. One distinction is worth naming, because it decided the schema.

Track 4 cuts cooking along flavor axes. Salt, sweetness and fat are roots, umami and aroma are mixins, and you read a dish by asking where each axis comes from. Sizzle cuts along techniques. A principle such as "move aroma into fat first, then cook in that fat" is a method with an interface of ingredient roles: an aromatic, a fat, a main ingredient, heat. Fat appears in both readings, as an axis in one and as a role in the other. Both are honest OO readings of a kitchen. The technique reading is the one that fits in a row, can be matched against a pantry, and can be proposed from one photo of a plate.

Principle: Write the concept table before the schema, and give it a column for where each concept lands. A concept with no landing place is a word the code will never honor, and a column with no concept is one nobody can explain later.

Code

cwkSizzle, backend/store.py: the encounter, the only captured object (verbatim, lines 19-32)·text
CREATE TABLE encounters (
    id TEXT PRIMARY KEY,
    request_id TEXT UNIQUE,
    captured_at TEXT NOT NULL,
    local_date TEXT,
    timezone TEXT,
    kind TEXT NOT NULL,
    note TEXT NOT NULL DEFAULT '',
    place TEXT,
    created_at TEXT NOT NULL,
    reading_status TEXT NOT NULL DEFAULT 'pending',
    reading_error TEXT,
    reading_id TEXT
);
cwkSizzle, backend/store.py: one table for every class, and the inheritance edge (verbatim, lines 62-86)·text
CREATE TABLE classes (
    id TEXT PRIMARY KEY,
    kind TEXT NOT NULL,
    name TEXT NOT NULL,
    gloss TEXT NOT NULL DEFAULT '',
    cuisine TEXT NOT NULL DEFAULT '',
    summary TEXT NOT NULL DEFAULT '',
    why TEXT NOT NULL DEFAULT '',
    roles TEXT NOT NULL DEFAULT '[]',
    method TEXT NOT NULL DEFAULT '[]',
    bindings TEXT NOT NULL DEFAULT '[]',
    overrides TEXT NOT NULL DEFAULT '[]',
    parameters TEXT NOT NULL DEFAULT '',
    verdict TEXT NOT NULL DEFAULT '',
    status TEXT NOT NULL,
    origin TEXT NOT NULL,
    source_encounter TEXT,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);
CREATE TABLE class_parents (
    child TEXT NOT NULL,
    parent TEXT NOT NULL,
    PRIMARY KEY (child, parent)
);
cwkSizzle, backend/domain.py: the two kinds of kind (verbatim, lines 23-25)·python
KINDS = ("out", "home", "market", "other")
CLASS_KINDS = ("principle", "dish", "variant")
MAX_TEXT = 4000

External links

Exercise

Pick a domain you know well outside software, such as a sport, a craft or a garden, and write its concept table: an OO word, the domain object, and what that object holds. Then add a fourth column, where it lands, and fill it with a table, a column or a JSON field in a schema you sketch. Mark which object is the only thing your app would capture, and which rows are claims derived from it.
Hint
If several concepts share one table with a kind column, check that anything pointing at them can use a single id. If a concept has no landing place at all, decide whether it is a real part of the model or only a way of talking about it. Store structure as JSON text only where no query will ever need to look inside it.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.