Skip to main content

Leaky API Abstraction

A leaky API exposes database schemas and internal details, coupling clients to internals and blocking refactoring. Design the contract deliberately with DTOs, use opaque external IDs, and keep storage free to change behind a stable boundary.

A leaky API abstraction lets implementation details escape through the contract: response fields mirror database columns, internal enum codes and auto-increment IDs are exposed, and the API shape is a one-to-one reflection of the storage schema. The abstraction the API should provide — a stable, intention-revealing contract — leaks the internals it ought to hide.

Why It Happens

Generating endpoints directly from database tables or ORM entities is fast and tempting. Auto-exposing the data model means no separate contract to design or maintain. Under time pressure, returning the entity "as is" feels efficient. Without a deliberate boundary (DTOs, mapping), internal and external representations fuse.

Why It Hurts

Clients couple to your internals, so you can no longer change the database schema, rename a column, split a table, or switch storage without breaking them. The very refactors that keep a system healthy become breaking changes. Internal details leak that shouldn't be public — surrogate keys reveal volume and ordering, internal status codes confuse consumers, and exposed fields can become security or privacy issues. The API ages badly because it cannot evolve independently of its implementation.

Warning Signs

  • Response fields match database column names exactly.
  • Internal auto-increment IDs or enum codes appear in payloads.
  • The API schema changes whenever the database schema changes.
  • Clients depend on quirks of the storage model.

Better Alternatives

Design the API contract deliberately and independently of storage. Use data transfer objects (DTOs) or response models that express the domain in client terms, mapped from internal entities. Apply domain-driven design to keep the external model meaningful rather than a storage dump. Use opaque or external identifiers (UUIDs or public slugs) instead of internal keys. Treat the contract as the stable surface and the implementation as free to change behind it.

How to Refactor Out of It

Introduce a mapping layer that translates between internal entities and external DTOs, so the two can diverge. Replace exposed internal IDs with stable public identifiers, providing a translation table if needed. Rename leaked fields to domain-meaningful names in the response model while keeping storage as-is. For a live API, evolve under versioning to avoid breaking current clients. Once the boundary exists, you can refactor storage freely — the contract stays put, which is exactly what consumers were promised.