A multi-tenant adapter layer that doesn't rot into spaghetti
Integration code rots in a predictable way: the first client is clean, the fifth
has three if (tenant === ...) branches, the tenth is unmaintainable. The fix is
structural — make “a new integration” mean “a new adapter,” never “a new branch
in the core.”
One contract, enforced
Every external system implements the same interface. The router never knows which CRM it’s talking to.
interface Adapter {
id: string;
send(event: NormalizedEvent, ctx: TenantContext): Promise<Result>;
verify(req: IncomingRequest): boolean;
}
Push the mess to the edges
Tenant-specific quirks live inside an adapter, behind that contract — never in
the router, the queue, or the store. The core pipeline stays generic: normalize,
dedup, route. When onboarding is “write one file that satisfies Adapter,” the
codebase stops fighting you at client number ten.