ADR-0002: Technology stack¶
- Status: Accepted
- Date: 2026-04-22
Context¶
kafkito is a single-binary Kafka management UI (see ADR-0001). We need a stack that:
- Produces a small, fast, statically linked binary.
- Provides first-class, actively maintained Kafka client support.
- Enables a protobuf-first API layer suitable for both backend and web/mobile clients.
- Ships a modern, maintainable SPA frontend that we can embed via
//go:embed. - Is Cloud Foundry / Kubernetes / container-friendly out of the box (see ADR-0003).
Decision¶
Backend¶
| Concern | Choice | Rationale |
|---|---|---|
| Language | Go 1.26+ | Single-binary, static linking, strong stdlib, mature Kafka client ecosystem. |
| HTTP router | go-chi/chi v5 |
Idiomatic, stdlib-compatible, excellent middleware story. |
| RPC layer | ~~Connect-RPC with buf (protobuf-first)~~ superseded, see Amendments 2026-09-25 and ADR-0005 |
HTTP/1.1, HTTP/2, gRPC-compatible, generated TypeScript client, API contract versioning. |
| Kafka client | twmb/franz-go + kadm + kmsg + sr |
Best-of-breed pure-Go Kafka client; supports KIPs promptly; wire-protocol-first. |
| Scripting | dop251/goja |
Sandboxed JS engine for message-filter DSL. |
| Config | knadh/koanf |
YAML + env + VCAP_SERVICES layering. |
| Logging | log/slog (stdlib) with optional go.uber.org/zap handler later if perf demands it |
Structured JSON logs to stdout, Cloud Foundry loggregator-friendly. |
| Observability | OpenTelemetry + Prometheus exporter | Industry standard. |
Frontend¶
| Concern | Choice | Rationale |
|---|---|---|
| Runtime | Bun | Fast install/build, first-class TypeScript, matches modern React tooling. |
| Framework | React 19 | Mainstream, large talent pool, stable ecosystem. |
| Build | Vite | Fast dev server, lean production bundles. |
| Routing | TanStack Router | Type-safe, data-loading built in, no server-side rendering required. |
| UI kit | ~~shadcn/ui~~ (superseded, see Amendments 2026-09-26) + Tailwind CSS | Copy-in component library (no vendor lock-in), accessible defaults. |
| State / data | TanStack Query + ~~Connect-RPC generated client~~ (superseded, see Amendments 2026-09-25 and ADR-0005) | Declarative data-fetching; protobuf types end-to-end. |
Distribution¶
- Single Go binary with the SPA embedded via
//go:embed. - Distroless multi-arch container image (
gcr.io/distroless/static-debian12,linux/amd64+linux/arm64).
Consequences¶
Positive
- One deployment artifact. No JVM, no sidecar node_modules.
- Protobuf-first API ensures end-to-end type safety and easy contract evolution.
Negative
- Bun is less mainstream in some enterprise environments than Node.js. We mitigate by producing plain static assets — the runtime is only a build-time dependency.
- Connect-RPC is newer than plain REST + OpenAPI. Team members need to learn
bufand the Connect generator.
Alternatives considered¶
- Echo / Gin instead of Chi — rejected, Chi has the cleanest middleware story and is stdlib-compatible.
- Sarama or IBM/sarama instead of franz-go — rejected, franz-go is more modern, has better performance and KIP coverage.
- Next.js or Remix instead of Vite SPA — rejected, we do not need SSR; a SPA simplifies the
//go:embeddistribution model. - gRPC-Web / grpc-gateway instead of Connect-RPC — rejected, Connect-RPC offers a simpler protocol with identical tooling benefits.
Amendments¶
2026-09-25¶
- Logging is
log/slogonly.go.uber.org/zapis not used and no zap handler is planned. - Observability: OpenTelemetry and Prometheus are not implemented and are
not planned until there is a consumer for them. Production runs on SAP BTP
Cloud Foundry, where logs are viewed via
cf logs. - RPC layer: superseded by ADR-0005.
Connect-RPC and
bufare removed; the HTTP contract is the OpenAPI 3.1 spec atapi/openapi.yaml, and the frontend uses TanStack Query with TypeScript types generated from that spec (this also replaces the "Connect-RPC generated client" in the State / data row). - Go package layout: application packages live under
internal/(no public Go API);pkg/is reserved for code intended for external import.
2026-09-26¶
- UI kit: shadcn/ui is not used. The UI is built from kafkito's own
primitives in
frontend/src/components/ui/, styled with Tailwind CSS v4 and the design tokens infrontend/src/index.css(this replaces the "shadcn/ui" part of the UI kit row). Radix UI headless primitives (@radix-ui/react-*, no shadcn) may be added for behaviour that is hard to get right by hand, such as focus management and collision-aware positioning; each one is wrapped incomponents/ui/, and kafkito paints the whole visual layer. Toasts usesonner, iconslucide-react. The rules and the component inventory are in DESIGN_GUIDELINES.md (§ 1 and § 6). - Kafka client: the
srpackage is not used; Schema Registry calls go through a small HTTP client ininternal/kafka/schema_registry.go.