Skip to content
Tutorial3/4

3. Add a second loader by hand

In this chapter you scaffold the web shop’s loader and wire it into the System by editing the topology declaration yourself.

From the scratch directory root — the same command as chapter 1, with the new names:

Terminal window
intropy int create loader -n WebLoader -o web-loader --template-version v0.4.1 \
-s organization=Fluxia -s topic=order-notifications -s contract=Order -s empty=false
fetching integrio-intropy/intropy-templates@v0.4.1
created web-loader from integrio-intropy/intropy-templates@v0.4.1 (template loader)
dependency Contracts already scaffolded from shared-contracts — skipped

The topic and contract are the same values as before: the new loader joins the existing system contract instead of defining anything new.

You could rerun sys create --force and let it regenerate the host from the records. This chapter does it by hand instead, for two reasons: regeneration overwrites any local edits the host has accumulated, and the edit teaches you what the declaration actually is. Three files, one addition each.

First the Port — the loader’s named interface to the web shop:

order-notifications/Ports.cs
public static class Ports
{
public static readonly PortRef ErpExtractorSource = PortRef.Define("erp-extractor-source");
public static readonly PortRef WebLoaderDestination = PortRef.Define("web-loader-destination");
public static readonly PortRef WmsLoaderDestination = PortRef.Define("wms-loader-destination");
}

Then the Component, in the system definition. The AddLoader name must match the scaffolded directory (web-loader); the host locates the project by that convention:

order-notifications/OrderNotificationsSystem.cs
public void Define(SystemBuilder builder)
{
builder.AddExtractor("erp-extractor")
.From(Ports.ErpExtractorSource)
.Publishes(Topics.OrderNotifications)
.Uses(Services.Idempotency)
.Uses(Services.BusinessIncidents);
builder.AddLoader("web-loader")
.Subscribes(Topics.OrderNotifications)
.To(Ports.WebLoaderDestination)
.Uses(Services.Idempotency)
.Uses(Services.BusinessIncidents);
builder.AddLoader("wms-loader")
.Subscribes(Topics.OrderNotifications)
.To(Ports.WmsLoaderDestination)
.Uses(Services.Idempotency)
.Uses(Services.BusinessIncidents);
}

Both loaders call .Subscribes(Topics.OrderNotifications) — the same TopicRef, defined once in Topics.cs. That single shared reference is the entire fan-out; there is no routing table anywhere.

Last, the development resolution, so the new Port is a folder when the host runs locally:

order-notifications/OrderNotificationsDevelopment.cs
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.ErpExtractorSource).RootPath("./test/erp-extractor-source");
development.Files(Ports.WebLoaderDestination).RootPath("./test/web-loader-destination");
development.Files(Ports.WmsLoaderDestination).RootPath("./test/wms-loader-destination");
}

Nothing else changes. The host’s project file references no Component projects, and no other file names the loaders; the declaration you just edited is the System.

The host validates its own topology without starting anything:

Terminal window
cd order-notifications
dotnet run -- check
ok: 'order-notifications' is valid (3 components).

If a name doesn’t line up — an AddLoader that matches no scaffolded directory, a Port declared but never resolved — this is where it surfaces, not at runtime.