코드보다 먼저, 데이터 — 콘텐츠 중심 프로젝트의 설계 원칙
스펙이 정해지면 대부분의 개발자가 하고 싶어하는 일이 있다. 코드를 쓰는 것이다. 터미널을 열고, 프로젝트를 초기화하고, 첫 번째 컴포넌트를 만드는 그 쾌감. 나도 그 유혹에 빠질 뻔했다. 하지만 이 프로젝트에서 가장 먼저 해야 할 일은 코드가 아니라 데이터였다.
이 글은 78장의 타로카드 데이터를 어떤 구조로 설계했는지, 그리고 왜 코드보다 데이터가 먼저여야 하는지에 대한 이야기다.
왜 데이터가 먼저인가
타로 마스터에서 사용자가 실제로 "경험"하는 것은 무엇인가. 화려한 UI가 아니다. 카드 해석 텍스트다. 사용자가 카드를 뒤집고 가장 먼저 읽는 것, 가장 오래 머무는 것, 가장 기억에 남는 것이 해석 텍스트다. UI가 아무리 화려해도 해석이 빈약하면 앱의 가치는 없다.
이것은 타로 앱만의 문제가 아니다. 콘텐츠 중심 프로젝트에서 데이터는 곧 제품이다. 요리 레시피 앱이라면 레시피 데이터가 핵심이고, 사전 앱이라면 단어 데이터가 핵심이다. 이런 프로젝트에서 데이터 설계를 소홀히 하고 UI부터 만들면, 나중에 데이터 구조가 바뀔 때 UI 전체를 뒤엎어야 하는 상황이 온다.
데이터 스키마가 바뀌면 영향 범위가 프로젝트 전체에 미친다. 데이터를 표시하는 UI 컴포넌트가 바뀌고, 데이터를 처리하는 로직이 바뀌고, 데이터를 불러오는 방식이 바뀐다. 반대로 데이터 스키마가 확정되면, 나머지는 그 스키마에 맞춰 자연스럽게 따라온다.
그래서 원칙을 세웠다. 코드를 한 줄도 쓰기 전에 데이터 구조부터 확정한다.
78장, 단순 계산의 함정
먼저 필요한 데이터의 양을 계산해봤다. 78장의 카드 각각에 정방향과 역방향 해석이 필요하다. 단순 계산으로 156개의 해석 텍스트다. 여기에 각 카드의 기본 정보(이름, 타입, 번호, 이미지 경로)와 키워드가 추가된다.
156개의 해석 텍스트. 이 숫자가 처음에는 그냥 "많다" 정도로 느껴졌다. 하지만 각 해석이 3~4문장의 자연스러운 한국어 문단이 되어야 한다고 생각하면, 이건 사실상 소규모 책 한 권 분량의 글쓰기다. 개발자 혼자 이걸 직접 쓰는 건 비현실적이다.
이 시점에서 두 가지가 명확해졌다. 첫째, 데이터 구조를 잘 설계해야 이 양을 효율적으로 관리할 수 있다. 둘째, AI를 활용한 텍스트 생성이 필수적이다. 5편에서 다룰 내용이지만, 데이터 설계 단계에서 이미 "AI가 생성하기 좋은 구조"를 고려했다.
Claude와 함께 잡은 스키마
Claude와 함께 카드 하나의 데이터 구조를 먼저 정의했다. 어떤 필드가 필요한지, 각 필드의 역할은 무엇인지 대화를 통해 정리한 과정이다.
기본 정보로는 고유 식별자(id), 영문 이름(name), 한국어 이름(nameKr), 카드 번호(number), 아르카나 유형(type), 이미지 경로(image)가 들어간다. 여기까지는 직관적이다.
핵심은 해석 데이터의 구조였다. 정방향(upright)과 역방향(reversed) 각각에 키워드 배열(keywords)과 해석 텍스트(meaning)가 포함된다. 키워드는 4개 내외의 짧은 단어나 구로 구성하고, 해석 텍스트는 자연스러운 한국어 문단으로 작성한다.
이 구조를 잡는 데 가장 많이 고민한 부분이 두 가지 있었다. 해석 텍스트의 형태와 파일 구조다.
의사결정 1: 해석 텍스트 — 문단 vs 항목
해석 텍스트를 한 문단으로 할 것인가, 여러 항목으로 쪼갤 것인가. 이 결정은 예상보다 중요했다.
여러 항목으로 나누면 장점이 있다. UI에서 유연하게 표시할 수 있다. 항목별로 접고 펼칠 수 있고, 카테고리(연애, 재정, 건강 등)별로 분류해 보여줄 수도 있다. 데이터 관점에서도 구조화가 잘 되어 있으니 검색이나 필터링에 유리하다.
하지만 타로 해석의 특성을 생각했다. 타로 해석은 "정보"가 아니라 "읽기 경험"이다. 점술사가 카드를 보고 이야기를 풀어주듯, 해석 텍스트도 하나의 흐름으로 읽혀야 한다. "연애: 새로운 만남이 있을 수 있습니다. 재정: 안정적입니다. 건강: 주의가 필요합니다."식의 나열은 정보로서는 명확하지만, 점술의 분위기가 깨진다.
결론은 자연스러운 문단 형태였다. "새로운 여정의 시작을 알립니다. 두려움 없이 미지의 세계로 발을 내딛을 때입니다. 순수한 마음으로 새로운 가능성을 받아들이세요." 이런 식으로 문장들이 자연스럽게 이어지는 형태다.
이 결정은 단순히 텍스트 형식의 문제가 아니었다. 프로젝트의 정체성에 대한 결정이었다. "타로 마스터는 정보 제공 앱인가, 경험 제공 앱인가?" 나는 후자를 선택한 것이다.
의사결정 2: 파일 구조 — 단일 파일 vs 분리
78장의 데이터를 어떤 파일 구조로 관리할 것인가. 세 가지 선택지가 있었다.
첫째, 단일 파일에 78장을 모두 넣는 방식. 관리가 단순하지만 파일이 커진다. 둘째, 메이저 아르카나와 마이너 아르카나를 분리하는 방식. 논리적이지만 import가 번거로워진다. 셋째, 슈트별로 완전히 분리하는 방식. 가장 모듈화되지만 이 규모에서는 오버엔지니어링이다.
각 방식의 트레이드오프를 따져봤다. 이 데이터는 런타임에 전체가 필요하다. 카드를 뽑을 때 78장 전체에서 랜덤으로 선택하기 때문에 부분 로딩의 이점이 없다. 코드 스플리팅이 의미 없는 정적 데이터에 파일을 쪼개는 건 복잡성만 높일 뿐이다.
결국 단일 파일을 선택했다. 다만 파일 내부에서는 논리적으로 구분했다. 메이저 아르카나와 마이너 아르카나를 별도 배열로 선언하고, export 시 합치는 방식이다. 물리적으로는 하나의 파일이지만, 코드를 읽을 때는 구분이 가능하다.
이 결정에서 배운 교훈이 있다. "분리하는 것이 항상 좋은 것은 아니다." 모듈화, 관심사 분리는 좋은 원칙이지만, 그것이 실제로 이점을 주는 상황인지 따져봐야 한다. 이 프로젝트에서는 단일 파일이 가장 단순하고 효율적인 선택이었다.
키워드 설계: 검색이 아니라 직관을 위한 것
각 카드에 4개 내외의 키워드를 붙이기로 했다. 이 키워드의 목적을 정하는 것이 중요했다.
검색 기능을 위한 키워드가 아니다. 타로 앱에서 "재정적 안정"으로 카드를 검색하는 사용자는 없다. 키워드의 목적은 사용자가 카드 해석을 읽기 전에 핵심 의미를 한눈에 파악하기 위한 "해석의 앵커"다. 긴 문단을 읽기 전에 키워드를 보고 "아, 이 카드는 이런 의미구나"를 먼저 잡는 역할이다.
키워드 선정 기준도 정했다. 추상적인 단어보다 구체적인 상태를 선호한다. "에너지"보다 "새로운 시작", "변화"보다 "재정적 안정" 같은 식이다. 같은 슈트 내에서 키워드가 겹치지 않도록 한다. Wands의 Ace가 "열정"이면 Two도 "열정"이면 안 된다.
그리고 가장 중요한 기준. 정방향과 역방향의 키워드가 단순 반의어 관계가 되지 않도록 주의한다. 정방향이 "성공"이면 역방향은 "실패"가 아니라 "성급함" 같은 원인 측면의 키워드를 배치한다. 이것은 뒤에서 다룰 역방향 해석의 철학과 연결되는 부분이다.
이 기준들을 데이터 생성 전에 정해둔 것은 나중에 큰 도움이 됐다. AI에게 텍스트를 생성 요청할 때 이 기준을 가이드라인으로 제시하면 일관성 있는 결과물이 나왔다.
type 필드의 설계
type 필드는 단순하게 "major"와 "minor"로 나눌 수도 있었다. 하지만 마이너 아르카나의 경우 슈트 정보도 필요했다. UI에서 카드를 슈트별로 색상 구분하거나, 슈트별 아이콘을 표시하려면 슈트 정보가 별도 필드로 있어야 한다.
최종적으로 type에는 "major"와 "minor"를 넣고, 마이너 아르카나에는 추가로 suit 필드("wands", "cups", "swords", "pentacles")를 두는 구조로 정했다. 메이저 아르카나에는 suit 필드가 없다.
이런 세부 결정도 Claude와의 대화에서 자연스럽게 나왔다. "UI에서 슈트별로 다른 색상을 쓰고 싶으면 어떻게 해야 할까?"라는 질문을 던지니, suit 필드를 별도로 분리하는 것을 제안받았다. 미래의 UI 요구사항을 데이터 설계 단계에서 미리 반영한 셈이다.
number 필드의 함정
카드 번호(number) 필드에도 고민이 있었다. 메이저 아르카나는 0부터 21까지 깔끔한 정수다. 문제는 마이너 아르카나의 코트 카드(Page, Knight, Queen, King)다. 이들에게 번호를 어떻게 부여할 것인가.
11, 12, 13, 14로 부여하는 방식이 있다. 전통적인 순서대로 Page=11, Knight=12, Queen=13, King=14다. 하지만 이렇게 하면 "번호 카드(1~10)"와 "코트 카드(11~14)"의 구분이 코드에서 매직 넘버에 의존하게 된다.
다른 방식은 코트 카드에 문자열 식별자를 쓰는 것이다. "page", "knight", "queen", "king"으로 하면 타입 시스템에서 구분이 명확해진다.
결국 정수 방식을 택했다. 이유는 정렬이 쉽기 때문이다. 카드를 번호순으로 정렬할 때 정수 비교가 자연스럽다. 코트 카드의 구분은 별도 유틸리티 함수로 처리하면 된다. 완벽한 해결은 아니지만 실용적인 선택이었다.
이미지 경로 설계
이미지 경로도 미리 컨벤션을 정해둬야 했다. 78장의 이미지 파일이 일정한 패턴으로 명명되어야 코드에서 동적으로 경로를 생성할 수 있다.
메이저 아르카나는 "major-{번호}.jpg" 형태로, 마이너 아르카나는 "{슈트}-{번호}.jpg" 형태로 정했다. 예를 들어 The Fool은 "major-00.jpg", Ace of Wands는 "wands-01.jpg"다.
이 컨벤션을 데이터 스키마에 하드코딩할 것인가, 아니면 규칙 기반으로 생성할 것인가. 하드코딩을 선택했다. 이유는 유연성이다. 일부 이미지가 다른 소스에서 오거나, 파일명이 불규칙할 경우를 대비할 수 있다. 실제로 나중에 이미지 소싱 과정에서 이 유연성이 필요했다. 이 이야기는 시리즈 후반에서 다루게 될 것이다.
스키마가 확정되면 코드는 따라온다
데이터 스키마를 확정하는 데 걸린 시간은 대화 30분 정도였다. 하지만 이 30분이 이후의 모든 작업을 결정지었다.
스키마가 확정되자 해야 할 일이 명확해졌다. 이 스키마에 맞는 데이터를 78장분 채워야 한다. UI는 이 스키마의 각 필드를 표시하는 컴포넌트를 만들면 된다. 리딩 로직은 이 데이터 배열에서 랜덤으로 카드를 뽑고 정역방향을 결정하는 것이다.
데이터 설계가 선행되면 이후의 모든 작업이 "빈칸 채우기"에 가까워진다. 반대로 데이터 설계 없이 코드부터 쓰면, 나중에 데이터 구조가 바뀔 때마다 코드 전체를 수정해야 한다. 이것이 "코드보다 데이터가 먼저"인 이유다.
이 과정에서 배운 것
첫째, 콘텐츠 중심 프로젝트에서 데이터 설계는 첫 번째 마일스톤이다. 코드는 데이터 구조가 확정된 후에 쓰는 게 맞다. 데이터 스키마가 바뀌면 UI부터 로직까지 전부 영향받기 때문이다.
둘째, 데이터 형태의 결정은 프로젝트의 정체성을 반영한다. 해석 텍스트를 문단으로 할 것인가, 항목으로 할 것인가는 "이 앱이 정보 제공 앱인가, 경험 제공 앱인가"에 대한 대답이다.
셋째, AI와의 대화에서 미래의 요구사항을 미리 반영할 수 있다. "나중에 이런 걸 하고 싶으면?"이라는 질문을 던지면, 지금의 설계에 반영할 포인트를 찾을 수 있다. 과도한 미래 대비(오버엔지니어링)와 합리적인 확장 고려 사이의 균형을 AI와의 대화에서 잡을 수 있었다.
넷째, "분리하는 것이 항상 좋은 것은 아니다." 파일 분리, 모듈화는 좋은 원칙이지만, 실제로 이점이 있는 상황인지 따져봐야 한다. 단일 파일이 맞는 상황에서 굳이 쪼개면 복잡성만 늘어난다.
다음 편 예고
데이터 스키마가 확정됐다. 이제 이 빈 구조에 156개의 해석 텍스트를 채워 넣어야 한다. 메이저 아르카나 22장의 전수 검증, 마이너 아르카나 56장의 슈트별 프레임워크, 한국어 해석의 톤 잡기, 역방향 해석의 철학까지. AI로 대량 콘텐츠를 생성하는 전략을 5편에서 다룬다.
댓글
댓글 쓰기