Skip to main content

December 16th, 2025

The Day We Redesigned the Schema Today was a case study in schema design. We started with a naive approach, hit real problems, and arrived at a metric-based architecture that actually scales. This is the complete journey.

The Problem Context

Q: What were we trying to build? A task/commitment system where users can create tasks with different verification conditions:
  • Time (within / outside a time window)
  • Location (within a radius)
  • App usage restrictions
  • AI-based activity verification
  • Photo / video proof
  • Accountability partner approval
Each task can have multiple conditions, and these conditions will keep growing as new features are added. The main challenge: How do we design a backend schema that supports unlimited condition types without constantly changing schema or backend logic?

The Initial (Naive) Schema

Q: What was our first approach? Store conditions as attributes directly:
Why this looked good initially:
  • Very readable
  • Easy to understand
  • Direct mapping from UI to backend
  • Felt “clean” and straightforward

Problems with the Initial Design

Problem 1 — Schema Locks Feature Growth

Every new condition requires:
  • Adding a new key in conditions
  • Updating backend logic
  • Updating validation
  • Handling old tasks without the new field
Example: Adding video verification forces schema change:
This is a schema migration for every new feature.

Problem 2 — Backend Must Guess the Rule

Example:
Backend has to guess:
  • Is this “never open”?
  • Is it “open less than X times”?
  • Is it “only blocked during task time”?
The rule is implicit, not explicit. Implicit rules = hidden logic = bugs later.

Problem 3 — Backend Logic Grows Uncontrollably

Backend ends up with logic like:
Each new condition:
  • Adds a new if
  • Increases coupling
  • Makes backend harder to maintain
This is hard-coded logic tied to schema shape.

The Key Insight (Turning Point)

Q: What was the core realization? This is where my friend Atheeq stepped in. My first approach was bad and naive. I was storing only values and attributes, and expecting the backend to decide the rule. Atheeq suggested the metric-based approach, and at first I didn’t fully get why it was better. But after working through the problems above, I now clearly understand why his approach was better: A good schema must also encode the rule intent. Backend should execute declared rules, not infer rules. Thanks Atheeq 👊

The Metric-Based Schema Design

Q: What’s the new approach? We redesigned conditions into a generic rule structure:
This structure is:
  • Fixed
  • Generic
  • Data-driven
  • Future-proof

Why a Separate Metrics Definition Exists

Q: What is the metrics registry for? To support and validate conditions, we added a global metric registry:
Purpose of metrics:
  • Defines what a metric is
  • Restricts allowed relations
  • Restricts target types
  • Declares permissions required
Think of it as:
  • metrics → what is possible
  • conditions → what is required

Concrete Example — Location Condition

Stored condition:
Metric definition:

How Backend Evaluates This

Step 1 — Fetch current user location
Step 2 — Calculate distance
Step 3 — Evaluate relation

Why This Is Scalable

Q: Does this actually remove if/else? No — but here’s the important realization: Yes, if/else still exists — but:
  • The number of if/else blocks is fixed per metric
  • It does not grow per task
  • It does not grow per condition
Comparison:

The Final Learning

Q: What are the key lessons learned?
  1. Schemas should encode rule intent, not just data
  2. Backend should execute rules, not infer them
  3. Hard-coding happens when logic depends on schema shape
  4. Good abstractions freeze where complexity lives
  5. Scalability comes from bounded logic, not fewer lines

One-Line Takeaway

We don’t remove if/else. We control where they live — and keep them from spreading.

Summary

Today was about schema architecture. We went from:
  • My naive attribute-based conditions
  • To understanding why that doesn’t scale
  • To Atheeq’s metric-based architecture with explicit rules
The key insight: Backend should execute declared rules, not infer rules from data shape. Honest reflection: My first approach felt clean but was actually a trap. Atheeq saw the scaling problem before I did. Now I understand why — and that’s the real learning.