Vibhanshu Sharma
active · powerplay
PORTFOLIO.SYS›content›blog›dont-leak-your-schema.mdx
Markdown · 7 min read · 2026-07-09

Don't Leak Your Database Schema: The Manifest Pattern for Integrations

Your DB calls a field cust_dn. Your integration partner and your UI should never see that name. Here's how a tiny translation table decouples your storage from your public contract — and lets you rename columns without breaking a single consumer.


// tl;dr
  • Never expose raw DB field names to consumers — once a partner integration depends on cust_dn, you can never rename it.
  • The manifest pattern maps a stable public id (customer_name) to a private physical path (cust_dn) that can change freely.
  • Renaming a column becomes a one-line manifest change instead of a breaking API migration.
  • It's a textbook anti-corruption layer: your storage schema and your public contract evolve on independent schedules.

Imagine a field in your database called cust_dn. It's a fine name internally. It is also a name that your integration partners, your frontend, and your error messages should never, ever see.

Because the day you expose cust_dn to the outside world is the day you can never rename it. Every consumer hardcodes it. It becomes load-bearing legacy the moment it leaks. And "we can't rename this database column because a partner's integration depends on the name" is a genuinely miserable place to end up.

The fix is small: a translation layer between what you store and what you expose. I call it the manifest pattern.


The Split: Public ID vs Physical Path

Every field gets two names:

  • A public id — stable, meaningful, what consumers actually use. customer_name.
  • A physical path — where the value actually lives in your document. cust_dn.

Consumers only ever touch the public id. The physical path is a private implementation detail.

// field manifest — click a row
Public field_idInternal DB pathCategory
customer_namecust_dnAccount
email_addresscontact.primaryContact
account_tierbilling.plan_codeBilling
signup_datemeta.created_atLifecycle
regiongeo.region_codeLocation
statusstate.currentLifecycle

Click any row to see the two sides. Then hit "simulate DB rename" — watch what happens to consumers when you change the underlying column. (Spoiler: nothing. That's the entire point.)

The phone-contact analogy

You don't dial your mom's phone number from memory every time. You tap "Mom." The contact name is stable; the number behind it can change. When she switches carriers and gets a new number, you update it once in your contacts, and every future call to "Mom" just works.

The public id is the contact name. The physical path is the phone number. The manifest is your contacts app.


The Three Payoffs

1. Stability — rename freely

This is the headline. Rename cust_dn to cust_dn_v2 in the database, change one line in the manifest, and every consumer keeps working. They were never coupled to the physical name — they were coupled to customer_name, which didn't move.

const MANIFEST = {
  customer_name:  'cust_dn_v2',          // ← only this line changed
  account_tier:   'billing.plan_code',
  signup_date:    'meta.created_at',
};

2. Security — never expose internals

The frontend, the partner API, and your error messages only ever see customer_name. Your actual schema — table shapes, nested document structure, naming conventions — stays private. Leaking billing.plan_code in an API response tells an attacker how your documents are shaped. Leaking customer_name tells them nothing.

3. Abstraction — users map logical fields

When a partner configures their integration, they map their fields to your logical fields — customer_name, account_tier — not to cust_dn and billing.plan_code. They're working with a clean domain vocabulary, not spelunking through your storage layout.


How the Engine Uses It

The manifest is just a Map from public id to physical path. To read a value, look up the path, then pull it out of the document — including nested, dotted paths — with a safe getter like lodash's get:

import get from 'lodash.get';
 
function readField(doc, publicId) {
  const path = MANIFEST[publicId];          // 'billing.plan_code'
  if (!path) throw new Error(`Unknown field: ${publicId}`);
  return get(doc, path);                    // safely walks the nested path
}
 
readField(account, 'account_tier'); // → account.billing.plan_code

That get matters: physical paths are often nested (billing.plan_code, meta.created_at), and a naive doc[path] won't traverse them. The getter handles dotted paths and returns undefined instead of throwing on a missing intermediate object.


The Bigger Idea: An Anti-Corruption Layer

This pattern has a name in domain-driven design: an anti-corruption layer. It sits between your internal model and an external system, translating between the two so that neither one's vocabulary contaminates the other.

Your database schema evolves on its own schedule. Your public contract evolves on its own schedule. The manifest is the seam between them — the one place where "the field moved" is a one-line change instead of a breaking, multi-team migration.

It costs you a small lookup table and a getter function. It buys you the freedom to refactor your storage layer whenever you want, without ever sending a "breaking API change" email. That's a trade I'll take every time.

Keep reading

We Shipped an AI Code Reviewer With Three Prompts. It Was Wrong Too Often and Quiet Too Long.

2026-07-28 · 9 min read

One Reviewer, Four Codebases, Four Different Definitions of Correct

2026-07-28 · 10 min read

Our Cross-File Pass Couldn't See Other Files. Tree-sitter Fixed That.

2026-07-28 · 10 min read

We Put a Cheap Model in Charge of the Expensive Ones

2026-07-28 · 10 min read
← all posts