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

Build Your First Model

In this chapter, you will build a coffee machine model from an empty file. By the end, you will have a typed, multi-definition model with attributes and imports – and you will understand the core concepts of SysML v2 through building, not through reading definitions.

Create a file called coffee_machine.sysml and follow along. After each step, you can run sysml check coffee_machine.sysml to verify your model.

You do not need the tool to follow the chapter – reading the models is enough. If you do want to run the commands, Appendix D explains how to get the sysml binary; it is a build from source rather than a package install, so it is worth setting up before you start rather than partway through.

The finished version of everything this book builds is already on disk, under examples/coffee-machine/. Chapter 11 maps each file to the chapter it belongs to. Peeking is allowed.

Step 1: An Empty Package

Every SysML v2 model lives inside a package. A package is a namespace – it groups related model elements and gives them a home.

Type this into your file:

package CoffeeMachineDomain {
}

That is a valid model. It does not describe anything yet, but it parses and checks cleanly. A package with nothing inside it is like an empty directory: a place waiting for content.

Run sysml check and confirm there are no errors.

Step 2: Your First Definition

Now add something inside the package. In SysML v2, a definition declares a reusable type – a blueprint for something that can exist in your system.

package CoffeeMachineDomain {
    part def Grinder;
}

part def Grinder says: there is a kind of thing called a Grinder. It is a part definition – a structural component type. You have not placed a grinder anywhere yet. You have only said that grinders exist as a concept in your domain.

The keyword part def is how SysML v2 spells “define a component type.” You will see this pattern everywhere: <keyword> def <Name>.

Step 3: A Usage Inside a Definition

A definition by itself is just a type. To say that a coffee machine contains a grinder, you create a usage of that type inside another definition:

package CoffeeMachineDomain {
    part def Grinder;

    part def CoffeeMachine {
        part grinder : Grinder;
    }
}

part grinder : Grinder says: inside a CoffeeMachine, there is a part called grinder, and it is of type Grinder. This is a typed usage – the most fundamental pattern in SysML v2.

Notice the difference:

  • part def Grinder – defines a type (uppercase, no colon)
  • part grinder : Grinder – uses that type in context (lowercase name, colon, type name)

This definition/usage split is how SysML v2 keeps models reusable. You define a type once, then use it wherever you need it.

Step 4: Adding More Definitions

A coffee machine has more than a grinder. Add a brewer and a water tank:

package CoffeeMachineDomain {
    part def Grinder;
    part def Brewer;
    part def WaterTank;

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

Run sysml check. No errors. The model now says: a CoffeeMachine is composed of three subsystems, each with a named role.

Here is how this model looks in an interactive general view. Each definition appears as a labeled block with its stereotype — «part definition», «item definition», and so on. Pan or zoom to inspect it:

CoffeeMachineDomain definitions: Grinder, Brewer, WaterTank, CoffeeMachine, items, and GrindSetting.

Notice that each definition is independent. Grinder does not know about Brewer. They only meet inside CoffeeMachine, where they each have a named usage. This separation is intentional – it keeps definitions reusable across different contexts.

Step 5: Attributes and Imports

Definitions without properties are just names. To make the model useful, add attributes – measurable or configurable properties of each component.

Attributes need types. SysML v2 provides built-in scalar types like Real, Integer, Boolean, and String in the ScalarValues library. To use them, you need an import:

package CoffeeMachineDomain {
    import ScalarValues::*;

    part def Grinder {
        attribute grindSize : Real;
    }

    part def Brewer {
        attribute waterTemp : Real;
        attribute brewPressure : Real;
    }

    part def WaterTank {
        attribute currentLevel : Real;
        attribute maxCapacity : Real;
    }

    part def CoffeeMachine {
        part grinder : Grinder;
        part brewer : Brewer;
        part waterTank : WaterTank;
        attribute serialNumber : String;
    }
}

Several things happened:

  • import ScalarValues::* brings all scalar types into scope. The ::* means “import everything from this package.” Without this line, Real and String would be unresolved names.
  • Each definition now has attribute members. An attribute is a property with a type: attribute grindSize : Real says the grinder has a numeric grind size setting.
  • CoffeeMachine itself has a serialNumber attribute. Definitions can have both part usages (components) and attribute usages (properties).

Run sysml check again. If you forget the import line, you will see errors like “unresolved name: Real.” That error tells you exactly what is missing – add the import and the errors disappear.

Step 6: Items – Things That Flow

Not everything in a system is a structural component. Some things are consumed, produced, or transferred. In SysML v2, these are items.

A coffee machine deals with water, coffee beans, and brewed coffee. These are not parts of the machine – they flow through it.

package CoffeeMachineDomain {
    import ScalarValues::*;

    // Items: things that flow through the system
    item def Water;
    item def CoffeeBeans;
    item def BrewedCoffee;

    // Parts: structural components
    part def Grinder {
        attribute grindSize : Real;
    }

    part def Brewer {
        attribute waterTemp : Real;
        attribute brewPressure : Real;
    }

    part def WaterTank {
        attribute currentLevel : Real;
        attribute maxCapacity : Real;
    }

    part def CoffeeMachine {
        part grinder : Grinder;
        part brewer : Brewer;
        part waterTank : WaterTank;
        attribute serialNumber : String;
    }
}

item def Water is like part def Grinder, but it signals a different intent. Parts are components you assemble into a structure. Items are things that move, get consumed, or get produced. The distinction matters when you model behavior and flows in later chapters.

The // lines are comments. They are ignored by the checker but help readers navigate the model.

Step 7: An Enumeration

Some properties have a fixed set of valid values. The grind size might be Fine, Medium, or Coarse. SysML v2 models this with an enumeration:

package CoffeeMachineDomain {
    import ScalarValues::*;

    // Items
    item def Water;
    item def CoffeeBeans;
    item def BrewedCoffee;

    // Enumerations
    enum def GrindSetting {
        fine;
        medium;
        coarse;
    }

    // Parts
    part def Grinder {
        attribute grindSize : GrindSetting;
    }

    part def Brewer {
        attribute waterTemp : Real;
        attribute brewPressure : Real;
    }

    part def WaterTank {
        attribute currentLevel : Real;
        attribute maxCapacity : Real;
    }

    part def CoffeeMachine {
        part grinder : Grinder;
        part brewer : Brewer;
        part waterTank : WaterTank;
        attribute serialNumber : String;
    }
}

enum def GrindSetting defines a type with exactly three values. The Grinder attribute now uses GrindSetting instead of Real, which is more precise – a reviewer can see exactly what values are valid.

What You Have Built

With the definitions expanded, you can see every attribute inside each part definition. Notice how CoffeeMachine contains typed usages of the other parts, and each part definition lists its own attributes:

Expanded CoffeeMachineDomain definitions, including typed part usages and attributes.

Step back and look at what you have. In about 40 lines, you have:

  • A package that organizes the model
  • Part definitions for three subsystems (Grinder, Brewer, WaterTank) and the top-level CoffeeMachine
  • Item definitions for things that flow (Water, CoffeeBeans, BrewedCoffee)
  • An enumeration for a constrained value set (GrindSetting)
  • Attributes on each definition, typed with scalar values or enumerations
  • Typed usages that compose the subsystems into the machine
  • An import that brings scalar types into scope

This is already a useful structural model. A reviewer can see what the system is made of, what properties each component has, and how the components relate.

What Comes Next

This model is the foundation for the rest of the book. Every chapter will extend it:

  • Chapter 3 formalizes the definition/usage pattern you used here.
  • Chapter 4 shows how to split this model across multiple files.
  • Chapter 5 adds ports – interaction points where components connect.
  • Chapter 6 wires the components together.
  • Chapter 7 adds behavior: what the machine does when you press brew.
  • Chapter 8 adds operating modes: idle, brewing, cleaning, error.
  • Chapter 9 adds checkable logic: temperature ranges, safety interlocks.
  • Chapter 10 adds requirements and traces them to the design.
  • Chapter 11 assembles everything into one coherent model.

Keep your coffee_machine.sysml file. You will keep building on it.

Common Mistakes

Forgetting the import. If you use Real or String without import ScalarValues::*, the checker will report unresolved names. The fix is always the same: add the import at the top of your package.

Leaving usages untyped. This is valid syntax:

part def CoffeeMachine {
    part grinder;
}

But it tells a reviewer nothing about what kind of grinder this is. Always type your usages: part grinder : Grinder. Typed usages make the model checkable and reusable.

Defining everything at the top level. If you write definitions outside a package, they have no namespace boundary. As your model grows, name collisions become likely. Always wrap definitions in a package.