Replace cloud-based LLM providers (Anthropic/OpenAI) with a local Ollama sidecar running lightweight quantized models (qwen2.5-coder:1.5b as primary, phi3:mini as fallback). All generation stays under 60s on CPU, data never leaves the network, and there are zero API costs. Updated: architecture diagram, component description, technology choices, project layout, docker-compose, env vars, and consequences sections.
548 lines
23 KiB
Markdown
548 lines
23 KiB
Markdown
# ADR-001: Architecture of stub-service — Go-based HTTP Stub/Mock Service
|
||
|
||
**Status:** Proposed
|
||
**Date:** 2026-05-07
|
||
**Authors:** backend-agent
|
||
|
||
---
|
||
|
||
## Context
|
||
|
||
Our development and QA workflows require a service-virtualization tool that can stand in for real HTTP services during integration testing, local development, and demo environments. [Mountebank](http://www.mbtest.org/) is the de-facto open-source option, but it presents several pain points for our team:
|
||
|
||
1. **Node.js runtime** — adds a runtime dependency our Go/Java-centric infrastructure doesn't otherwise need.
|
||
2. **No built-in Web UI** — managing imposters requires curl/Postman or third-party UIs.
|
||
3. **No AI-assisted authoring** — creating complex stubs is manual and error-prone.
|
||
4. **Limited extensibility** — behaviours and predicates are plugin-based in JavaScript, which is awkward to extend from our stack.
|
||
|
||
We need a lightweight, single-binary alternative written in Go that keeps Mountebank's core concepts (imposters, stubs, predicates, responses, proxies) while adding a web UI and LLM-powered stub generation.
|
||
|
||
---
|
||
|
||
## Decision
|
||
|
||
Build **stub-service** — a Go application that provides:
|
||
|
||
- An HTTP stub server with rich request matching
|
||
- A REST management API (CRUD for imposters/stubs)
|
||
- A web UI for visual management and real-time request logging
|
||
- An LLM adapter for natural-language stub authoring
|
||
- Proxy mode with record/replay capability
|
||
|
||
### High-Level Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ stub-service process │
|
||
│ │
|
||
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐ │
|
||
│ │ Management │ │ Web UI │ │ Stub Listeners │ │
|
||
│ │ REST API │ │ (embedded) │ │ (dynamic ports) │ │
|
||
│ │ :8080/api/v1 │ │ :8080/ui │ │ :N per imposter │ │
|
||
│ └──────┬───────┘ └──────┬───────┘ └───────────┬────────────┘ │
|
||
│ │ │ │ │
|
||
│ ▼ ▼ ▼ │
|
||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||
│ │ Core Engine │ │
|
||
│ │ ┌─────────────┐ ┌──────────┐ ┌───────────┐ ┌────────┐ │ │
|
||
│ │ │ Imposter │ │ Matcher │ │ Response │ │ Proxy │ │ │
|
||
│ │ │ Manager │ │ Engine │ │ Renderer │ │ Module │ │ │
|
||
│ │ └─────────────┘ └──────────┘ └───────────┘ └────────┘ │ │
|
||
│ └──────────────────────────┬──────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ┌───────────────────┼────────────────────┐ │
|
||
│ ▼ ▼ ▼ │
|
||
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
|
||
│ │ Stub Store │ │ Request Log │ │ LLM Adapter │ │
|
||
│ │ (SQLite / │ │ (ring buf) │ │ (local CPU / │ │
|
||
│ │ bbolt) │ │ │ │ Ollama) │ │
|
||
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Component Breakdown
|
||
|
||
#### 1. Management REST API (`internal/api`)
|
||
|
||
The control plane. All imposter/stub CRUD operations happen here.
|
||
|
||
- Listens on a single admin port (default `:8080`), path prefix `/api/v1`
|
||
- JSON request/response bodies
|
||
- Endpoints (see API Design section below)
|
||
- Provides SSE endpoint for real-time request log streaming
|
||
|
||
#### 2. Web UI (`internal/ui`)
|
||
|
||
An embedded single-page application served from the same admin port under `/ui`.
|
||
|
||
- **Technology:** [Templ](https://templ.guide/) templates + [htmx](https://htmx.org/) + minimal CSS (e.g., Pico CSS or similar classless framework)
|
||
- **Rationale:** No JavaScript build step, no npm dependency. Templ compiles to Go, htmx provides dynamic behavior via HTML attributes. The result is a server-rendered UI that feels interactive without a JS framework.
|
||
- **Pages:**
|
||
- Dashboard — list of all imposters with status, port, hit counts
|
||
- Imposter detail — list of stubs, predicates, responses, recent requests
|
||
- Stub editor — form-based creation/editing of stubs with predicate/response builders
|
||
- Request log — real-time log of incoming requests (SSE-powered)
|
||
- LLM assistant — text input for natural-language stub creation
|
||
|
||
#### 3. Stub Listeners (`internal/server`)
|
||
|
||
Each imposter binds to a dedicated port and handles incoming traffic.
|
||
|
||
- Dynamically started/stopped when imposters are created/deleted
|
||
- Each listener runs an HTTP handler that delegates to the Matcher Engine
|
||
- TLS support via optional per-imposter cert/key configuration
|
||
|
||
#### 4. Core Engine (`internal/engine`)
|
||
|
||
The domain logic of the application.
|
||
|
||
- **Imposter Manager** — lifecycle management of imposters (create, update, delete, start, stop). Coordinates listener creation and stub store persistence.
|
||
- **Matcher Engine** — evaluates incoming requests against stub predicates. Returns the first matching stub or a configurable default response. Supports predicate composition (`and`, `or`, `not`).
|
||
- **Response Renderer** — produces the HTTP response from a stub definition. Handles static bodies, Go template rendering (with request data as context), latency injection, and header manipulation.
|
||
- **Proxy Module** — forwards requests to a real upstream, optionally records the response as a new stub for future replay.
|
||
|
||
#### 5. Stub Store (`internal/store`)
|
||
|
||
Persistence layer for imposters, stubs, and request logs.
|
||
|
||
- **Primary:** SQLite via [modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite) (pure Go, no CGO) or [bbolt](https://github.com/etcd-io/bbolt) for a simpler key-value approach.
|
||
- **Decision:** SQLite preferred — it supports rich queries for filtering stubs and request logs, and the schema maps naturally to the relational data model.
|
||
- Migrations managed via embedded SQL files (`embed` package).
|
||
- Backup: the SQLite file can be copied while the service runs (WAL mode).
|
||
- Optional file-based export/import in JSON format for portability.
|
||
|
||
#### 6. LLM Adapter (`internal/llm`)
|
||
|
||
Translates natural-language descriptions into stub configurations using a **local, CPU-only** LLM. No external API calls — everything runs on-premise, keeping data private and eliminating network latency / cloud costs.
|
||
|
||
- **Interface:** A `StubGenerator` interface that takes a text prompt and returns a structured stub definition.
|
||
- **Runtime:** [Ollama](https://ollama.com/) runs as a sidecar container (or host-installed binary). It serves an OpenAI-compatible HTTP API on `localhost:11434` and manages model downloads, quantization, and inference. No GPU required.
|
||
- **Model selection (CPU, ≤ 60 s per generation):**
|
||
- **Primary: `qwen2.5-coder:1.5b-instruct-q4_K_M`** (~1 GB RAM) — strong code/JSON generation for its size; comfortably produces a stub JSON within 15–40 s on a 4-core CPU.
|
||
- **Fallback: `phi3:mini-4k-instruct-q4_K_M`** (3.8 B, ~2.3 GB RAM) — higher quality output when the host has ≥ 8 GB RAM; generation stays under 60 s on 4+ cores.
|
||
- The model is configurable via `LLM_MODEL` env var. Any Ollama-compatible GGUF model can be substituted.
|
||
- **Prompt strategy:** System prompt contains the stub JSON schema and 2-3 few-shot examples. User message is the natural-language description. Response is constrained to JSON via Ollama's `format: "json"` parameter. The result is validated against the stub schema before use.
|
||
- **Performance budget:** Generation must complete within **60 seconds** wall-clock time. The adapter enforces a context-based timeout; if the model exceeds it, the request fails with a clear error rather than hanging. Recommended: keep prompts under 512 tokens input and request ≤ 1024 tokens output.
|
||
- **Flow:**
|
||
1. User types: *"Return 200 with a JSON user profile when GET /api/users/123 is called"*
|
||
2. LLM adapter sends the prompt to the local Ollama API (`POST /api/generate`) with schema context and `format: "json"`
|
||
3. Response is parsed and validated against the stub schema
|
||
4. If valid, the stub is presented to the user for confirmation in the Web UI
|
||
5. On confirmation, the stub is saved via the Management API
|
||
- **Graceful degradation:** If Ollama is unavailable or the model is not pulled, the `/generate` endpoint returns `503 Service Unavailable` with a diagnostic message. All other stub-service functionality remains unaffected — the LLM feature is strictly optional.
|
||
|
||
#### 7. Request Log (`internal/log`)
|
||
|
||
In-memory ring buffer of recent requests per imposter, with optional persistence to SQLite.
|
||
|
||
- Stores: timestamp, method, path, headers, body (truncated), matched stub ID, response status, latency
|
||
- Queryable via Management API (filter by imposter, time range, match status)
|
||
- Streamed to the Web UI via SSE
|
||
|
||
---
|
||
|
||
### Technology Choices
|
||
|
||
| Concern | Choice | Rationale |
|
||
|---|---|---|
|
||
| Language | Go 1.23+ | Single binary, excellent HTTP stdlib, team expertise |
|
||
| HTTP framework | `net/http` (stdlib) + [chi](https://github.com/go-chi/chi) router | Minimal dependency, chi adds route params and middleware without abstracting stdlib |
|
||
| Web UI | Templ + htmx + Pico CSS | No JS build toolchain, server-rendered, interactive via htmx |
|
||
| Database | SQLite (modernc.org/sqlite) | Pure Go, zero CGO, rich SQL queries, single file |
|
||
| LLM runtime | [Ollama](https://ollama.com/) sidecar (local CPU) | No cloud dependency, data stays on-premise, zero API cost; `qwen2.5-coder:1.5b` generates stubs in < 60 s on CPU |
|
||
| Config | Environment variables + optional YAML file | 12-factor, simple for Docker |
|
||
| Logging | `log/slog` (stdlib) | Structured logging, zero deps |
|
||
| Testing | `testing` + [testify](https://github.com/stretchr/testify) | Assertions, standard tooling |
|
||
| TLS | `crypto/tls` (stdlib) | Per-imposter certs for HTTPS stubs |
|
||
|
||
---
|
||
|
||
### API Design Overview
|
||
|
||
Base URL: `http://localhost:8080/api/v1`
|
||
|
||
#### Imposters
|
||
|
||
| Method | Path | Description |
|
||
|---|---|---|
|
||
| `GET` | `/imposters` | List all imposters |
|
||
| `POST` | `/imposters` | Create a new imposter |
|
||
| `GET` | `/imposters/{id}` | Get imposter details |
|
||
| `PUT` | `/imposters/{id}` | Update imposter |
|
||
| `DELETE` | `/imposters/{id}` | Delete imposter and stop listener |
|
||
| `DELETE` | `/imposters` | Delete all imposters |
|
||
| `POST` | `/imposters/{id}/start` | Start the stub listener |
|
||
| `POST` | `/imposters/{id}/stop` | Stop the stub listener |
|
||
|
||
#### Stubs (nested under imposter)
|
||
|
||
| Method | Path | Description |
|
||
|---|---|---|
|
||
| `GET` | `/imposters/{id}/stubs` | List stubs for an imposter |
|
||
| `POST` | `/imposters/{id}/stubs` | Add a stub |
|
||
| `PUT` | `/imposters/{id}/stubs/{stubId}` | Update a stub |
|
||
| `DELETE` | `/imposters/{id}/stubs/{stubId}` | Delete a stub |
|
||
|
||
#### Request Log
|
||
|
||
| Method | Path | Description |
|
||
|---|---|---|
|
||
| `GET` | `/imposters/{id}/requests` | Get request log (paginated) |
|
||
| `GET` | `/imposters/{id}/requests/stream` | SSE stream of live requests |
|
||
| `DELETE` | `/imposters/{id}/requests` | Clear request log |
|
||
|
||
#### LLM
|
||
|
||
| Method | Path | Description |
|
||
|---|---|---|
|
||
| `POST` | `/generate` | Generate stub config from natural-language prompt |
|
||
|
||
#### System
|
||
|
||
| Method | Path | Description |
|
||
|---|---|---|
|
||
| `GET` | `/health` | Health check |
|
||
| `GET` | `/config` | Current configuration (redacted) |
|
||
| `POST` | `/import` | Import imposters from JSON file |
|
||
| `GET` | `/export` | Export all imposters as JSON |
|
||
|
||
---
|
||
|
||
### Data Model
|
||
|
||
#### Imposter
|
||
|
||
```json
|
||
{
|
||
"id": "uuid",
|
||
"name": "User Service Mock",
|
||
"protocol": "http",
|
||
"port": 4545,
|
||
"tls": {
|
||
"cert": "...",
|
||
"key": "..."
|
||
},
|
||
"default_response": {
|
||
"status": 404,
|
||
"headers": {"Content-Type": "application/json"},
|
||
"body": "{\"error\": \"no matching stub\"}"
|
||
},
|
||
"stubs": [ ... ],
|
||
"state": "running",
|
||
"created_at": "2026-05-07T10:00:00Z",
|
||
"request_count": 142
|
||
}
|
||
```
|
||
|
||
#### Stub
|
||
|
||
```json
|
||
{
|
||
"id": "uuid",
|
||
"imposter_id": "uuid",
|
||
"priority": 1,
|
||
"predicates": [ ... ],
|
||
"response": { ... },
|
||
"hit_count": 37,
|
||
"created_at": "2026-05-07T10:00:00Z"
|
||
}
|
||
```
|
||
|
||
#### Predicate
|
||
|
||
Predicates are composable. Each predicate has a `type` and type-specific fields:
|
||
|
||
```json
|
||
{
|
||
"type": "equals",
|
||
"field": "path",
|
||
"value": "/api/users/123"
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"type": "regex",
|
||
"field": "path",
|
||
"value": "^/api/users/\\d+$"
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"type": "jsonpath",
|
||
"expression": "$.order.items[?(@.quantity > 5)]",
|
||
"exists": true
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"type": "and",
|
||
"predicates": [
|
||
{"type": "equals", "field": "method", "value": "POST"},
|
||
{"type": "contains", "field": "body", "value": "\"action\":\"create\""}
|
||
]
|
||
}
|
||
```
|
||
|
||
Supported predicate types:
|
||
- `equals` — exact match on method, path, header, query param, or body
|
||
- `contains` — substring match
|
||
- `regex` — regular expression match
|
||
- `jsonpath` — JSONPath expression evaluation
|
||
- `xpath` — XPath expression evaluation (for XML bodies)
|
||
- `and` — all child predicates must match
|
||
- `or` — at least one child predicate must match
|
||
- `not` — invert a child predicate
|
||
|
||
Fields available for matching: `method`, `path`, `query.<param>`, `header.<name>`, `body`
|
||
|
||
#### Response
|
||
|
||
```json
|
||
{
|
||
"status": 200,
|
||
"headers": {
|
||
"Content-Type": "application/json"
|
||
},
|
||
"body": "{\"id\": 123, \"name\": \"Alice\"}",
|
||
"template": false,
|
||
"latency_ms": 0,
|
||
"proxy": null
|
||
}
|
||
```
|
||
|
||
When `template: true`, the `body` is rendered as a Go template with the request context:
|
||
|
||
```
|
||
{
|
||
"echo": "You requested {{.Request.Path}} with method {{.Request.Method}}"
|
||
}
|
||
```
|
||
|
||
#### Proxy Response
|
||
|
||
```json
|
||
{
|
||
"proxy": {
|
||
"to": "https://real-service.example.com",
|
||
"mode": "record",
|
||
"add_wait_behavior": true
|
||
}
|
||
}
|
||
```
|
||
|
||
Proxy modes:
|
||
- `passthrough` — forward and return, do not record
|
||
- `record` — forward, return, and save as a new stub for future replay
|
||
- `replay` — use previously recorded response if available, otherwise forward
|
||
|
||
---
|
||
|
||
### SQLite Schema (initial)
|
||
|
||
```sql
|
||
CREATE TABLE imposters (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
protocol TEXT NOT NULL DEFAULT 'http',
|
||
port INTEGER NOT NULL UNIQUE,
|
||
tls_cert TEXT,
|
||
tls_key TEXT,
|
||
default_response_json TEXT,
|
||
state TEXT NOT NULL DEFAULT 'stopped',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL
|
||
);
|
||
|
||
CREATE TABLE stubs (
|
||
id TEXT PRIMARY KEY,
|
||
imposter_id TEXT NOT NULL REFERENCES imposters(id) ON DELETE CASCADE,
|
||
priority INTEGER NOT NULL DEFAULT 0,
|
||
predicates_json TEXT NOT NULL,
|
||
response_json TEXT NOT NULL,
|
||
hit_count INTEGER NOT NULL DEFAULT 0,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL
|
||
);
|
||
|
||
CREATE INDEX idx_stubs_imposter ON stubs(imposter_id, priority);
|
||
|
||
CREATE TABLE request_log (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
imposter_id TEXT NOT NULL REFERENCES imposters(id) ON DELETE CASCADE,
|
||
timestamp TEXT NOT NULL,
|
||
method TEXT NOT NULL,
|
||
path TEXT NOT NULL,
|
||
headers_json TEXT,
|
||
body TEXT,
|
||
matched_stub_id TEXT,
|
||
response_status INTEGER,
|
||
latency_ms INTEGER,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
|
||
CREATE INDEX idx_request_log_imposter_ts ON request_log(imposter_id, timestamp DESC);
|
||
```
|
||
|
||
---
|
||
|
||
### Project Layout
|
||
|
||
```
|
||
stub-service/
|
||
├── cmd/
|
||
│ └── stub-service/
|
||
│ └── main.go # Entry point, config loading, wiring
|
||
├── internal/
|
||
│ ├── api/ # Management REST API handlers
|
||
│ │ ├── handler.go
|
||
│ │ ├── imposter.go
|
||
│ │ ├── stub.go
|
||
│ │ ├── requestlog.go
|
||
│ │ ├── generate.go # LLM generation endpoint
|
||
│ │ └── middleware.go
|
||
│ ├── engine/ # Core domain logic
|
||
│ │ ├── imposter.go # Imposter manager
|
||
│ │ ├── matcher.go # Predicate matching engine
|
||
│ │ ├── renderer.go # Response rendering (static, template)
|
||
│ │ └── proxy.go # Proxy/record/replay
|
||
│ ├── model/ # Domain types
|
||
│ │ ├── imposter.go
|
||
│ │ ├── stub.go
|
||
│ │ ├── predicate.go
|
||
│ │ └── response.go
|
||
│ ├── store/ # Persistence
|
||
│ │ ├── store.go # Store interface
|
||
│ │ ├── sqlite.go # SQLite implementation
|
||
│ │ └── migrations/ # Embedded SQL migrations
|
||
│ │ └── 001_init.sql
|
||
│ ├── server/ # Dynamic stub HTTP listeners
|
||
│ │ └── listener.go
|
||
│ ├── llm/ # LLM integration (local, CPU-only)
|
||
│ │ ├── generator.go # StubGenerator interface + timeout logic
|
||
│ │ └── ollama.go # Ollama HTTP client implementation
|
||
│ ├── ui/ # Web UI
|
||
│ │ ├── templates/ # Templ files
|
||
│ │ ├── static/ # CSS, minimal JS (htmx)
|
||
│ │ └── handler.go # UI route handlers
|
||
│ └── config/ # Configuration loading
|
||
│ └── config.go
|
||
├── docs/
|
||
│ └── adr/
|
||
│ └── adr-001.md # This document
|
||
├── go.mod
|
||
├── go.sum
|
||
├── Dockerfile
|
||
├── docker-compose.yaml
|
||
├── Makefile
|
||
└── README.md
|
||
```
|
||
|
||
---
|
||
|
||
### Deployment Model
|
||
|
||
**Docker (primary):**
|
||
|
||
```dockerfile
|
||
# Build stage
|
||
FROM golang:1.23-alpine AS build
|
||
WORKDIR /src
|
||
COPY go.mod go.sum ./
|
||
RUN go mod download
|
||
COPY . .
|
||
RUN CGO_ENABLED=0 go build -o /stub-service ./cmd/stub-service
|
||
|
||
# Runtime stage
|
||
FROM alpine:3.20
|
||
RUN apk add --no-cache ca-certificates
|
||
COPY --from=build /stub-service /usr/local/bin/stub-service
|
||
EXPOSE 8080
|
||
VOLUME /data
|
||
ENTRYPOINT ["stub-service"]
|
||
```
|
||
|
||
**docker-compose.yaml (local dev):**
|
||
|
||
```yaml
|
||
services:
|
||
stub-service:
|
||
build: .
|
||
ports:
|
||
- "8080:8080" # Admin API + Web UI
|
||
- "4545-4600:4545-4600" # Range for stub listeners
|
||
volumes:
|
||
- stub-data:/data
|
||
environment:
|
||
STUB_ADMIN_PORT: "8080"
|
||
STUB_DATA_DIR: "/data"
|
||
STUB_LOG_LEVEL: "debug"
|
||
LLM_OLLAMA_URL: "http://ollama:11434"
|
||
LLM_MODEL: "qwen2.5-coder:1.5b-instruct-q4_K_M"
|
||
LLM_TIMEOUT: "60s"
|
||
depends_on:
|
||
ollama:
|
||
condition: service_started
|
||
|
||
ollama:
|
||
image: ollama/ollama:latest
|
||
volumes:
|
||
- ollama-models:/root/.ollama
|
||
# CPU-only — no GPU runtime required
|
||
# Pull the model on first start: docker compose exec ollama ollama pull qwen2.5-coder:1.5b-instruct-q4_K_M
|
||
|
||
volumes:
|
||
stub-data:
|
||
ollama-models:
|
||
```
|
||
|
||
**Configuration (environment variables):**
|
||
|
||
| Variable | Default | Description |
|
||
|---|---|---|
|
||
| `STUB_ADMIN_PORT` | `8080` | Admin API and Web UI port |
|
||
| `STUB_DATA_DIR` | `./data` | SQLite database directory |
|
||
| `STUB_LOG_LEVEL` | `info` | Log level (debug, info, warn, error) |
|
||
| `STUB_PORT_RANGE_START` | `4545` | Start of port range for stub listeners |
|
||
| `STUB_PORT_RANGE_END` | `4600` | End of port range for stub listeners |
|
||
| `LLM_OLLAMA_URL` | `http://localhost:11434` | Ollama API base URL |
|
||
| `LLM_MODEL` | `qwen2.5-coder:1.5b-instruct-q4_K_M` | Ollama model tag for stub generation |
|
||
| `LLM_TIMEOUT` | `60s` | Maximum wall-clock time for a single generation request |
|
||
|
||
---
|
||
|
||
## Consequences
|
||
|
||
### Positive
|
||
|
||
- **Single binary** — no runtime dependencies beyond the OS; trivial to deploy.
|
||
- **Embedded Web UI** — no separate frontend build/deploy pipeline; Templ + htmx keeps it all in Go.
|
||
- **SQLite** — zero operational overhead, no external database to manage, easy backup (copy a file).
|
||
- **Local LLM integration** — significantly lowers the barrier for creating complex stubs, especially for QA engineers unfamiliar with JSONPath/regex predicates. Runs entirely on-premise (CPU-only, no GPU), so sensitive request/response data never leaves the network and there are zero API costs.
|
||
- **Mountebank-compatible concepts** — teams familiar with Mountebank can transfer their mental model (imposters, stubs, predicates).
|
||
- **Port-per-imposter isolation** — each mock service gets its own port, matching how real services are addressed.
|
||
|
||
### Negative
|
||
|
||
- **Port range management** — dynamic port allocation requires firewall/Docker port-range configuration.
|
||
- **SQLite single-writer** — under very heavy concurrent management API load, SQLite's single-writer lock could be a bottleneck. Acceptable for a testing/dev tool.
|
||
- **LLM resource usage** — the Ollama sidecar with a 1.5 B model requires ~1 GB RAM and consumes noticeable CPU during generation (~15–40 s on 4 cores). Larger fallback models (3.8 B) need ~2.3 GB RAM. The feature is optional — the service works fully without Ollama running.
|
||
- **No multi-protocol support initially** — only HTTP/HTTPS. TCP/SMTP stubs (which Mountebank supports) are deferred to a future iteration.
|
||
|
||
### Risks
|
||
|
||
- **Templ maturity** — Templ is relatively new. Mitigation: the UI is simple enough that switching to `html/template` is feasible with moderate effort.
|
||
- **JSONPath/XPath complexity** — Go's ecosystem for JSONPath and XPath is less mature than Node.js. Mitigation: use well-maintained libraries ([github.com/PaesslerAG/jsonpath](https://github.com/PaesslerAG/jsonpath), [github.com/antchfx/xmlquery](https://github.com/antchfx/xmlquery)) and test thoroughly.
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
- [Mountebank](http://www.mbtest.org/) — the inspiration
|
||
- [chi router](https://github.com/go-chi/chi)
|
||
- [Templ](https://templ.guide/) — Go HTML templating
|
||
- [htmx](https://htmx.org/) — HTML-driven interactivity
|
||
- [modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite) — pure Go SQLite
|