One Migration, Zero Java: How We Add a New Protocol to the Control Plane
If the LLM is an agent's brain and its tools are its hands, then integration is the nervous system — the part that actually connects intent to the systems where work happens. It's also the part that quietly rots. Every new protocol you support is a temptation to copy the last one's 150 lines, tweak the names, and move on. Do that four times and you have four subtly divergent code paths, four places to fix every bug, and a platform that fights you every time it grows.
We set a harder target for our IntegrationHub control plane, and it's become one of the design decisions we're proudest of:
Adding a new product/protocol to the control plane should be a Flyway migration plus a config generator and a deploy chart — with zero new Java that branches on the protocol's name.
That's the north star. When we added an API gateway (Kong), then a message broker (RabbitMQ), then a streaming platform (Kafka), each one had to prove it out. Here's the discipline that made it real.
Declare the product; don't code it
Each product declares its behavior as data — which provisioning tool to run, which decommission tool, its default image, its plans, and what kind of workload it is (a stateless Deployment vs. a stateful StatefulSet). A generic facade reads that declaration and dispatches. There is no if (protocol == "kafka") buried in a service somewhere. The provisioning workflow doesn't know or care whether it's standing up a gateway or a broker; it resolves the delegate from the declaration and runs it.
The test we hold ourselves to is blunt: a new provider should need only a migration — no Java, no frontend branch. The moment we catch ourselves adding a name-check in code, that's a signal the abstraction leaked, and we fix the abstraction instead of adding the branch.
Share the shape, not the copy-paste
The three products don't share code by accident; they share it by structure. Their provisioning columns — what was requested, what was allocated, the audit trail — come from a shared shape, so they can't quietly drift apart. When we add a column, we regenerate the schema snapshot and a test fails loudly if anything other than the intended table changed. Shared shape, enforced by a test, beats shared code maintained by discipline.
But don't over-abstract from one example
Here's the counterintuitive part, and the mistake we deliberately avoided. It's tempting to extract a grand AbstractEverythingTool the first time you see a pattern. We don't. Behavioral abstractions get extracted at the second or third real case, not the first.
Why? Because the first example bakes in its own assumptions. Our first product delivered its config as a single rendered file mounted into a container — clean, simple, stateless. Then the stateful products arrived and broke that assumption: a broker needs a shared secret and peer discovery; a streaming platform needs per-pod identity and a cluster ID. Neither is "one flat file." Had we frozen the abstraction around the first example, every stateful product would have fought it.
So we split the difference by kind of change:
- Contract and shape refactors (a shared superclass, a resolver key, an enum) are safe to do from a single example — they're about structure, not behavior.
- Behavioral abstractions (a shared base tool, a common ops service) wait until we've seen two or three real cases, so the abstraction is drawn around what's actually common, not what one example happened to do.
Protect the shared machinery
A control plane has shared, finite capacity — you can't provision infinite clusters at once. The wrong fix is to make the overflow wait in a worker thread; that just pins threads and starves everything. The right fix is to park and requeue: when capacity is full, the task is parked with a clear "queued — waiting for a slot" status and picked up automatically when a slot frees. No thread sits blocking on a semaphore; no operator has to babysit a queue. The knobs that tune this — the cap, the poll rate, the give-up window — live in platform config, because they protect shared infrastructure and have no per-tenant meaning.
Why this is a feature, not just tidy code
Architecture maturity is a buying signal. When a prospect asks "how long until you support our broker," the answer "a migration and a chart, not a release cycle" is a fundamentally different conversation than "we'll scope it for next quarter." The discipline that keeps the code lean is the same discipline that lets the platform grow at the speed a customer needs.
The nervous system is where integrations go to die in most platforms. Treating it as a declarative control plane — one migration, zero branching Java — is how you keep it alive as you scale.
StrideAX's IntegrationHub manages API gateways, message brokers, and streaming platforms as one declarative control plane. New protocols are declarations, not rewrites — and that's on purpose.
Comments (0)
No comments yet. Be the first!
Log in to add comments.