Interfaces
An interface is a versioned contract that types implement. It names attributes, associations and methods a type must provide, without fixing the type's position in the inheritance hierarchy. Dependent models can then rely on "anything that is Named-1" instead of on a concrete type of another model — which is what keeps a change of that concrete type from cascading into every dependent.
Interfaces require ckLanguage: 2 (see Overview).
Declaring an Interface
Interfaces live in YAML files under the interfaces/ folder of the model (next to types/, attributes/, ...). Each file has an interfaces: root key. The compiler reads interfaces only from that folder — an interfaces: key in a file of another folder is ignored, with warning 110.
$schema: https://schemas.meshmakers.cloud/construction-kit-elements.schema.json
interfaces:
- interfaceId: Named-1
description: Anything with a human-readable name.
attributes:
- id: ${System}/Name
name: Name
- id: ${System}/Description
name: Description
isOptional: true
- interfaceId: Identified-1
description: A named thing with a serial number.
extends:
- ${this}/Named-1
attributes:
- id: ${this}/SerialNumber
name: SerialNumber
| Key | Required | Description |
|---|---|---|
interfaceId | yes | PascalCase name with the element version, e.g. Named-1. The version is the contract version (see Versioning). |
description | no | Free text. |
attributes | no | Attribute members: id (attribute definition, reused as on types), name (assignment name the implementing type must use), isOptional. |
extends | no | Interfaces this interface extends, e.g. ${this}/Named-1. Multiple entries are allowed; members are inherited. |
associations | no | Association members. |
methods | no | Method members — same schema as type methods. |
deprecated | no | true marks the contract as deprecated; every reference to it produces a compile warning (message 124). |
visibility | no | Public (default) or Internal — see Access modifiers. |
An interface needs at least one member or one extends entry (message 123). An interface may not have the same name as a type of the same model (message 93), and each interface id may be declared only once (message 92) — Named-1 and Named-2 are different ids.
Inside one interface each attribute and each member name may appear only once (message 127). Across an interface and the interfaces it extends, one name must not stand for two attributes and one attribute must not appear under two names (message 119); repeating an inherited member identically is allowed. extends must name known interfaces, not the interface itself and not form a cycle (message 118).
Implementing an Interface
A type lists the interfaces it implements in implements:
types:
- typeId: Device
isAbstract: true
derivable: Any
derivedFromCkTypeId: ${System}/Entity
implements:
- ${this}/Identified-1
attributes:
- id: ${System}/Name
name: Name
- id: ${System}/Description
name: Description
isOptional: true
- id: ${this}/SerialNumber
name: SerialNumber
The compiler checks every type that implements an interface — including the members of all interfaces it extends:
| Rule | Message |
|---|---|
| The interface is known | 95 |
| Every required attribute member is assigned, with the same attribute id, by the type or one of its base types | 96 |
A required member is required on the type (isOptional not true) | 97 |
| The assignment uses the member's name | 98 |
The assignment is not access: Hidden — interface members must be visible | 99 |
| Every required association member is provided (see below) | 121 |
| A type does not redeclare an interface method with a different invocation contract (kind, parameters, result, error codes) | 122 |
Optional members need not be assigned. Members inherited from a base type satisfy the interface, and implements is inherited by derived types: Sensor and Gateway in the example implement Identified-1 and Named-1 because Device does. Implementing an interface implements every interface it extends.
Example of a missing member:
Error 96 types/types.yaml: Type 'Acme.Plant-1.0.0/Tag-1' implements 'Acme.Assets-1.0.0/Identified-1' but does not
assign required member 'Acme.Assets-1.0.0/SerialNumber-1' (name 'SerialNumber').
Association Members
An association member requires implementing types to have an outbound association with a given role and a compatible target:
interfaces:
- interfaceId: Monitoring-1
associations:
- id: ${this}/Monitors # association role
targetCkInterfaceId: ${this}/Identified-1 # or targetCkTypeId — exactly one of the two
multiplicity: N # One | ZeroOrOne | N; omitted = any
isOptional: false
- Exactly one of
targetCkTypeIdandtargetCkInterfaceIdis set, the role and the target must exist (message 120). - A type implementing the interface must have an own or inherited outbound association with that role whose target is the member's target type or a subtype — for an interface target: a type implementing the interface, or an association narrowed to that interface (or to one extending it). Its multiplicity must be at least as strict as the member's (
Oneis stricter thanZeroOrOne, which is stricter thanN). Otherwise message 121. Optional members are not checked.
Narrowing a Type Association to an Interface
A type association can narrow its target to implementors of an interface with targetCkInterfaceId:
types:
- typeId: Gateway
derivedFromCkTypeId: ${this}/Device
implements:
- ${this}/Monitoring-1
associations:
- id: ${this}/Monitors
targetCkTypeId: ${System}/Entity # still required
targetCkInterfaceId: ${this}/Identified-1 # target must also implement this interface
targetCkTypeId stays required: the interface narrows the target — a valid target derives from targetCkTypeId and implements the interface. Use ${System}/Entity as targetCkTypeId to accept any implementor. An unknown interface is message 128.
The compiler uses the narrowing to satisfy interface association members (rule 121), and the GraphQL navigation field of a narrowed association only offers the concrete target types that implement the interface. When associations are created at runtime, the target is checked against targetCkTypeId only.
Method Members
Interfaces can declare method definitions with the same schema as type methods (see Method definitions). A type implementing the interface inherits them (they appear among the type's methods); redeclaring one with a different invocation contract (kind, the parameters by name with their type, isOptional and sensitive, the result, the set of error codes) is message 122; descriptions, authorization and execution may differ, and the redeclaring type's own authorization and execution apply. Every change of that contract on an interface method is a MAJOR version change of the interface's model, because redeclaring types in other models would stop compiling. Interface methods follow the same rules as type methods (messages 100–104).
Versioning an Interface
The element version in interfaceId (Named-1) is the contract version. It is independent of the model version.
- A published interface's contract does not change. Adding, removing or changing a member (attribute, association or method, including
isOptional) and adding or removing anextendsentry are Major changes of the model. - Instead, publish the changed contract as a new interface element next to the old one —
Named-2besideNamed-1, in the same model and the same model major. A new interface is a Minor change. - Types can implement both versions during a transition; dependents move to
Named-2when they choose. - Mark the old version
deprecated: true(Minor). Dependents get warning 124 for everyimplements,extends, interface association target andtargetCkInterfaceIdthat still references it. Remove it only with the next model major.
# interfaces/named.yaml
interfaces:
- interfaceId: Named-1
deprecated: true
attributes:
- id: ${System}/Name
name: Name
- interfaceId: Named-2
attributes:
- id: ${System}/Name
name: Name
- id: ${this}/ShortName
name: ShortName
# types/tag.yaml
types:
- typeId: Tag
derivedFromCkTypeId: ${System}/Entity
implements:
- ${this}/Named-1 # warning 124 (deprecated)
- ${this}/Named-2
attributes:
- id: ${System}/Name
name: Name
- id: ${this}/ShortName
name: ShortName
Interface ids always carry their version, also in compiled models and references (Acme.Assets-1.0.0/Named-1, or Acme.Assets@1/Named-1 with range retention).
The full SemVer classification is in Versioning rules.
Referencing Interfaces From Other Models
Reference an interface of a dependency like any other element: ${Acme.Assets}/Identified-1. A model can implement it, extend it, use it as an association target — provided the interface is not visibility: Internal (message 112):
# Acme.Plant (ckLanguage: 2, depends on Acme.Assets-[1.0,2.0))
types:
- typeId: Pump
derivedFromCkTypeId: ${Acme.Assets}/Device # Device declares derivable: Any
implements:
- ${Acme.Assets}/Calibratable-1
In GraphQL and Code
- Each CK interface becomes a GraphQL interface type that the implementing entity types implement (
Named-1→AcmeAssetsNamed,Named-2→AcmeAssetsNamed2);extendsmaps to GraphQL interface inheritance — see GraphQL mapping. Querying entities by interface is not available yet. - The source generator emits a C# interface
IRt<Name>per CK interface (see Generated code). - The generated model documentation lists an
Interfaces.mdpage per model (members including inherited ones,Extends, association and method tables, a "Deprecated" marker) and an "Implements" line per type.