Designed for extensibility: the technical decisions behind DevStation
Hexagonal, tactical DDD, CQRS, internal events and JSON-RPC contracts. Why a set that can sound like overengineering became the central bet — and what the evidence on AI and complex code has to say about it.
A CLI that touches real infrastructure accumulates responsibilities fast. DevStation models clusters, nodes, VMs, images, sizes, vault, provisioning, stations and blueprints — and each of those carries its own rules. The list of features that fit a project like this is, in practice, unbounded. So the question that guided the design was not “how do we ship today’s feature” but “how do we accommodate the next one without compromising what already exists”. The throughline is extensibility.
Hexagonal: the domain isolated from the world
Hexagonal architecture — ports & adapters, proposed by Alistair Cockburn in 2005 — exists precisely to isolate business logic from infrastructure and let it be tested independently. In DevStation, Proxmox, the TUI, the file system and OpenTofu live at the edge; the core sees them only as ports. Architecture tests validate that boundary on every commit: the domain imports no infrastructure, and the inbound side never imports the outbound. The practical effect is direct — adding a provider or swapping the interface does not touch the business rules.
The layout follows Herberto Graça’s Explicit Architecture: an App Core — the Domain wrapped by the Application layer — with ports as the boundary and every dependency pointing inward. The naming here is inbound (driving) and outbound (driven), in place of primary/secondary.
Tactical DDD + CQRS: small contexts that grow without friction
The resources are organized into isolated bounded contexts — cluster, vault, station, service, images, size, blueprint, auth — each with its own aggregates, commands and events. Reads and writes are separated following CQRS. As Martin Fowler describes the pattern, it is about using different models to read and to write; it is worth being precise that CQRS requires neither a separate database nor event sourcing. Here it is single-store CQRS, where the queries project records directly, bypassing the aggregates.
The payoff shows up in maintenance. Extracting images into its own context was replaying a pattern, not inventing one. Renaming definition to size across the whole stack was mechanical because the boundary was explicit. And renaming deploy → install and destroy → uninstall end to end — contract, domain, events, persistence and UI — stayed a disciplined operation, precisely because each context knows what is its own.
Internal events between contexts
DevStation is a single binary, not a distributed system. Even so, the contexts integrate through domain events: a policy reacts to another context’s event and dispatches a command of its own. The project’s context map records this as a rule — cross-context communication is never direct. When a station finishes an install, the cluster context records the service projection on the VM and the vault stores the published secrets, both through policies, with no direct coupling between the contexts.
In a single binary this can sound excessive. The justification is one of timing: separating the contexts from the start, with eventual communication, keeps the door open to extracting a backend later — a shared service, a PaaS — without rewriting rules. It is cheap to do now and expensive to do later.
JSON-RPC contracts: the interface is detachable
The engine exposes a JSON-RPC boundary over stdio, and the contracts (OpenRPC, with code generated from them) are the real edge. The migration to that boundary was deliberately incremental: one context first, with no dual path, and a formal review before expanding to the rest — because mistakes in the envelope and contract modeling are costly to reverse once spread around. The result is that the React Ink TUI is just a client. Another TUI, a desktop app, even a web UI are, from the engine’s point of view, the same contract with a different screen. The contract is what matters; the interface is a detail.
Complexity, AI, and the role of the harness
It is worth acknowledging the trade-off: structure has a cost. There is evidence that AI’s gains shrink on mature projects a team already knows well, and that models lose effectiveness on very large codebases with many files and cross-file dependencies. That cost is real, and it was taken into account.
What changes the outcome is how the structure reaches the agent. The bottleneck those scenarios describe — lack of structure, scattered context, implicit patterns — is exactly what a good harness addresses: rules always loaded, skills on demand, and explicit boundaries the agent follows by construction. Clear structure does not get in the AI’s way; it guides it. With that, the cost of keeping the discipline drops, and the complexity of the practices stops being a burden and becomes a lever — it is what lets you add a context, a provider or a UI with confidence.
Extensibility, in DevStation, is not architectural decoration. It is the decision that makes everything after it cheaper — which is why it came first.
References
- Hexagonal architecture (ports & adapters) — Wikipedia
- Explicit Architecture — Herberto Graça
- CQRS — Martin Fowler
- Experienced developers’ productivity with AI on mature projects — METR (2025)
- LLMs’ difficulty on large codebases — arXiv