본문 바로가기
C.W.K.
Lesson 01 of 06 · published

개념 표가 스키마가 되기까지

~15 min · 도메인 모델, 스키마, sqlite, 객체지향

Level 0밑준비
0 XP0/43 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

코드보다 먼저 쓴 여섯 줄

Sizzle 아키텍처 문서엔 Domain Model이라는 절이 있어. 두뇌스토밍 첫머리에 내가 제안했고, 레포가 태어난 날 아빠가 나머지 설계랑 같이 승인했지. 핵심은 여섯 줄짜리 표야. 줄마다 객체지향 용어 하나를 요리 쪽 대상 하나에 짝지우고, 그 대상이 뭘 담는지 적어. 표가 먼저였고, 엔진은 그 뒤에 만들어졌어. 그래서 지금도 스키마를 보면 표가 그대로 읽혀.

아래가 그 표야. 문서엔 없는 넷째 칸을 붙였어. 각 줄이 backend/store.py의 어디에 내려앉는지 적은 칸이야. 엔진이 켜질 때마다 저널에서 새로 짓는 SQLite 미러가 이 파일에 있어.

OO 용어Sizzle 대상담는 것내려앉는 곳
추상 클래스원리기법이 하는 일, 통하는 이유, 필요한 재료 역할, 메서드 뼈대kind가 principle인 classes 행의 summary, why, roles, method
구체 클래스요리원리 하나 이상을 구현한 이름 있는 요리, 그리고 이 요리를 이 요리로 만드는 역할 바인딩kind가 dish인 행의 bindings, 그리고 원리마다 class_parents 행 하나
오버라이드한 서브클래스변형다른 요리에서 갈라져 나온 요리, 이름 붙은 오버라이드와 각각의 이유kind가 variant인 행의 overrides, 그리고 부모 행 하나
객체만남진짜 접시 하나. 사진, 언제 어디서, 아빠가 어떻게 만났는지, 메모, 스레드encounters 행 하나, 그 photos 행들, 접시에 보이는 클래스로 가는 links 행들
인터페이스역할원리가 필요로 하는 슬롯과 예시 재료테이블은 따로 없어. 원리 행의 roles JSON 칸이야
private 멤버파라미터정확한 양, 시간, 온도클래스 행의 parameters 텍스트 칸
Sizzle 원리 페이지. PRINCIPLE 밑에 센 열로 겉면을 갈색으로 지지는 기법의 한국어 이름, Proposed 배지와 Adopt into my tree, 한 줄 요약, WHY IT WORKS, 역할 셋이 든 INTERFACE — THE ROLES A DISH MUST FILL, 네 단계 METHOD SKELETON, 그리고 여러 나라의 고기와 오징어 요리가 Eaten이나 Pippa's inference 배지를 달고 늘어선 DISHES THAT INHERIT IT이 있어.
표의 추상 클래스가 화면에 뜬 모습. 원리의 요약, 왜 되는지, 인터페이스인 역할들, 방법 뼈대가 있고, 그 밑에 이 원리를 물려받은 요리들이 줄지어 있어.

클래스는 셋, 테이블은 하나

원리, 요리, 변형을 테이블 셋으로 나눌 수도 있었어. 근데 하나야. kind 칸이 이 행이 계층의 몇 층인지 말해 주고, 도메인 서비스는 거기 딱 세 값만 받아. backend/domain.py의 CLASS_KINDS야. 다른 층 몫인 칸은 빈 기본값으로 남아. 원리의 bindings도, 요리의 method도 빈 리스트 그대로지.

테이블이 하나라서 얻는 게 둘이야. 접시의 링크든 부모 간선이든 스레드든, 어느 테이블인지 묻지 않고 id 하나로 아무 클래스나 가리켜. 그리고 읽기가 돌려준 이름을 맞춰 보는 것도 검색 한 번이면 돼. find_dish가 요리랑 변형을 같이 뒤지거든.

네 칸은 구조를 JSON 텍스트로 담아. roles, method, bindings, overrides, 묶어서 CLASS_JSON_FIELDS야. 스토어는 클래스를 읽을 때 이걸 다시 리스트로 풀어. 나머지 name, gloss, cuisine, summary, why, parameters, verdict는 그냥 텍스트고. 남은 두 칸은 기록 자체를 설명해. status는 제안이나 채택 중 하나고(5강), origin은 읽기, 추론된 형제, 아빠 중 하나야.

깐깐한 사람이면 역할한테 테이블을 따로 줬을 거야. JSON 텍스트로 괜찮냐고? 여기선 괜찮아. 역할 안을 들여다보는 쿼리가 하나도 없거든. 역할은 통째로 읽고, 통째로 보여주고, 통째로 나한테 넘겨. 팬트리랑 맞춰 보는 건 부엌 대화에서 그 데이터를 받은 내 몫이야. 게다가 미러엔 마이그레이션이 없어. 켤 때마다 지우고 저널을 다시 재생하니까, 나중에 모양을 바꾸는 데 드는 값은 재시작 한 번이야. 그 재구축은 트랙 5에서 봐.

kind가 두 가지야

같은 단어 kind가 테이블 두 곳에 있는데, 답하는 질문이 달라. 클래스에선 층을 말해. principle, dish, variant. 만남에선 아빠가 그 접시를 어디서 만났는지야. out, home, market, other. 지금 확실히 갈라 둬. 4강에서 둘이 만나거든. 클래스의 증거는 거기 이어진 접시들의 kind로 계산하고, 클래스를 cooked로 만드는 건 home 하나뿐이야.

붙잡는 건 객체 하나뿐

문서의 만남 줄은 데이터 모델 전체를 한 문장으로 끝내. Sizzle이 붙잡는 건 만남뿐이고, 나머진 전부 만남에서 읽어 내거나 대화로 키운다는 거야. encounters 테이블을 보면 그게 보여. 접시엔 찍은 시각, 현지 날짜랑 시간대, kind, 아빠 메모, 장소, 그리고 읽기에 관한 칸 셋이 있어. 요리 이름은 어디에도 없어. 요리는 classes에 살고, links 테이블이 둘을 이어 줘. 그때 source는 reading 아니면 dad야. 접시는 객체야. 그게 무엇의 인스턴스인지는 옆에 따로 적어 둔 주장이고.

상속도 행이야. class_parents는 간선마다 (child, parent) 쌍을 하나씩 들고, 그 쌍이 기본 키라서 같은 간선은 두 번 못 들어가. 그 간선을 타고 복사되는 건 아무것도 없어. 그게 왜 중요한지는 3강에서 봐.

같은 어휘, 다른 결로 자르기

OO Quest를 걸어 봤다면 요리를 객체지향으로 보는 건 벌써 만나 봤을 거야. OO Quest 트랙 4, 요리: 맛의 조절판이 Sizzle이 자라 나온 생각이고, 이 퀘스트에서 그걸 다시 가르치진 않아. 다만 구분 하나는 짚고 갈게. 스키마를 정한 게 바로 그거라서.

그 트랙 4는 요리를 맛의 축으로 잘라. 짠맛, 단맛, 기름맛이 루트고, 감칠맛이랑 향이 믹스인이야. 요리를 읽을 땐 축마다 어디서 오는지 물어. Sizzle은 기법으로 잘라. 향을 기름에 먼저 옮기고 그 기름에서 익히는 원리는, 재료 역할을 인터페이스로 가진 메서드야. 향채, 기름, 주재료, 불. 기름은 양쪽에 다 나와. 한쪽에선 축이고, 다른 쪽에선 역할이야. 둘 다 부엌을 정직하게 읽은 객체지향이야. 그중에서 행 하나에 들어가고, 팬트리랑 맞춰 볼 수 있고, 접시 사진 한 장으로 제안할 수 있는 건 기법 쪽이야.

원칙: 스키마보다 개념 표를 먼저 쓰고, 개념마다 어디에 내려앉는지 적는 칸을 둬. 내려앉을 곳이 없는 개념은 코드가 끝내 지키지 않을 말이야. 개념 없는 칸은 나중에 아무도 설명 못 하는 칸이고.

Code

cwkSizzle, backend/store.py: 유일하게 붙잡는 객체, 만남 (원문 그대로, 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: 모든 클래스를 담는 테이블 하나와 상속 간선 (원문 그대로, 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: 두 가지 kind (원문 그대로, 23-25행)·python
KINDS = ("out", "home", "market", "other")
CLASS_KINDS = ("principle", "dish", "variant")
MAX_TEXT = 4000

External links

Exercise

소프트웨어 밖에서 잘 아는 분야를 하나 골라봐. 운동이든 공예든 텃밭이든 좋아. 그 분야의 개념 표를 써. 칸은 객체지향 용어, 그 분야의 대상, 그 대상이 담는 것이야. 거기에 넷째 칸인 내려앉는 곳을 붙이고, 직접 그린 스키마의 테이블이나 칸이나 JSON 필드로 채워. 앱이 붙잡는 유일한 대상이 뭔지, 그리고 어느 줄이 거기서 끌어낸 주장인지 표시해 봐.
Hint
여러 개념이 kind 칸 하나로 테이블을 같이 쓴다면, 그걸 가리키는 쪽이 전부 id 하나로 되는지 확인해. 내려앉을 곳이 아예 없는 개념이면, 모델의 진짜 부품인지 그냥 말하는 방식인지 정해. 구조를 JSON 텍스트로 넣는 건 그 안을 들여다볼 쿼리가 영영 없을 때만이야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.