Data Before Code — Design Principles for a Content-Centric Project
Once the spec is locked, most developers want to do one thing: write code. Open the terminal, initialize the project, create the first component — that rush of momentum. I nearly gave in to it. But the very first thing this project needed was not code. It was data.
This is the story of how I designed the data structure for 78 tarot cards, and why data must come before code.
Why Data Comes First
What does a user actually "experience" in Tarot Master? It is not a flashy UI. It is the card interpretation text. The text is what users read first after flipping a card, what they linger on the longest, and what they remember. No matter how polished the UI is, if the interpretations are weak, the app has no value.
This is not unique to a tarot app. In any content-centric project, data is the product. If you are building a recipe app, recipe data is the core. If you are building a dictionary app, word data is the core. In projects like these, if you neglect data design and build the UI first, you will eventually have to tear the entire UI apart when the data structure changes.
When a data schema changes, the blast radius covers the entire project. UI components that display the data change. Logic that processes the data changes. The way data is fetched changes. Conversely, once the data schema is locked, everything else naturally follows.
So I set a rule: lock the data structure before writing a single line of code.
78 Cards — The Trap of Simple Math
First, I calculated the volume of data needed. Each of the 78 cards requires upright and reversed interpretations. That is 156 interpretation texts by simple math. On top of that, each card needs basic metadata — name, type, number, image path — and keywords.
156 interpretation texts. At first, that number just felt like "a lot." But when you consider that each interpretation needs to be a natural 3-4 sentence paragraph, this is essentially a small book's worth of writing. For a solo developer to write all of this manually? Not realistic.
At this point, two things became clear. First, the data structure needs to be well-designed to manage this volume efficiently. Second, AI-assisted text generation would be essential. This is a topic for Part 5, but even at the data design stage, I was already thinking about "a structure that makes it easy for AI to generate content."
Building the Schema with Claude
Together with Claude, I first defined the data structure for a single card. Through conversation, we identified what fields were needed and what role each would play.
For basic information: a unique identifier (id), English name (name), Korean name (nameKr), card number (number), arcana type (type), and image path (image). Straightforward so far.
The real thought went into the interpretation data structure. Both upright and reversed orientations each contain a keywords array and a meaning text field. Keywords are 4 or so short words or phrases, and the meaning is a natural paragraph of text.
Two design decisions required the most deliberation: the format of interpretation text and the file structure.
Decision 1: Interpretation Text — Paragraph vs. Itemized
Should the interpretation text be a single flowing paragraph, or broken into discrete items? This decision was more consequential than expected.
Itemized text has clear advantages. It can be displayed flexibly in the UI — items can be collapsible, categorized by topic (romance, finance, health), and so on. From a data perspective, structured items are better for search and filtering.
But I thought about the nature of tarot interpretation. A tarot reading is not "information" — it is a "reading experience." Just as a fortune teller looks at a card and weaves a narrative, the interpretation text should flow as a single story. A list like "Romance: you may meet someone new. Finance: stable. Health: be careful." is clear as information, but it shatters the atmosphere of divination.
The conclusion was a natural paragraph format. Something like: "A new journey begins. It is time to step into the unknown without fear. Embrace new possibilities with an open heart." Sentences flowing naturally into one another.
This was not merely a text formatting decision. It was a decision about the project's identity. "Is Tarot Master an information app or an experience app?" I chose the latter.
Decision 2: File Structure — Single File vs. Split
How should the data for 78 cards be organized across files? Three options.
First, put all 78 cards in a single file. Simple to manage, but the file gets large. Second, separate Major and Minor Arcana. Logical, but imports become cumbersome. Third, fully split by suit. Maximum modularity, but overkill at this scale.
I weighed the trade-offs. This data is needed in full at runtime. When drawing cards, you randomly select from all 78, so partial loading offers no benefit. Splitting files for static data where code splitting is meaningless only adds complexity.
I chose the single file approach. Within the file, though, I maintained logical separation. Major and Minor Arcana are declared as separate arrays and merged at export time. Physically one file, but logically distinct when reading the code.
The lesson from this decision: "splitting is not always better." Modularity and separation of concerns are good principles, but you must evaluate whether they actually provide benefit in your situation. For this project, a single file was the simplest and most efficient choice.
Keyword Design: For Intuition, Not Search
I decided to attach about 4 keywords to each card. Defining the purpose of these keywords was important.
These are not search keywords. No one using a tarot app is going to search for "financial stability" to find a card. The keywords serve as "interpretation anchors" — letting users grasp the core meaning at a glance before reading the full paragraph. See the keywords, get the gist, then read the detailed interpretation.
I also established keyword selection criteria. Prefer concrete states over abstract words: "new beginning" over "energy," "financial stability" over "change." No keyword overlap within the same suit. If the Ace of Wands uses "passion," the Two of Wands cannot also use "passion."
And the most important criterion: upright and reversed keywords should not be simple antonyms. If upright is "success," reversed should not be "failure" — it should be something like "haste," addressing the cause rather than the opposite outcome. This connects to the philosophy of reversed interpretations I will cover later.
Establishing these criteria before data generation was enormously helpful later. When requesting AI-generated text, I could present these criteria as guidelines, resulting in consistent output.
Designing the Type Field
The type field could have been simply "major" and "minor." But Minor Arcana also need suit information. To color-code cards by suit in the UI or display suit icons, suit information needs to be a separate field.
The final design: type holds "major" or "minor," and Minor Arcana cards get an additional suit field ("wands," "cups," "swords," "pentacles"). Major Arcana cards have no suit field.
Even this granular decision emerged naturally from conversation with Claude. When I asked "what if I want to use different colors per suit in the UI?" the suggestion to separate the suit field came back immediately. Future UI requirements were being reflected in the data design stage.
The Number Field Pitfall
The card number field had its own complications. Major Arcana numbers are clean integers from 0 to 21. The problem is the Minor Arcana court cards — Page, Knight, Queen, King. How do you assign them numbers?
One approach: 11, 12, 13, 14, following traditional order. But then distinguishing "number cards (1-10)" from "court cards (11-14)" relies on magic numbers in the code.
Another approach: use string identifiers for court cards — "page," "knight," "queen," "king." This makes the distinction explicit in the type system.
I went with integers. The reason: sorting. When displaying cards in numeric order, integer comparison is natural. Court card distinction can be handled by a utility function. Not a perfect solution, but a pragmatic one.
Image Path Design
Image path conventions also needed to be established upfront. With 78 image files, a consistent naming pattern is required so paths can be generated dynamically in code.
Major Arcana use "major-{number}.jpg" format; Minor Arcana use "{suit}-{number}.jpg." For example, The Fool is "major-00.jpg," Ace of Wands is "wands-01.jpg."
Should this convention be hardcoded in the data schema, or generated from rules? I chose hardcoding, for flexibility. If some images come from a different source or have irregular filenames, hardcoded paths can accommodate that. This flexibility actually proved necessary during the image sourcing phase later on.
Once the Schema Is Locked, Code Follows
Locking down the data schema took about 30 minutes of conversation. But those 30 minutes determined everything that came after.
With the schema locked, the work ahead became clear. Fill the schema with data for all 78 cards. Build UI components that display each field. Write reading logic that randomly draws cards from the data array and determines upright or reversed orientation.
When data design leads, all subsequent work becomes something close to "filling in blanks." Conversely, if you write code without data design, every time the data structure changes, you have to modify code across the entire project. This is why data comes before code.
What I Learned
First, in content-centric projects, data design is the first milestone. Code should be written after the data schema is locked. When the schema changes, everything from UI to logic is affected.
Second, the format of data reflects the project's identity. Whether interpretation text is a paragraph or an itemized list is really answering "is this app about information delivery or experience delivery?"
Third, AI conversation can surface future requirements during the design phase. Asking "what if I want to do this later?" identifies points to reflect in today's design. The balance between over-engineering and reasonable extensibility can be calibrated through AI dialogue.
Fourth, "splitting is not always better." File separation and modularity are good principles, but only when they actually provide benefit. When a single file is the right call, splitting it only adds complexity.
Next Up
The data schema is locked. Now I need to fill this empty structure with 156 interpretation texts. Full validation of all 22 Major Arcana, a per-suit framework for the 56 Minor Arcana, finding the right tone for Korean interpretations, and the philosophy behind reversed readings. In Part 5, I will cover the strategy for generating large-scale content with AI.
댓글
댓글 쓰기