Repository navigation
Expand file tree
/
Copy pathnote-why-multi-protocol.html
More file actions
267 lines (252 loc) · 20.7 KB
/
Copy pathnote-why-multi-protocol.html
File metadata and controls
267 lines (252 loc) · 20.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Why MockForge Stayed Multi-Protocol When Everyone Else Stayed HTTP</title>
<meta name="description" content="The market is full of HTTP-only mock services. We took the harder path: ten protocols out of the box. Here's the reasoning that drove that decision." />
<link rel="icon" href="/public/favicon.ico" sizes="any">
<link rel="icon" type="image/png" sizes="32x32" href="/public/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/public/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/public/apple-touch-icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/public/styles.css" />
<link rel="alternate" type="application/rss+xml" title="MockForge Engineering Notes" href="/rss.xml" />
<script>
(function () {
try {
var storedTheme = localStorage.getItem('theme');
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (storedTheme === 'dark' || (!storedTheme && prefersDark)) {
document.documentElement.classList.add('dark');
}
} catch (error) {
console.warn('Theme initialization failed:', error);
}
})();
</script>
<style>
html, body { height: 100%; }
* { transition: background-color 0.2s ease, border-color 0.2s ease; }
html.dark { color-scheme: dark; }
html.dark body { background-color: #0f172a; color: #e2e8f0; }
html.dark .bg-white { background-color: #1e293b !important; }
html.dark .bg-surface-subtle,
html.dark .bg-stone-50,
html.dark section:not(.bg-white):not([style*="background"]) { background-color: #0f172a; }
html.dark .text-text-primary,
html.dark .text-slate-900,
html.dark .text-brand-ink,
html.dark .text-brand-dark { color: #f1f5f9 !important; }
html.dark .text-text-secondary,
html.dark .text-slate-700,
html.dark .text-slate-600,
html.dark .text-slate-500,
html.dark .text-slate-300 { color: #cbd5e1 !important; }
html.dark .text-orange-700,
html.dark .text-orange-200 { color: #fdba74 !important; }
html.dark header { background-color: #0f172a !important; border-bottom-color: rgba(255, 255, 255, 0.1) !important; }
html.dark header nav a { color: #e2e8f0 !important; }
html.dark header nav a:hover { color: #ffffff !important; }
html.dark .border-black\/5,
html.dark .border-black\/10,
html.dark .border-slate-200,
html.dark .border-slate-300 { border-color: rgba(255, 255, 255, 0.1) !important; }
html.dark .ring-black\/5 { --tw-ring-color: rgba(255, 255, 255, 0.1) !important; }
html.dark .shadow-soft { box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); }
html.dark .shadow-note { box-shadow: 0 14px 40px rgba(0, 0, 0, 0.35); }
html.dark code { background-color: #1e293b; color: #e2e8f0; }
/* code inside a <pre> is its own block; don't paint the inline-code box behind it */
html.dark pre code { background-color: transparent; color: inherit; }
.reveal { opacity: 0; transform: translateY(20px); transition: opacity .6s cubic-bezier(.16,1,.3,1), transform .6s cubic-bezier(.16,1,.3,1); }
.reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .reveal { opacity: 1; transform: none; transition: none; } }
</style>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Why MockForge Stayed Multi-Protocol",
"description": "The market is full of HTTP-only mock services. Why we took the harder path: protocols beyond HTTP, spec-driven mocking that generalizes, and what the trade actually costs.",
"datePublished": "2026-05-14T00:00:00.000Z",
"url": "https://mockforge.dev/note-why-multi-protocol.html",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://mockforge.dev/note-why-multi-protocol.html"
},
"author": {
"@type": "Organization",
"name": "MockForge",
"url": "https://mockforge.dev/"
},
"publisher": {
"@type": "Organization",
"name": "MockForge",
"logo": {
"@type": "ImageObject",
"url": "https://mockforge.dev/public/logo-transparent.png"
}
}
}
</script>
<!-- social:begin -->
<link rel="canonical" href="https://mockforge.dev/note-why-multi-protocol.html" />
<meta property="og:title" content="Why MockForge Stayed Multi-Protocol" />
<meta property="og:description" content="The market is full of HTTP-only mock services. Why we took the harder path: protocols beyond HTTP, spec-driven mocking that generalizes, and what the trade actually costs." />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://mockforge.dev/note-why-multi-protocol.html" />
<meta property="og:image" content="https://mockforge.dev/public/og-image.png" />
<meta property="og:site_name" content="MockForge" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Why MockForge Stayed Multi-Protocol" />
<meta name="twitter:description" content="The market is full of HTTP-only mock services. Why we took the harder path: protocols beyond HTTP, spec-driven mocking that generalizes, and what the trade actually costs." />
<meta name="twitter:image" content="https://mockforge.dev/public/og-image.png" />
<!-- social:end -->
</head>
<body class="bg-surface-subtle text-text-primary antialiased font-sans">
<header class="sticky top-0 z-40 backdrop-blur bg-white/90 dark:bg-slate-900 border-b border-black/5 dark:border-white/10">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<a href="/" class="flex items-center gap-3">
<img src="/public/logo-transparent.png" alt="MockForge logo" class="h-8 w-8" />
<span class="font-semibold tracking-tight text-brand-dark dark:text-white">MockForge</span>
</a>
<nav class="hidden md:flex items-center gap-3 lg:gap-4">
<a href="https://docs.mockforge.dev" class="text-text-secondary dark:text-gray-300 hover:text-text-primary dark:hover:text-white text-sm lg:text-base">Docs</a>
<a href="/engineering-notes.html" class="text-text-secondary dark:text-gray-300 hover:text-text-primary dark:hover:text-white text-sm lg:text-base">Notes</a>
<a href="/#features" class="text-text-secondary dark:text-gray-300 hover:text-text-primary dark:hover:text-white text-sm lg:text-base">Features</a>
<a href="/pricing.html" class="text-text-secondary dark:text-gray-300 hover:text-text-primary dark:hover:text-white text-sm lg:text-base">Pricing</a>
<a href="https://github.com/SaaSy-Solutions/mockforge" class="text-text-secondary dark:text-gray-300 hover:text-text-primary dark:hover:text-white text-sm lg:text-base">GitHub</a>
<a href="https://app.mockforge.dev/login" class="text-text-secondary dark:text-gray-300 hover:text-text-primary dark:hover:text-white text-sm lg:text-base">Log In</a>
<a href="https://app.mockforge.dev/register" class="inline-flex items-center gap-1.5 px-2.5 py-1.5 lg:px-3 lg:py-2 rounded-lg text-white hover:opacity-90 shadow-soft whitespace-nowrap text-sm" style="background-color: #D35400;">
<span>Get started free</span>
</a>
<button id="themeToggle" class="p-2 rounded-lg border border-black/10 dark:border-white/20 hover:bg-black/5 dark:hover:bg-white/10" aria-label="Toggle dark mode">
<svg id="sun" class="h-5 w-5 dark:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v2m0 14v2m9-9h-2M5 12H3m15.364 6.364-1.414-1.414M7.05 7.05 5.636 5.636m12.728 0-1.414 1.414M7.05 16.95l-1.414 1.414"/><circle cx="12" cy="12" r="4"/></svg>
<svg id="moon" class="h-5 w-5 hidden dark:block" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"/></svg>
</button>
</nav>
<button id="menuBtn" type="button" class="md:hidden p-2 rounded-lg border border-black/10 dark:border-white/20" aria-label="Open menu" aria-controls="mobileMenu" aria-expanded="false">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="h-6 w-6"><path d="M3 12h18M3 6h18M3 18h18"/></svg>
</button>
</div>
</div>
<div id="mobileMenu" class="md:hidden hidden border-t border-black/5 dark:border-white/10 bg-white dark:bg-brand-dark">
<div class="px-4 py-3 space-y-2">
<a href="https://docs.mockforge.dev" class="block text-text-primary dark:text-white">Docs</a>
<a href="/engineering-notes.html" class="block text-text-primary dark:text-white">Notes</a>
<a href="/#features" class="block text-text-primary dark:text-white">Features</a>
<a href="/pricing.html" class="block text-text-primary dark:text-white">Pricing</a>
<a href="https://github.com/SaaSy-Solutions/mockforge" class="block text-text-primary dark:text-white">GitHub</a>
<a href="https://app.mockforge.dev/login" class="block text-text-primary dark:text-white">Log In</a>
<a href="https://app.mockforge.dev/register" class="inline-flex items-center gap-2 px-3 py-2 rounded-lg text-white hover:opacity-90 shadow-soft" style="background-color: #D35400;">Get started free</a>
</div>
</div>
</header>
<main class="mx-auto max-w-3xl px-4 py-12 sm:px-6 lg:px-8">
<article class="rounded-2xl bg-white p-8 shadow-note ring-1 ring-black/5 sm:p-10">
<a href="/engineering-notes.html" class="text-sm font-medium text-brand-orange hover:opacity-80">← Back to Engineering Notes</a>
<p class="mt-6 text-xs uppercase tracking-[0.24em] text-slate-500">May 14, 2026</p>
<h1 class="mt-3 text-3xl font-semibold tracking-tight text-brand-ink sm:text-4xl">Why MockForge Stayed Multi-Protocol</h1>
<p class="mt-6 text-lg leading-8 text-slate-700">There's a reason almost every commercial mock service is HTTP-only. We picked the harder path anyway. This is the calculation behind it.</p>
<h2 class="mt-10 text-2xl font-semibold tracking-tight text-brand-dark">The shape of the market</h2>
<p class="mt-4 leading-8 text-slate-700">When you look at the mock-services market — WireMock Cloud, Postman Mock Server, Beeceptor, Mocky.io — the pattern is identical. HTTP-only. Request matching by URL/method/header. Saved-example responses. The pricing is per-seat or per-request, and the value proposition is "save your team the time of running a stub server."</p>
<p class="mt-4 leading-8 text-slate-700">It's not a coincidence. HTTP is universal, easy to mock, and the demand is mostly from frontend or API-consumer teams who need a fake backend for development. Building one is a contained problem. Building a Kafka mock or a gRPC streaming mock is a different category of work — you're implementing a protocol, not parsing a request line.</p>
<h2 class="mt-10 text-2xl font-semibold tracking-tight text-brand-dark">Why we did it anyway</h2>
<p class="mt-4 leading-8 text-slate-700">Two reasons.</p>
<p class="mt-4 leading-8 text-slate-700"><strong>Most production systems aren't HTTP-only.</strong> A typical service-oriented architecture today has REST APIs at the edge, gRPC between services, Kafka or NATS for async events, maybe a WebSocket layer for live data, SMTP for transactional email, and a sprinkle of legacy TCP somewhere. Mocking only the HTTP layer means your tests stop at the doorway. The interesting failure modes — partial Kafka delivery, gRPC deadline propagation, WebSocket reconnection storms — happen at the protocols nobody else mocks.</p>
<p class="mt-4 leading-8 text-slate-700"><strong>Spec-driven mocking generalizes better than collection-driven mocking.</strong> If your source of truth is "examples saved in a Postman collection," you're locked into one tool's editor and your mocks drift the moment the spec changes. If your source of truth is the proto file, the OpenAPI spec, or the GraphQL schema, the mock is regenerated from the contract automatically — and it covers every method, not just the ones someone remembered to add an example for.</p>
<h2 class="mt-10 text-2xl font-semibold tracking-tight text-brand-dark">The cost of the choice</h2>
<p class="mt-4 leading-8 text-slate-700">It was real. Each new protocol meant a domain learning curve, a separate listener implementation, and a fresh set of edge cases — gRPC's deadline semantics, Kafka's consumer rebalancing, AMQP's prefetch, MQTT's QoS-2 four-step handshake. The Kafka work alone surprised us; we'd budgeted "a couple weeks for the wire protocol" and ended up shipping it across multiple releases as request types kept showing up that the JVM clients all assumed were trivial. Two years in, the codebase has a separate crate per protocol — <code class="rounded bg-stone-100 px-1.5 py-0.5 font-mono text-sm">mockforge-grpc</code>, <code class="rounded bg-stone-100 px-1.5 py-0.5 font-mono text-sm">mockforge-kafka</code>, <code class="rounded bg-stone-100 px-1.5 py-0.5 font-mono text-sm">mockforge-mqtt</code>, and a few more — and a shared core that handles cross-cutting concerns like chaos injection, data drift, and the admin UI.</p>
<p class="mt-4 leading-8 text-slate-700">The win is that those concerns generalize across protocols. Add latency injection once, it works for HTTP, gRPC, and WebSocket simultaneously. The fault-injection primitives we wrote for the HTTP listener got reused verbatim when we added Kafka. The data-drift simulation that came with the OpenAPI work made gRPC responses feel realistic without separate effort.</p>
<h2 class="mt-10 text-2xl font-semibold tracking-tight text-brand-dark">When HTTP-only is actually enough</h2>
<p class="mt-4 leading-8 text-slate-700">For frontend teams mocking a single REST API, an HTTP-only tool is fine — sometimes better, because the editor experience is more polished and the team already lives in Postman. We've watched users churn back to those tools for that exact reason and we don't think it's a mistake.</p>
<p class="mt-4 leading-8 text-slate-700">The question is whether your test surface stays HTTP-only as the company grows. Most don't. The first time someone needs to mock a Kafka topic for a CI test, the team starts shopping for something new — and the cost of switching mock tools mid-project is high. MockForge is a bet on teams that want to make the decision once.</p>
<h2 class="mt-10 text-2xl font-semibold tracking-tight text-brand-dark">What this implies for the product</h2>
<p class="mt-4 leading-8 text-slate-700">A few things, in case it's useful for anyone making a similar bet:</p>
<ul class="mt-4 space-y-2 leading-8 text-slate-700 list-disc list-inside">
<li><strong>The core has to be protocol-neutral.</strong> Chaos, latency, data drift, the admin UI — these can't live inside individual protocol crates or you'll re-implement them N times.</li>
<li><strong>The spec is the contract.</strong> OpenAPI for HTTP, proto for gRPC, schema for GraphQL. The mock should be derivable from the contract, not from saved examples.</li>
<li><strong>The OSS path matters more, not less.</strong> Multi-protocol mocking is more valuable when self-hosted, because the protocol breadth removes the "but our Kafka clusters are internal" objection.</li>
</ul>
<p class="mt-4 leading-8 text-slate-700">Two years in, the bet looks right. Multi-protocol is the thing nobody else does — and the thing the teams who try us most often need.</p>
<div class="mt-8 flex flex-wrap gap-3 text-sm">
<a href="/pricing.html" class="inline-flex items-center rounded-xl bg-brand-orange px-4 py-2 font-medium text-white hover:opacity-90 transition">View Pricing</a>
<a href="/compare-wiremock.html" class="inline-flex items-center rounded-xl border border-black/10 px-4 py-2 font-medium text-text-primary hover:bg-black/5 dark:hover:bg-white/10 transition">vs WireMock</a>
<a href="/compare-postman-mock.html" class="inline-flex items-center rounded-xl border border-black/10 px-4 py-2 font-medium text-text-primary hover:bg-black/5 dark:hover:bg-white/10 transition">vs Postman Mock</a>
</div>
</article>
</main>
<footer class="py-10 border-t border-black/5 dark:border-white/10">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col sm:flex-row items-center justify-between gap-4">
<div class="flex items-center gap-3">
<img src="/public/logo-transparent.png" alt="MockForge logo" class="h-6 w-6" />
<span class="text-sm text-text-secondary">© <span id="year"></span> MockForge. MIT or Apache-2.0.</span>
</div>
<div class="flex flex-wrap items-center justify-center gap-x-4 gap-y-2 text-sm">
<a href="https://docs.mockforge.dev" class="text-text-secondary hover:text-text-primary">Docs</a>
<a href="/engineering-notes.html" class="text-text-secondary hover:text-text-primary">Notes</a>
<a href="/pricing.html" class="text-text-secondary hover:text-text-primary">Pricing</a>
<a href="/compare-wiremock.html" class="text-text-secondary hover:text-text-primary">Compare: WireMock</a>
<a href="/compare-mockserver.html" class="text-text-secondary hover:text-text-primary">Compare: MockServer</a>
<a href="https://github.com/SaaSy-Solutions/mockforge" class="text-text-secondary hover:text-text-primary">GitHub</a>
<a href="https://www.linkedin.com/company/mockforge" class="text-text-secondary hover:text-text-primary" target="_blank" rel="noopener noreferrer">LinkedIn</a>
<a href="https://app.mockforge.dev/legal/privacy" class="text-text-secondary hover:text-text-primary">Privacy</a>
<a href="https://app.mockforge.dev/legal/terms" class="text-text-secondary hover:text-text-primary">Terms</a>
<a href="https://app.mockforge.dev/legal/dpa" class="text-text-secondary hover:text-text-primary">DPA</a>
<a href="/trust.html" class="text-text-secondary hover:text-text-primary">Trust</a>
<a href="/contact.html" class="text-text-secondary hover:text-text-primary">Contact</a>
</div>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function () {
var toggle = document.getElementById('themeToggle');
var html = document.documentElement;
var menuBtn = document.getElementById('menuBtn');
var mobileMenu = document.getElementById('mobileMenu');
var year = document.getElementById('year');
if (year) year.textContent = new Date().getFullYear();
if (toggle) {
toggle.addEventListener('click', function () {
html.classList.toggle('dark');
var isDark = html.classList.contains('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
}
if (menuBtn && mobileMenu) {
var setMenu = function (open) {
mobileMenu.classList.toggle('hidden', !open);
menuBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
menuBtn.setAttribute('aria-label', open ? 'Close menu' : 'Open menu');
};
menuBtn.addEventListener('click', function () {
setMenu(mobileMenu.classList.contains('hidden'));
});
mobileMenu.querySelectorAll('a').forEach(function (link) {
link.addEventListener('click', function () {
setMenu(false);
});
});
}
// Scroll-reveal for any element with .reveal (IntersectionObserver, no scroll listeners).
var reveals = document.querySelectorAll('.reveal');
if (reveals.length && 'IntersectionObserver' in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add('in');
io.unobserve(entry.target);
}
});
}, { threshold: 0.12 });
reveals.forEach(function (el) { io.observe(el); });
} else {
reveals.forEach(function (el) { el.classList.add('in'); });
}
});
</script>
</body>
</html>