# Abstract The Provider

> When swapping one third-party dependency for another (Shopify→Medusa, Stripe→Paddle, OpenAI→Anthropic), prefer a provider-agnostic abstraction with the new provider as the default, rather than a hard swap. Use when the user asks to replace a vendor, when choosing an open-source alternative to a paid service, or when a feature could plausibly need a second provider later.

- Skill: `jcdavis131/abstract-the-provider` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jcdavis131/abstract-the-provider`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jcdavis131/abstract-the-provider/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: jcdavis131 (https://skillmd.com/u/jcdavis131)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/jcdavis131/abstract-the-provider

---


# Abstract The Provider

A hard swap locks you to the new vendor. An agnostic layer with a default lets you swap again without a rewrite. The default means you ship now; the abstraction means you're not stuck.

## When to abstract

- The user asks to replace a vendor ("is there an open-source alternative to Shopify?").
- The feature could plausibly need a second provider later (commerce, payments, email, auth, LLM, search).
- You're choosing between a paid and an open-source option and the user prefers optionality.
- The integration is non-trivial — a hard swap would mean a future hard swap too.

Don't abstract when:
- The integration is one function call — abstraction is more code than the call.
- The provider is genuinely irreplaceable (a specific data source only one vendor offers).
- YAGNI is clear — one provider, no realistic second, and the abstraction adds complexity.

## The shape

```
<feature>/provider.ts        # the interface (methods, types)
<feature>/providers/
  medusa.ts                  # default implementation
  shopify.ts                 # the old one, kept or removed
<feature>/config.ts          # picks the provider, default = "medusa"
```

Rules:
- **The interface is the stable surface.** Callers depend on the interface, never on a provider.
- **A default is chosen, not a wiring puzzle.** `COMMERCE_PROVIDER=medusa` (default) — ship now, no config required.
- **Each provider implements the same interface.** No provider-specific methods leak into callers.
- **The old provider is either kept (for migration) or removed (if fully replaced) — don't leave it half-wired.**

## Example (commerce)

```ts
// commerce/provider.ts
export interface CommerceProvider {
  listProducts(): Promise<Product[]>;
  createCheckout(items: CartItem[]): Promise<CheckoutUrl>;
}

// commerce/providers/medusa.ts  (default)
export const medusa: CommerceProvider = { /* ... */ };

// commerce/config.ts
const name = process.env.COMMERCE_PROVIDER ?? "medusa";
export const commerce = providers[name];
```

Callers import `commerce` from `config.ts` — they never import `medusa` directly.

## Why this beats a hard swap

- **Ship now, swap later.** The default gets you working today; a future swap is a new provider file + a config flip, not a rewrite.
- **Open-source by default, paid as escape hatch.** You can default to Medusa and add Shopify later if a paid feature is needed.
- **Test surface is the interface.** Mock the interface in tests; don't mock each provider.
- **The "swap" decision is reversible.** A hard swap is a one-way door; an abstraction is a two-way door.

## Anti-patterns

- **Hard swap "to keep it simple".** Simpler now; the next swap is a full rewrite.
- **Provider methods leak into callers.** `if (provider === "medusa") ...` in a page — the abstraction is broken.
- **No default.** Forces config to ship; the default is what makes it shippable today.
- **Abstracting a one-call integration.** An interface for `getUnixTime()` is more code than the call.
- **Keeping the old provider half-wired.** Pick: migrate-then-remove, or keep-both-supported. Don't leave it dangling.

## Pair with

- `dependency-hygiene` — the new provider's pins should be an opt-in extra if it's heavy.
- `match-conventions` — mirror the existing provider's interface shape if one already exists.
- `validate-gate` — the interface is the test surface; mock it, don't mock the provider.

## Include the go-live handoff for the default provider

After the abstraction lands, the default provider still has to be stood up. Hand off the provider-specific go-live steps so the user can take it to production without re-discovering them:

> "For Medusa go-live you'll need to: stand up a Medusa backend (`npx create-medusa-app@latest`), create …"

The abstraction is the wiring; the go-live is the runtime. The handoff closes the loop — without it, the user has a clean interface and no running provider. This belongs in the `readiness-report`'s "next steps" or a follow-up message, not buried in a commit message.

