Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Connections and Interfaces

Your coffee machine has parts and ports. The WaterTank has an outlet, the Brewer has inlets, the Grinder has an output. But right now, nothing is wired together. The ports exist in isolation – interaction points with nothing to interact with.

This chapter connects the machine. You will start with the simplest possible connection, add port-level wiring, define interface contracts, and finally model what actually flows through those wires. By the end, the coffee machine will be a connected system, not just a bag of parts.

The Simplest Connection

A connection in SysML v2 says: these two parts interact. The simplest form names the two ends directly:

part def CoffeeMachine {
    part waterTank : WaterTank;
    part brewer : Brewer;

    connect waterTank to brewer;
}

connect waterTank to brewer creates a connector between the two part usages. It says the water tank and brewer are linked somehow, but it does not say how. No ports are involved. No item type is specified. It is a statement of topology: these two parts interact.

This is useful early in modeling when you want to capture the fact that two subsystems communicate before you have worked out the details. A reviewer can see the interaction structure without getting buried in port definitions.

But there is a problem. The connection says nothing about where on each part the interaction happens, or what moves between them. For a coffee machine, that matters. Water enters the brewer through a specific inlet, not through the grinder’s bean hopper. To express that, you need port-level connections.

Connecting Through Ports

In Chapter 5, you added ports to the coffee machine’s parts. The WaterTank has a waterOut port that sends Water. The Brewer has a waterInlet that receives Water and a groundInput port that receives ground CoffeeBeans. The Grinder has a groundOutput that sends ground CoffeeBeans.

Now connect them at the port level:

part def CoffeeMachine {
    part waterTank : WaterTank;
    part brewer : Brewer;
    part grinder : Grinder;

    connect waterTank.waterOut to brewer.waterInlet;
    connect grinder.groundOutput to brewer.groundInput;
}

This is more precise. connect waterTank.waterOut to brewer.waterInlet says: the water tank’s waterOut port is connected to the brewer’s waterInlet port. The dot notation navigates from the part usage to its port.

Two things changed from the simple form:

  1. Each end is a port, not a part. The connection targets the specific interaction point on each component.
  2. Direction matters implicitly. waterOut is typed by WaterSupplyPort, which declares out item water; waterInlet is typed by WaterPort, which declares in item water. The connection goes from a source of Water to a sink of Water. SysML v2 does not force you to declare the direction on the connector itself – the port definitions already carry that information.

This is the form you will use most often. Port-level connections make the interaction topology explicit and checkable. A tool can verify that the types on each end are compatible: an outlet sending Water connects to an inlet receiving Water.

Naming Your Connections

Every connection so far has been anonymous. You can give a connector a name if you want to refer to it later – in constraints, requirements, or documentation:

part def CoffeeMachine {
    part waterTank : WaterTank;
    part brewer : Brewer;
    part grinder : Grinder;

    connection waterLine connect waterTank.waterOut to brewer.waterInlet;
    connection beanFeed connect grinder.groundOutput to brewer.groundInput;
}

waterLine and beanFeed are now named connectors. The names do not change behavior, but they make the model more readable and give you handles to attach flows, constraints, or requirements to specific connections.

Interface Definitions

A connection says two ports are linked. An interface definition says what a valid connection between two kinds of ports looks like. It is a contract.

Suppose you want to formalize what it means to connect a water source to a water consumer. Define an interface:

interface def WaterSupply {
    end supplierPort : WaterSupplyPort;
    end consumerPort : WaterPort;
}

interface def WaterSupply declares a reusable connection contract with two ends. Each end is typed by a port definition. Any connection that matches this pattern – a WaterSupplyPort on one side, a WaterPort on the other – conforms to the WaterSupply interface.

You use the interface when creating the connection:

part def CoffeeMachine {
    part waterTank : WaterTank;
    part brewer : Brewer;

    interface waterLine : WaterSupply {
        end supplierPort ::> waterTank.waterOut;
        end consumerPort ::> brewer.waterInlet;
    }
}

This says: the connection between waterTank.waterOut and brewer.waterInlet is an instance of the WaterSupply interface. The ::> binds each interface end to a specific port usage.

When do you need interface definitions? Not always. For simple models, direct connect statements are fine. Interface definitions earn their keep when:

  • Multiple connections follow the same pattern and you want to enforce consistency.
  • You need to attach shared constraints or metadata to a class of connections.
  • You are modeling a system where the interface contract matters independently of who implements it – like a standardized connector.

Item Flows

Connections and interfaces describe the topology: what is linked to what. Item flows describe the content: what actually moves through a connection.

You already defined item types in Chapter 2 – Water, CoffeeBeans, BrewedCoffee. An item flow says that a specific item type travels along a specific connection:

part def CoffeeMachine {
    part waterTank : WaterTank;
    part brewer : Brewer;
    part grinder : Grinder;

    connection waterLine connect waterTank.waterOut to brewer.waterInlet;
    connection beanFeed connect grinder.groundOutput to brewer.groundInput;

    flow of Water from waterTank.waterOut to brewer.waterInlet;
    flow of CoffeeBeans from grinder.groundOutput to brewer.groundInput;
}

flow of Water from waterTank.waterOut to brewer.waterInlet says: Water moves from the tank’s outlet to the brewer’s inlet. The of Water clause types the flow. The from ... to ... clause identifies the source and destination.

Item flows serve a different purpose than connections. A connection says two ports are linked. A flow says something specific travels through that link. You can have a connection without a flow (the link exists, but you have not specified what moves), and in principle you can have multiple flows on the same connection (water and dissolved minerals, say).

In practice, most connections carry one primary flow, and you will often write both the connection and the flow together. The flow is what makes reviewers confident the model is complete: not just “these parts are linked” but “Water moves from here to there.”

Named Flows

Like connections, flows can be named:

flow hotWater of Water from waterTank.waterOut to brewer.waterInlet;
flow groundBeans of CoffeeBeans from grinder.groundOutput to brewer.groundInput;

Named flows are useful when you need to reference them in constraints or requirements. For example, a requirement might say “the hotWater flow shall not exceed 95 degrees Celsius.” Without a name, you have nothing to point at.

Flow Definitions

Every flow so far has been a usage written directly where it happens. Flows have definitions too, the same way parts and actions do – and if you find yourself writing the same of <Item> from <port> to <port> shape repeatedly, that is the signal to define it once.

flow def <Name> {
    end <name> : <SourcePortType>;
    end <name> : <TargetPortType>;
}
flow def WaterFlow {
    end supply : WaterSupplyPort;
    end intake : WaterPort;
}

part def CoffeeMachine {
    part waterTank : WaterTank;
    part brewer : Brewer;

    flow supplyLine : WaterFlow from waterTank.waterOut to brewer.waterInlet;
}

A flow definition is a reusable kind of flow. Its end members say what the two sides must be – here, that a WaterFlow always runs from something with a WaterSupplyPort to something with a WaterPort. The usage then names the particular flow and says which ports it connects.

Read it as: WaterFlow is the kind of movement; supplyLine is this movement, in this machine.

This is the def/usage split from Chapter 3 applied to flows, and it buys the same things it always does. The definition is one place to document what the flow means, one place to attach a constraint on its rate or temperature, and one thing a requirement can point at for every machine rather than for one instance.

The coffee machine’s own model defines WaterFlow, CoffeeFlow and SteamFlow this way, and then declares usages against them.

Flows that also order

part def CoffeeMachine {
    succession flow waterThenCoffee of Water
        from waterTank.waterOut to brewer.waterInlet;
}

A succession flow does two jobs at once: it carries something, and it constrains time. The plain flow says water moves from the tank to the brewer. succession flow adds that the source end completes before the target end begins – the ordering meaning you met as first ... then in Chapter 7, attached to a flow rather than to two actions.

Reach for it when the sequence matters as much as the transfer: the tank finishes delivering before the brewer starts drawing, rather than both being live at once.

Binding Connectors

There is one more kind of connector you should know about: the binding connector. A regular connection says two separate things interact. A binding says two things are the same thing, seen from different contexts.

Suppose you expose a port on the CoffeeMachine itself, and you want to bind it to an internal part’s port:

part def Brewer {
    port waterInlet : WaterPort;
    port groundInput : BeanInputPort;
    port brewOutput : BrewOutputPort;
}

part def CoffeeMachine {
    port dispenserOut : BrewOutputPort;

    part brewer : Brewer;

    bind dispenserOut = brewer.brewOutput;
}

bind dispenserOut = brewer.brewOutput says: the machine’s dispenserOut port is the brewer’s brewOutput port. They are not separate endpoints with something flowing between them. They are the same port, exposed at two levels of the hierarchy. What appears on brewer.brewOutput appears on dispenserOut, by identity.

Bindings are how you thread internal ports up to an external interface. If the CoffeeMachine is used inside a larger system – a cafe counter, say – that outer system connects to dispenserOut without knowing about the brewer inside.

Allocation

Every relationship so far has been structure-to-structure: this port talks to that port, this item moves along that link. Allocation is the one that crosses between models. It says a behavioral element is the responsibility of a structural one – this step of the brew cycle is the grinder’s job.

You need it because the two decompositions are deliberately independent (Chapter 7 makes that point about actions). BrewCycle breaks into grind, heat, and extract; CoffeeMachine breaks into grinder, waterTank, and brewer. Nothing in either decomposition says which part does which step. Allocation is where you write that down:

part def CoffeeMachine {
    part grinder : Grinder;
    part brewer : Brewer;
    part waterTank : WaterTank;

    perform action brewCycle : BrewCycle;

    // Commit each step of the cycle to the part responsible for it
    allocate brewCycle.grind to grinder;
    allocate brewCycle.heat to waterTank;
    allocate brewCycle.extract to brewer;
}

allocate brewCycle.grind to grinder says: whatever the grind step requires, the grinder is what has to deliver it. That is a claim a reviewer can check. If grind needs 200 W and the grinder is specified at 150 W, the allocation is where the mismatch surfaces – not in a meeting six months later. Allocations are also queryable in both directions: “what does the brewer have to do?” and “who is responsible for heating?” both have answers in the model.

When you need to attach anything to the allocation itself – a rationale, a margin, a review status – name it and give it a definition, exactly as with connections:

allocation def FunctionToComponent;

part def CoffeeMachine {
    part grinder : Grinder;
    perform action brewCycle : BrewCycle;

    allocation grindAlloc : FunctionToComponent allocate brewCycle.grind to grinder;
}

Reach for the bare allocate when the mapping is the whole story, and the named form when the mapping itself needs properties. Allocation is not limited to behavior-to-structure: the same construct maps requirements to components, logical designs to physical ones, or software functions to processors. The pattern is always “this element is realized by that one”.

The Connected Coffee Machine

Here is the coffee machine with all of its connections and flows in one place. This builds on the ports from Chapter 5 and the definitions from Chapter 2:

package CoffeeMachineConnections {
    import CoffeeMachineDomain::*;

    part def CoffeeMachine {
        // Internal parts
        part waterTank : WaterTank;
        part brewer : Brewer;
        part grinder : Grinder;

        // External port
        port dispenserOut : BrewOutputPort;

        // Port-level connections
        connection waterLine connect waterTank.waterOut to brewer.waterInlet;
        connection beanFeed connect grinder.groundOutput to brewer.groundInput;

        // Typed flows -- what moves through each connection
        flow hotWater of Water from waterTank.waterOut to brewer.waterInlet;
        flow groundBeans of CoffeeBeans from grinder.groundOutput to brewer.groundInput;

        // Binding -- expose brewer output as machine output
        bind dispenserOut = brewer.brewOutput;
    }
}

Read this model from top to bottom. The parts tell you what the machine is made of. The connections tell you how the parts are wired. The flows tell you what moves through those wires. The binding tells you where the output appears to the outside world.

A reviewer looking at this model can answer: What are the subsystems? How do they connect? What flows between them? Where does the product come out? That is a complete structural interaction picture, and it took about 15 lines.

The Progression

This chapter followed a deliberate progression that mirrors how you should model connections in practice:

  1. Simple connect. connect tank to brewer – capture the topology before the details.
  2. Port-level connect. connect waterTank.waterOut to brewer.waterInlet – specify exactly where interaction happens.
  3. Named connection. connection waterLine connect ... – give handles for later reference.
  4. Interface definition. interface def WaterSupply – formalize reusable connection contracts.
  5. Item flow. flow of Water from ... to ... – specify what moves.
  6. Binding. bind dispenserOut = brewer.brewOutput – expose internal ports to the outside.
  7. Allocation. allocate brewCycle.grind to grinder – commit behavior to the structure responsible for it.

You do not need all seven levels for every connection. Many models get by with port-level connections and typed flows. Interface definitions and bindings appear when the model needs to formalize contracts or manage hierarchy. Start simple and add precision where the design demands it.

Visualizing Connections

This interconnection view shows how the parts connect through their ports, with item flows indicating what travels through each connection:

Interconnection view: parts connected through ports with typed item flows.

This is the Interconnection view — one of the eight standard view definitions covered in Chapter 13. If you are coming from SysML v1, you will recognize the same content under the older labels: a part-level Interconnection is what v1 called an Internal Block Diagram (IBD), and a definition-level Interconnection is what v1 called a Block Definition Diagram (BDD).

sysml-rs extends the spec here. When the connection sits between two ports whose types are quantities from the standard library’s ISQ family — voltage, current, force, velocity, and so on — the simulation runtime treats the connection as a bond in a bond graph, with conservation laws applied automatically. The bond-graph synthesis is a sysml-rs simulation behaviour, not a spec construct; see Appendix E (the physics-aware simulation reference) once you reach the simulation chapters.

Common Mistakes

Connecting definitions instead of usages. This is wrong:

connect WaterTank to Brewer;  // Wrong: these are definitions, not usages

Connections link usages – the specific parts inside a context. You cannot connect WaterTank as a type; you connect waterTank, the specific instance inside CoffeeMachine.

Mismatched port types on a connection. If waterTank.waterOut sends Water but brewer.groundInput expects CoffeeBeans, connecting them is a type error. The port types on each end of a connection should be compatible. This is one of the main benefits of typed ports – the model catches wiring mistakes.

Flows without connections. A flow says what moves, but a connection says what is linked. If you write a flow between two ports that have no connection, you are asserting transfer through a link that does not exist. Always establish the connection first, then add the flow.

Using bind when you mean connect. A binding says two things are identical. A connection says two separate things interact. If the water tank and brewer are separate parts with different ports, you want connect. If you are exposing an internal port at a higher level in the hierarchy, you want bind.

What Comes Next

The coffee machine now has structure, ports, connections, and flows. It is fully wired but entirely static. In Chapter 7, you will add behavior – the brew cycle that moves water through the tank, grinds beans, and produces coffee. The connections you built here become the pathways that behavior uses.