The seam comes first: designing a capability before we build it.
We design the interface to a capability before we build the thing behind it. Here is why the seam, not the implementation, is where the real engineering happens.
Every capability at Existence is used through a door we designed before we built the room behind it. Before a single line of the advertising code existed, there was AdsService: a short list of things an app is allowed to ask for, and nothing at all about who answers. The same is true of notifications, of localisation, of every shared layer we have written about here. The interface came first. The implementation came second, and quietly, because by the time we reached it the interesting decision had already been made.
That ordering is not an accident of how we happened to type. It is one of the habits that lets a small team keep a growing family of apps standing on one shared foundation without that foundation slowly turning into a cage. It is worth explaining why we draw the seam before we fill it.
The question we answer before writing any code
When we start a new shared capability, the first question is never "which library should we use?" It is "what is the smallest thing an app actually needs to ask for?" An app does not need to know about ad networks, mediation, or fill rates. It needs to say: put a banner in this slot, give me a rewarded video and tell me whether it was watched, stop showing ads to this person because they paid. That short list is the interface. Everything about the vendor behind it is a detail we deliberately refuse to let the app see.
Writing that list down honestly is most of the work. Once the contract is small and true, the implementation has somewhere to stand and the apps have something stable to lean on, something that will outlive any particular vendor we happen to pick this year.
An interface is a promise about what a capability does, kept deliberately silent about how it does it.
The seam is where the real design happens
It is tempting to assume the hard part of a package is the implementation: the integration code, the edge cases, the vendor's quirks. Those are laborious, but they are replaceable. The part that is genuinely hard to change later is the boundary, because the boundary is the thing a dozen apps have already written their own code against. Get the implementation wrong and you fix it in one place. Get the interface wrong and you are asking every app on the foundation to change at once, which is exactly the floor-moving problem we try never to create.
So we spend our most careful thinking on the shape of the seam, and we treat the code behind it as something we fully expect to replace one day. In practice we have replaced it: a capability can change the engine it leans on entirely, and the apps on top never find out, because the only thing they ever knew was the interface.
What designing the seam first buys us
The discipline pays for itself in more places than the obvious one. A few of them:
- We can swap the engine without touching the apps. The whole reason the contract exists is so that the answer to "who provides this?" can change while the question an app asks stays exactly the same. The apps depend on the promise, never on the provider.
- We can test apps against a stand-in. A fake implementation of the interface lets an app be exercised with no network, no live vendor, and no flaky ad fill. The interface makes the real thing optional in precisely the places where the real thing is painful.
- Onboarding gets smaller. A new engineer learns the interface, which is a handful of methods, rather than the vendor, which is a manual. The surface they have to hold in their head is the one we designed to be small.
- Work can happen in parallel. Once the interface is agreed, one person can build the screen that uses it while another finishes the thing behind it. Both are coding against the same promise, so the two halves meet cleanly in the middle.
Keeping the interface honest
The failure mode here is subtle, and we have caught ourselves drifting toward it. It is letting the vendor leak through the seam. A method named after a specific ad network, a parameter that only makes sense if you already know the underlying SDK, a return type borrowed straight from a third-party library: each of these is a small crack where the implementation has seeped into the contract. The moment an app has to understand the vendor in order to use the interface, the seam has stopped protecting anything at all.
So we hold a few lines. The interface speaks the app's language, not the vendor's. It stays as small as it can while still being genuinely useful. And when we are tempted to add a method because one particular implementation happens to make it easy, we ask whether every future implementation could honour it too. If the answer is no, it does not belong in the contract.
Why it is worth the patience
Designing the interface first feels slower on the first day, because you are writing a promise instead of a feature, and a promise does not demo. It stops feeling slow the first time a vendor disappoints you, a price changes, or a better option appears, and you find that the switch is an afternoon's work inside one package rather than a migration spread across every app you ship. The seam you drew carefully at the start is the thing that makes the change cheap at the end.
It is the same idea that runs under nearly everything we build on this foundation: decide the shape once, with care, so that you stay free to change the details forever. The interface is where we make that decision, and it is why we always draw it first.