Best Practices

Why Data Modeling and API Design Matter More Than Ever in the Age of AI Code Review

Tony Dong
September 2, 2025
14 min read
Share:
Featured image for: Why Data Modeling and API Design Matter More Than Ever in the Age of AI Code Review

AI has made writing and refactoring application logic dramatically faster. But that also means the most durable parts of your system — your data model, database schema, and API contracts — matter more than ever. These artifacts live for years, anchor many downstream services, and are expensive to change once released. Great code reviews in the AI era prioritize these long-lived contracts.

Why These Contracts Are More Durable Than Code

  • High blast radius: Changing a column, enum, or endpoint shape breaks customers, internal consumers, and stored data.
  • Migrations are costly: Data transformations, backfills, and dual-write windows are risky and time-consuming.
  • Backward compatibility: Public APIs force deprecation windows and versioning strategies that outlive any single feature.
  • AI accelerates the rest: With AI generating functions quickly, the relative value of a clean model and contract increases.

Clean Models Make AI Code Generation Better

  • Clear types and invariants reduce ambiguity in prompts and lower hallucination risk.
  • Well‑named entities and fields lead to higher‑quality completions and tests, because intent is obvious.
  • Stable, documented APIs give AI solid constraints to generate against (OpenAPI/GraphQL schemas are powerful guardrails).

What To Prioritize in Code Review

Database & Data Model

  • Names reflect domain concepts; avoid overloaded or generic names (e.g., `data`, `obj`).
  • Normalization vs. denormalization trade‑offs are explicit; no hidden coupling.
  • Constraints exist: primary keys, foreign keys, unique, not‑null, and check constraints.
  • Indexes match query patterns; no accidental N+1 by design.
  • Enums and status fields are well‑defined; transitions are validated.
  • Soft deletes and multi‑tenant boundaries are consistent and auditable.
  • Migrations are reversible, idempotent, and safe for large tables (batched backfills).

API Design

  • Resource names, paths, and fields are consistent and predictable.
  • Requests/responses documented via OpenAPI (or GraphQL SDL) with examples.
  • Pagination, filtering, and sorting are consistent across endpoints.
  • Error model is standardized (e.g., RFC 7807 problem+json) with stable codes.
  • Idempotency for mutation endpoints (keys + retry semantics) where applicable.
  • Versioning strategy is defined; breaking changes are avoided or versioned.
  • AuthN/AuthZ, rate‑limits, and quota semantics are documented.

A Practical Review Checklist

  • Schema migration reviewed with sample real data; indexes and constraints verified.
  • OpenAPI/GraphQL schema diffed; run API linters (Spectral) and contract tests (Pact/Dredd).
  • Backward‑compatibility: deprecations, versioning, and rollout plan documented.
  • Performance: query plans sampled; N+1 risks called out; pagination required on lists.
  • Ownership & lifecycle: who owns the table/endpoint; retention and deletion semantics.
  • Telemetry: logs/metrics added for new critical paths and contract breakpoints.

Recommended Practices and Tools

  • API Guidelines: Google API Design Guide, Microsoft REST API Guidelines, Stripe’s API design posts, JSON:API for JSON conventions.
  • Contracts: OpenAPI/JSON Schema, GraphQL SDL; run Spectral for linting; add consumer‑driven contracts with Pact; use Dredd for spec‑to‑implementation tests.
  • DB Migrations: Atlas, Prisma Migrate, Flyway, Liquibase; enable drift detection and review plans for large tables.
  • Error Model: Adopt RFC 7807 for REST; with GraphQL, standardize errors and extensions fields.

OpenAPI Example: Bad vs. Better

The “bad” example is ambiguous, inconsistent, and lacks error shapes. The “better” example is explicit, consistent, and comes with examples and clear pagination.

# ❌ Bad (partial)
openapi: 3.1.0
info: { title: API, version: 1.0.0 }
paths:
  /listUsers:
    get:
      parameters:
        - in: query
          name: p
          schema: { type: integer }
      responses:
        '200':
          description: ok
          content:
            application/json:
              schema: { type: array }
        '500': { description: error }
# ✅ Better (partial)
openapi: 3.1.0
info:
  title: Accounts API
  version: 1.2.0
components:
  schemas:
    Problem:
      type: object
      properties:
        type: { type: string, format: uri }
        title: { type: string }
        status: { type: integer }
        detail: { type: string }
        instance: { type: string, format: uri }
    User:
      type: object
      required: [id, email]
      properties:
        id: { type: string, format: uuid }
        email: { type: string, format: email }
        createdAt: { type: string, format: date-time }
  parameters:
    PageSize: { in: query, name: limit, schema: { type: integer, minimum: 1, maximum: 100 }, required: false }
    PageCursor: { in: query, name: cursor, schema: { type: string }, required: false }
paths:
  /users:
    get:
      summary: List users
      parameters: [ { $ref: '#/components/parameters/PageSize' }, { $ref: '#/components/parameters/PageCursor' } ]
      responses:
        '200':
          description: A page of users
          headers:
            X-Next-Cursor:
              schema: { type: string }
              description: Cursor to fetch the next page
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/User' } }
                  count: { type: integer }
              examples:
                sample:
                  value:
                    items: [ { id: '2b1c...', email: 'a@example.com', createdAt: '2025-09-01T00:00:00Z' } ]
                    count: 1
        '400': { description: Bad request, content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } } }

Spectral Linting: Quick Start

Lint your OpenAPI with Spectral to enforce naming, pagination, and error‑model rules in CI.

# .spectral.yaml (example)
extends: ["spectral:oas"]
rules:
  no-ambiguous-tag-names: warn
  operation-singular-tag: off
  prop-kebab-or-snake-case:
    description: properties use snake_case or lowerCamelCase consistently
    given: "$.components.schemas.*.properties.*~"
    then:
      function: pattern
      functionOptions:
        match: "^([a-z][a-zA-Z0-9]*|[a-z]+(_[a-z0-9]+)*)$"
  use-problem-json:
    description: errors must use RFC 7807 problem+json
    given: "$..responses.*.content.*~
      ?(@property === 'application/problem+json')^"
    then: { function: truthy }
# package.json scripts
{
  "scripts": {
    "lint:api": "spectral lint openapi.yaml"
  }
}

# Run locally
npx spectral lint openapi.yaml

Contract Tests: Dredd (Spec → Implementation)

# dredd.yml (example)
swagger: ./openapi.yaml
endpoint: http://localhost:3000
hooks-worker-command: node
language: nodejs

How To Spot Problems Quickly

  • Smell: ambiguous naming. Ask for domain language. Rename before shipping to avoid permanent confusion.
  • Smell: missing constraints. If logic enforces uniqueness, the database should too.
  • Smell: undefined pagination or filtering. Lists must define limits, order, and cursors/links.
  • Smell: ad‑hoc error payloads. Move to a standard shape and documented error codes.
  • Smell: breaking changes without a plan. Require deprecations and staged rollouts.

Sources and Further Reading

Image: run this to generate a Japanese‑themed header image (logos allowed; no readable text):

./scripts/generate-blog-image-openai.sh 'data-modeling-api-design-ai-code-review' 'Zen‑inspired abstraction of data modeling and API design: clean tables, schema lines, and subtle endpoint diagrams; natural materials and negative space; no text' 'japanese-minimalist'

Then set the image path to /blog-data-modeling-api-design-ai-code-review.webp in blog metadata (already configured).

Level-up Your Code Reviews

Propel helps teams review the parts that matter most — data models, database changes, and API contracts — with context-aware suggestions and automated checks.

Explore More

Propel AI Code Review Platform LogoPROPEL

The AI Tech Lead that reviews, fixes, and guides your development team.

SOC 2 Type II Compliance Badge - Propel meets high security standards

Company

© 2025 Propel Platform, Inc. All rights reserved.