DEV Community

Chris Lee
Chris Lee

Posted on

Always Wrap External API Calls in an Adapter Layer

One of the most painful refactors I've done was replacing a payment provider that had leaked into every corner of the codebase—controllers, background jobs, even model callbacks. The fix is simple: create a thin adapter (or "port") that exposes only the domain operations your app actually needs, like charge_customer(amount, currency) or subscribe_user(plan_id). Your internal code talks to this interface, not the provider's SDK. When you inevitably switch providers, hit a rate limit, or need to add retries/logging/metrics, you change one file instead of hunting down 47 Stripe::Charge.create calls.

The adapter also gives you a natural seam for testing. In your test suite, swap the real implementation for an in-memory fake that records calls and returns canned responses. No VCR cassettes, no network flakiness, no secret keys in CI. You can even simulate edge cases—network timeouts, idempotency conflicts, webhook retries—that are nearly impossible to reproduce against a real sandbox. Your integration tests become fast, deterministic, and actually worth running on every commit.

Top comments (0)