AI News HubLIVE
In-site rewrite6 min read

Show HN: Itara – Distributed system topology as an explicit, executable layer

Itara is an open-source project that makes distributed system topology explicit by separating it into a dedicated configuration layer. It uses a wiring agent that reads a config file at startup, resolves all connections, and wires components together before the application runs at full speed. The tooling validates topologies before deployment and provides observability through four key events. It supports incremental adoption and cross-language interop (Java, Rust, and more planned).

SourceHacker News AIAuthor: gkiss0001

Uh oh!

There was an error while loading. Please reload this page.

Notifications You must be signed in to change notification settings

Fork 1

Star 10

BranchesTags

Open more actions menu

Folders and files

NameName

Last commit message

Last commit date

Latest commit

History

64 Commits

64 Commits

.github

.github

demo

demo

docs

docs

java

java

rust

rust

spec

spec

.gitattributes

.gitattributes

.gitignore

.gitignore

CONTRIBUTING.md

CONTRIBUTING.md

GLOSSARY.md

GLOSSARY.md

LICENSE

LICENSE

README.md

README.md

Repository files navigation

Itara makes distributed system topology explicit, declared, verifiable, and executable. Topology — which components exist, how they connect, what transport they use, how failures are handled — lives in a single wiring config, not scattered across codebases as a consequence of implementation decisions made years apart.

This is achieved through two co-equal parts: a language-specific wiring agent that reads the wiring config before the application starts, resolves all connections once, wires the components together, and then steps aside — the application runs at full speed with no intermediary, no proxy in the call path, no decisions made at call time — and a tooling ecosystem that makes the topology layer safe and manageable. The tooling validates configurations before deployment, catches mismatches and incompatibilities at authoring time, visualises the topology as a graph, and guides engineers through changes. Incorrect topologies cannot be deployed silently. The layer Itara introduces is the layer the tooling understands completely.

Change how your components communicate — collocated direct calls, HTTP, message queues — by changing a config file. No code changes. No redeployment ceremony. No migration scripts. Reference implementations of the wiring agent exist in Java and Rust. More languages are planned. The tooling ecosystem has begun: itara-cli ships two commands — itara inspect to visualise a topology and itara verify to catch configuration errors before deployment. The visual editor and controller are planned.

Contents

The problem

The idea

Gradual adoption

The proof of concept

Language support

Observability

Event-driven topology

Failure semantics

Repository structure

The demo

Running the Rust demo

The CLI

What Itara is not

Current state

The problem

The pain

Changing how components in a distributed system communicate is painful and dangerous — it means changing code on both sides, finding all the clients that call a particular service, identifying the relevant code owners and teams, making a step-by-step plan, and coordinating the effort across teams. Even in the most well-designed systems, it is a very significant and time-consuming effort. Most of us have been there: mark an API deprecated, replace it everywhere you know it's called, and then spend a week watching the monitoring — hoping nothing you did not know about would call it. When nothing does, you delete it and move on, never quite sure whether you were thorough enough or just got lucky. There are ways to mitigate the risk: parallel runs, rollback plans, coordinated change freezes, additional observability during migration, the strangler fig pattern — we do them routinely. But none of them tackle the real issue. They only mitigate the consequences.

The root cause

The topology of a distributed system — which components exist, how they communicate, where they run — is not a first-class concern. It lives in HTTP clients, retry policies, timeout configurations, service discovery calls, Kafka consumer group registrations, schema registry entries: scattered across every service, encoded as a consequence of implementation decisions made years apart by people who may no longer be around. There is no place you can go to ask what the topology is right now, what depends on what, or what breaks if you move something. The blast radius of any topology change is very hard to determine, often impossible, until you are already committed to it.

See VISION.md for the full analysis and MANIFESTO.md for the principles that follow from it.

The idea

We've solved this pattern before — with configuration, with infrastructure as code. Distributed system topology is next.

Topology belongs in a dedicated, separately declared layer — not distributed across the codebase as a consequence of implementation decisions. Itara elevates and concentrates it there.

A component declares its contract — what it accepts and what it returns. It does not declare how it is called — that is declared in the wiring config. The wiring config is not a deployment detail. It is the authoritative representation of the system's topology: which components exist, how they connect, what transport they use, how failures are handled, expressed as a directed graph.

The Itara wiring agent reads the config at startup, prepares all connections exactly as declared, and then steps aside. No decisions are made at call time. The topology is fully determined before the first request arrives.

Every artifact the system must reason about — components, API contracts, transport implementations, serializers, observers, event contracts — declares itself via a .itara metadata file. This is the symbol table the agent and tooling work from: identifiers, versions, declared contracts, capability declarations, and idempotency flags all live there. The tooling reads these files to validate compatibility and catch misconfigurations before deployment.

This is not about hiding the network or making remote calls look like local ones. Topology is explicit, not hidden — declared in the wiring config, validated by tooling before deployment, and visible to everyone. See the FAQ for how Itara compares to service meshes, sidecars, and transparency-oriented systems. See SPEC.md for the formal specification.

The wiring config for a two-component system looks like this:

One master config describes the entire topology.

Each process is told which node it represents at startup.

Change this file. Restart. Topology changes.

nodes:

  • id: gatewayNode

component: gateway

  • id: calculatorNode

component: calculator

connections:

  • from: gatewayNode

to: calculatorNode transport: type: direct # or: http, kafka — code does not change

Gradual adoption

Itara is designed to be adopted incrementally — one component at a time, one service boundary at a time, without touching the rest of the system.

A component needs three things to participate:

An API artifact — a plain interface declaring the contract

An activator — a single factory method that receives the registry and returns the implementation

A wiring config entry — declaring the node and its connections

The only Itara dependency a component ever introduces is the core library — itara-common in Java, itara-core in Rust. The business logic implementation itself requires no Itara imports at all. Only the activator, the single composition root of a component, touches the registry. Spring Boot works alongside Itara without any adapter — they operate at different layers and do not interfere with each other.

With partial adoption, even a single service boundary under Itara gives you structural observability on that connection, explicit topology declaration for those components, and the ability to switch transport without code changes.

With full adoption, the complete topology picture becomes available. The wiring config describes the entire system. The CLI derives all deployment groups, validates all connections, and detects compatibility issues before deployment. The blast radius of any topology change becomes determinable rather than estimated.

The full benefits require the full picture. The value of partial adoption is real and immediate.

The proof of concept

Two components — a gateway and a calculator — implemented in both Java and Rust. Switch between direct and HTTP topology by changing one line in the wiring config. The component code does not change.

Direct topology — single process, zero network overhead:

[Gateway] add(3, 4) = 7

HTTP topology — separate processes, transport cost visible in the traces:

[Itara/HTTP] -> add on calculator at localhost:8081 [Gateway] add(3, 4) = 7

This works across languages. A Java gateway calling a Rust calculator produces a single distributed trace in Kibana — the same trace ID, correct parent-child relationships, across two runtimes. The code in either component does not change.

The order processing demo below shows the full picture: four Java components, one Rust service, three topologies, and the traces that make each topology decision measurable.

Language support

Language Status Notes

Java Reference implementation JVM agent, full observability, Spring Boot compatible

Rust Working implementation Transport SPI, config parser, agent library

Go Planned —

Python Planned —

C++ Planned —

Components in different languages participate in the same topology graph and produce the same distributed traces.

Observability

Itara treats observability as a first-class citizen. Every component call produces four events regardless of transport:

Event Side Fires

CALL_SENT Caller proxy When the business layer hands over the call to the topology layer

CALL_RECEIVED Callee dispatcher When the topology layer hands over the call to the business layer

RETURN_SENT Callee dispatcher When the business layer hands over the return to the topology layer

RETURN_RECEIVED Caller proxy When the topology layer hands over the return to the business layer

The placement is deliberate. The outer span (CALL_SENT → RETURN_RECEIVED) measures the full round trip from the caller's perspective. The inner span (CALL_RECEIVED → RETURN_SENT) measures pure component processing time, independent of transport. The gap between them is the transport overhead — serialization cost, network latency, deserialization cost — directly measurable without a single line of monitoring code. This is possible because the topology layer and the business layer have a clear boundary — and that boundary is precisely where these events fire.

Switching a connection from direct to HTTP changes the latency numbers in the traces. Nothing else changes. Topology decisions have a measurable cost, visible before you commit to them.

Beyond the four key events, plugins — including failure semantics implementations — may emit custom spans. These appear as children of the active context in the trace, making sub-call events such as retry attempts observable without modifying the four-event model.

The observer SPI is pluggable — any implementation can consume the four events and the custom spans. An OpenTelemetry observer ships with the open core as the reference implementation: distributed traces appear in Kibana with correct parent-child relationships across JVMs, using W3C traceparent headers for propagation. The Rust implementation has working OTel support — including cross-language traces spanning Java and Rust processes — at proof of concept level, planned for a redesign before production use.

See ADR 0003 for the full observability model, ADR 0010 for why the events fire where they do, ADR 0013 for the Rust implementation state, and ADR 0014 for the correlation ID model.

Event-driven topology

Itara supports event-driven communication through virtual nodes and event contracts. A virtual node represents an event channel — a Kafka topic, a queue, or any async transport — declared in the wiring config like any other node. The four-event observability model extends naturally to async calls: the producer side fires CALL_SENT and RETURN_RECEIVED, the consumer side fires CALL_RECEIVED and RETURN_SENT. The itaraTraceId correlation key links the two sides in the trace.

Event contracts are declared in API artifacts alongside regular method contracts. A component declare

[truncated for AI cost control]