Skip to content
Reference

The system host

The system host is the .NET project that carries a System’s topology declaration and runs it locally. intropy sys create renders it from the workspace’s scaffold records. Why a declared topology exists, and what it deliberately leaves out, is Systems and topology; this page lists what the host contains and the verbs it answers to. The distribution tutorial creates a host and walks these files in a scaffolded workspace.

The declaration is split across small files, one per kind of declaration, each name defined exactly once.

Messages are typed by the contract they carry, shared from the workspace’s contracts project. Each message names the pub/sub component and topic it travels on:

Messages.cs
using Intropy.Topology;
using Contracts;
public static class Messages
{
/// <summary>OrderPlaced messages, published on the orders topic.</summary>
public static readonly MessageRef<OrderPlaced> OrderPlaced =
MessageRef<OrderPlaced>.Define("order-placed", "order-flow-pubsub", "orders");
}

Each System owns one pub/sub component, named <system>-pubsub. MessageRef.Define takes the pub/sub and topic as optional arguments: the pub/sub defaults to the System’s own, and the topic defaults to the message name. intropy sys create writes both out. Several messages can share one topic.

Ports are the named interfaces edge Components reach the outside world through. The name is the whole declaration; what it leaves out is described in What the declaration leaves out:

Ports.cs
using Intropy.Topology;
public static class Ports
{
public static readonly PortRef OrderExtractorSource = PortRef.Define("order-extractor-source");
public static readonly PortRef OrderLoaderDestination = PortRef.Define("order-loader-destination");
}

Services are the platform services Components call through Dapr service invocation:

Services.cs
using Intropy.Topology;
public static class Services
{
public static readonly ServiceRef Idempotency = ServiceRef.Define("idempotency-service");
public static readonly ServiceRef BusinessIncidents = ServiceRef.Define("business-incident-service");
}

The system definition wires them together:

OrderFlowSystem.cs
using Intropy.Topology;
/// <summary>The order-flow system: what exists, and what connects it.</summary>
public sealed class OrderFlowSystem : ISystemDefinition
{
public string SystemName => "order-flow";
public void Define(SystemBuilder builder)
{
builder.AddExtractor("order-extractor")
.From(Ports.OrderExtractorSource)
.Publishes(Messages.OrderPlaced)
.Calls(Services.Idempotency)
.Calls(Services.BusinessIncidents);
builder.AddLoader("order-loader")
.Subscribes(Messages.OrderPlaced,
configure: sub => sub
.IgnoreOthers())
.To(Ports.OrderLoaderDestination)
.Calls(Services.Idempotency)
.Calls(Services.BusinessIncidents);
}
}

Each Add* method returns a builder that exposes only that Component kind’s legal edges; wiring a loader to publish does not compile. There is no AddMessage or AddPort: a ref enters the model when a Component uses it, and a ref no Component uses never enters the topology.

Component kind Legal edges on its builder
AddExtractor .From(port), .Publishes(message), .Calls(service)
AddLoader .Subscribes(message, when, configure), .To(port), .Calls(service)
AddTransactionalIntegration .From(port), .To(port), .Calls(service)

An extractor may publish several messages, each once. A loader declares one subscription, and all the messages it handles travel on one channel (pub/sub and topic). The subscription renders as one declarative Dapr Subscription with a routing rule per handled message. configure takes:

Method Effect
.AlsoHandles(message, when) Handle another message on the same channel.
.IgnoreOthers() Acknowledge and drop the channel’s messages the subscription does not handle. Without it, the broker dead-letters them.
.InBatches(maxMessages, maxWait) Deliver messages in batches (Dapr bulk subscribe).

when is an optional Dapr CEL expression over the event, such as event.data.status == '0', appended to the type match. Payload properties are camelCase.

A transactional-integration Component moves messages through an internal, component-owned queue between its receive and send halves. The queue is minted into the model as internal-<component> so deployment can provision it; it is not a System Topic, and no other Component can touch it.

The development definition substitutes local stand-ins for the things the topology only names: Mock backs a Service with an OpenAPI mock, Files resolves a Port to a folder, and Rerun starts a run-to-completion Component again a set time after each run ends. Rerun applies only to local runs; the deployed schedule is deployment configuration.

OrderFlowDevelopment.cs
using Intropy.Topology.Generation;
public sealed class OrderFlowDevelopment : IDevelopmentDefinition
{
public void Define(DevelopmentBuilder development)
{
development.Mock(Services.Idempotency).FromOpenApi("mocks/idempotency-service.openapi.yaml");
development.Mock(Services.BusinessIncidents).FromOpenApi("mocks/business-incident-service.openapi.yaml");
development.Files(Ports.OrderExtractorSource).RootPath("./test/order-extractor-source");
development.Files(Ports.OrderLoaderDestination).RootPath("./test/order-loader-destination");
development.Rerun("order-extractor").AfterEachRun(TimeSpan.FromSeconds(30));
}
}

The host’s entry point dispatches the same discovered model to different backends, selected by verb:

Program.cs
using System.Reflection;
using Intropy.Topology.Aspire;
using Intropy.Topology.Generation;
var assembly = Assembly.GetExecutingAssembly();
return args is ["run", ..] or []
? await IntropyAspire.RunAsync(assembly, args)
: IntropyGenerate.Run(assembly, args);
  • run (or F5) translates the topology into .NET Aspire resources: one process per Component, a Redis broker, a Dapr sidecar each, and the development definition’s mocks and folders. The Quick start walks through it.
  • check validates the topology and reports every diagnostic at once.
  • graph prints the model as interchange JSON (topology.intropy.io/v1, kind SystemTopology). This document is the contract the deployment tooling consumes; the runtime concepts page picks up the story from there.
  • generate writes the Dapr component YAML and per-component configuration the model implies.

Intropy.Topology is the DSL and model with zero runtime dependencies, Intropy.Topology.Generation adds the check/graph/generate verbs and the development definition, and Intropy.Topology.Aspire is the local-run backend.