Skip to main content
Handbook/Architect/Page 30 · Integrations

Wrap the Services You Depend On

Share

Share this page

Pass it to someone who needs it.

Star on GitHub

Key takeaway: Put a vendor behind your own door

Your agent wires an email service, an AI API, and a payment SDK, the vendor's own ready-made code, straight into feature code, wherever each one is needed. It works, until the vendor changes its prices, you want to switch providers, or the SDK ships a breaking change. Now that one change is scattered across twenty files. This chapter gets each outside service behind one door you control.

3.6.1A vendor threaded everywhere is a rewrite waiting

When feature code calls a vendor's SDK directly, the vendor is glued into your whole app. Every screen that charges a card imports the payment library, pulling that vendor's code into its own file. Every place that emails a user does the same with the mail library. This is vendor lock-in: the outside service is now welded into dozens of files.

The day the vendor raises its price, breaks its SDK, or loses your trust, the swap means editing everywhere it appears. Vendor entanglement is one of the most common rewrite triggers in AI-built apps. Note this is a service you consume, distinct from the API you expose.

3.6.2Put each service behind one adapter

Give each outside service one adapter, a thin file you own that wraps the vendor and stands between it and your code. Your feature code calls your function (charge(...)), and only the adapter file ever imports the vendor. That one file is the whole surface the rest of your app sees. This owned wrapper is sometimes called an anti-corruption layer, because it stops the vendor's shape from leaking into, and corrupting, the rest of your code.

// features/checkout.ts (feature code, vendor-free) import { charge } from "../lib/payments" await charge({ amountCents, currency, customerId }) // lib/payments.ts (YOUR adapter: the ONLY file // allowed to import the vendor SDK) import Stripe from "stripe" const stripe = new Stripe(process.env.STRIPE_KEY) export async function charge(p) { const intent = await stripe.paymentIntents.create({ amount: p.amountCents, currency: p.currency, customer: p.customerId, }) return { id: intent.id, status: intent.status } }

Some vendors also call back. A payment provider takes your charge request, then contacts your app minutes later with the outcome, over a webhook, an address on your app the vendor sends to. That handler belongs beside the adapter and not in a feature file, so everything about that vendor still lives in one place.

3.6.3Now you can swap without a rewrite

To move from one provider to another, or to absorb a breaking SDK change, you rewrite the inside of that one adapter. The charge function keeps the same name and the same shape, so every file that calls it stays untouched.

The rest of your app never notices the vendor changed. That is exactly the "extend without rewrites" this part is built around: a swap that could have touched twenty files now touches one.

3.6.4Wrap what you could swap, not your framework

The rule has an edge, and it is worth knowing before you over-apply it. Wrap the services you could plausibly change one day: payments, email and SMS, an AI model API, a login provider.

Do not wrap your framework, your database library, or your UI toolkit. You committed to those when you picked your stack. Swapping one would be a rewrite either way, so the wrapper buys you nothing and costs every reader a layer to see through.

3.6.5Keep the door thin and in your words

An adapter is only useful if it stays narrow. Expose the handful of operations your app actually needs, named in your own terms (charge, refund, sendEmail), not the vendor's entire API surface.

Do not let the vendor's own types leak back out through the door. If charge returns the vendor's raw response object, every caller is quietly coupled to that vendor again, and you are back where you started. Return a small shape you define, the same narrow-interface idea from the coupling chapter.

This prompt has your agent pull every vendor behind its own door:

Ready prompt
Act as a senior engineer hardening my vendor boundaries. Read my rules file and my folder layout first, so adapters land where my layer rules put them and respect my import direction. Find every place my code touches an external service directly: a payment SDK, an email or SMS provider, an LLM API, an auth SDK. Move each vendor call behind a single thin adapter I own, so exactly one file imports that vendor's SDK. Expose only the operations my app needs, named in my own terms, and never leak the vendor's own types back through the adapter. Do not wrap my framework, my database library, or my UI toolkit. List each service, the one file that should own it, and the functions the rest of my app calls instead. Add one line to my rules file: only its adapter may import a vendor SDK. If you need the full reasoning behind this step, read https://zalt.me/guides/vibe-coding/architect/third-party-boundaries My services and where they are called:

Do this now: paste the prompt so your agent finds every direct vendor call and moves each behind one adapter you own, then confirm exactly one file imports each SDK.

Mahmoud Zalt

Mahmoud Zalt

Software engineer, 16+ yrs · built Sistava.com in 3 months, idea to production, using these methods

Resources
Star on GitHubContribute
Donate

Support my work

A small tip keeps the free work coming.

© 2026 Mahmoud Zalt. Free to read, not to republish.
Copyright & license