Skip to content

CMN reference

This page is a compact syntax reference for the Canonical Model Notation (CMN).

For background and modeling concepts see the Modeling section. This page focuses on syntax and where elements belong in a .cmn file.

Examples use the Base platform where generation behaviour matters. Other platforms (e.g. Java or Spring Boot) may add additional properties, stereotypes or generators on top of the same syntax.


1. File header

A CMN file starts with a header and then contains model elements.

cmn
core package com.example.app

import com.example.core
import com.example.shared as shared

platform Base
  • package – mandatory; defines the namespace of the model (com.example.app in the example).
  • core – optional layer in front of package. Layers can be used for outlet routing and platform-specific rules (for example to route generated artifacts from different layers to different Maven modules). If you omit the layer, you simply start with package com.example.app.
  • import – brings other packages into scope.
  • as – defines an alias for an imported package (shared in the example). The alias can later be used as a prefix like shared::TypeName.
  • platform – selects the platform (e.g. Base, SpringBoot).
    platform X exclude Y disables cartridge Y for this model.

Aliases are used as prefixes when referencing external elements:

cmn
type Info {
    createdAt: shared::DateTime
}

Subpackages allow structuring within a file:

cmn
subpackage backend {
    // elements in com.example.app.backend
}

See also: Model file structure.


2. Simple types

Simple types represent scalar values such as strings, numbers, dates or booleans.
They are always based on a stereotype defined in a profile (for example string, integer, decimal, …) and can optionally specialize another simple type.

There are two ways to define a simple type:

  • define a new simple type based on a stereotype
  • define a specialization of an existing simple type

2.1 Built-in simple types from the Base facility

The Base facility (org.joinedworkz.facilities.common.base) ships a CMN model that already defines a set of commonly used simple types:

cmn
type<any> Any
type<string> String(maxLength) maxLength=undefined
type<integer> Integer(maxDigits) max=2147483647 min=-2147483648 maxDigits=10
type<long> Long(precision) precision=undefined
type<decimal> Decimal(maxDigits,decimals) maxDigits=undefined decimals=undefined
type<date> Date
type<time> Time
type<time> Timestamp
type<boolean> Boolean
type<binary> Binary

formatter UUID pattern='^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$'

type Id specialization of String maxLength:=36 format=UUID javaType='java.util.UUID'
type Name specialization of String maxLength=50
type Text(maxLength) specialization of String maxLength=1000 multiLine=true

The formatter element is currently only a placeholder for future extensions and is not actively used in the generators.

These types become available in any model that imports the Base CMN model (e.g. org.joinedworkz.facilities.common.base.types).

2.2 Defining a new simple type

A new simple type always references a simpletype stereotype from a profile. The stereotype determines which properties are available.

Example: a simple type for three-letter currency codes:

cmn
type<string> CurrencyCode(maxLength) maxLength=3
  • string – the stereotype (from the Base profile).
  • maxLength in parentheses – parameter that refers to an existing property.
  • maxLength=3 – default value for this property.

The stereotype and its properties are defined in a profile, for example:

profil
contribute to simpletype<string> {
  property maxLength: INTEGER
  property pattern: STRING
}

This makes maxLength and pattern available on all string simple types and – by propagation – on fields in complex types that use those types.

2.3 Specializing an existing simple type

Instead of defining a completely new simple type, you can specialize an existing one and override some of its properties:

cmn
type Name specialization of String maxLength=50

type Text(maxLength) specialization of String maxLength=1000 multiLine=true
  • Name keeps all properties from String, but fixes maxLength to 50.
  • Text specializes String and introduces a parameter maxLength plus an additional property multiLine.

Property values of the base type can be overridden in the specialization. The profile controls which properties exist and how they propagate.

2.4 Properties, parameters and propagation

Simple types get their properties from profiles via contribute blocks:

profil
contribute to simpletype<string> {
  property maxLength: INTEGER
  property javaType: STRING
  property format: STRING
}

These properties

  • are available on the simple type itself, and
  • are usually propagated to fields in complex types that use those simple types.

You can override a property in three ways:

  1. On the simple type definition (as shown above):

    cmn
    type Name specialization of String maxLength=50
  2. On a field in a complex type:

    cmn
    type Person {
      name: Name maxLength=80   // override for this field only
    }
  3. Via parameters when using the simple type:

    cmn
    type Person {
      nickname: String(200)     // shorthand for: maxLength=200
    }

Parameters in the simple type declaration are references to properties, not new properties. They only exist if the profile defines them.

2.5 Property values and constants

Property values can be:

  • string literals
  • numeric values
  • booleans (true / false)
  • references to other model elements (e.g. a formatter)
  • the literal undefined (meaning “no value set”)

You can factor out common values into constants:

cmn
const XML  = 'xml'
const JSON = 'json'

These constants can then be used as property values:

cmn
type<string> ContentType(maxLength) maxLength=50
type<string> JsonContentType specialization of ContentType default=JSON

This avoids repeating long or error-prone literals across multiple types.


3. Complex types

Complex types are structured types with named fields. They can represent entities, aggregates, projections or general DTOs and can participate in inheritance hierarchies.

cmn
type<entity> Customer extends BaseEntity {

    firstName*:  Name
    lastName*:   Name
    category:    Category         // containment
    employer ->  Company          // reference

    addresses:   Address[]        // 0..n containments
    tags:        String[3]        // 0..3 values
    ratings:     Integer[1..*]    // 1..n values

    attributes:  Info[String]     // dictionary: key type String, value type Info
}
  • type – defines a complex type.
  • Optional stereotypes such as entity, aggregate, projection influence how platforms interpret the type (e.g. persistence mapping).
  • extends BaseEntity – complex types can inherit from another complex type.
  • Fields use simple types or other complex types and support different relation kinds, cardinalities and flags (see below).

3.1 Inheritance

Complex types support single inheritance via the extends keyword:

cmn
type<entity> Customer extends BaseEntity {
    // fields here
}

The derived type inherits all fields and properties from its base type. Profiles can define how properties propagate along inheritance (for example tableName is not propagated for entities in the Base platform).

3.2 Containments vs. references

The relationship between a complex type and a field’s type is expressed by the separator between the field name and the type:

cmn
category: Category      // containment
employer -> Company     // reference
  • :containment: the field contains the other type (typical for value objects or embedded types).
  • ->reference: the field refers to another type (typical for relations to another entity). The referenced type must define a key field.

Generators can use this information, for example to distinguish embedded objects from foreign-key relationships in persistence mappings.

3.3 Cardinalities and dictionaries

Collections and dictionaries use square brackets after the type name.

3.3.1 Collection cardinalities

cmn
addresses: Address[]      // 0..n
lines:     Line[2..*]     // 2..n
tags:      String[3]      // 0..3
  • [] – zero or more elements (0..n).
  • [min..*] – at least min elements (min..n).
  • [max] – up to max elements (0..max).

The exact runtime representation (e.g. Java List, array, …) is platform- specific; profiles usually define strategies for mapping collection types.

3.3.2 Dictionaries (maps)

When the brackets contain a type instead of a cardinality, the field is a dictionary / map:

cmn
additionalInfos: Info[String]
  • key type – specified in brackets (String),
  • value type – the type after the brackets (Info).

Platforms can map this to keyed collections or map types (for example Map<String, Info> in Java).

3.4 Field flags: mandatory and key

Short flags behind the field name control common boolean properties:

cmn
lastName*: Name   // mandatory
id**:      Id     // key
  • * (single star) – field is mandatory (required).
  • ** (double star) – field gets the stereotype key (primary identifier).

Profiles can also contribute additional boolean properties that can be set directly on fields, for example readOnly=true.

3.5 Including fields

Complex types can reuse fields from other types or from reusable field definitions. This helps avoid duplication and keeps models consistent.

3.5.1 Include all fields from another type

cmn
type Address {
    street: String
    zipCode: String
    city:   String
}

type CustomerAddress {
    ...Address
}

...Address includes all fields from Address into CustomerAddress.

You can exclude specific fields:

cmn
type CustomerAddress {
    ...Address exclude zipCode
}

3.5.2 Standalone field definitions

Fields can also be defined standalone (similar to XSD global elements):

cmn
field state: Integer

These fields can be included into complex types by name:

cmn
type Data {
    state
}

The type, cardinality and properties of the original field are reused.

3.5.3 Fieldsets (reusable groups of fields)

Fieldsets group multiple fields that are reused together:

cmn
fieldset CommonFields {
    createdAt: Timestamp
    revision:  Integer
}

type AuditInfo {
    ...CommonFields
}

Fieldsets can be included just like complex types: ...CommonFields.

3.5.4 Include single fields from another type

You can also include a single field from another complex type or fieldset by using a qualified name:

cmn
type AuditSummary {
    CommonFields.revision
}

It is possible to rename the field while including it:

cmn
type AuditSummary {
    rev ..CommonFields.revision
}

The new field keeps the original type, cardinality and properties but gets the new name rev.

Generators can use field inclusion information, for example to generate mappings between related types automatically.

3.6 Operations on complex types

Complex types can declare operations. This is particularly useful for entity types where you want to model finder or helper methods close to the data structure:

cmn
type<entity> Customer {

    id**:   Id
    name*:  Name

    operation findByName(name: Name): Customer
}

Platforms are free to interpret these operations, e.g. by generating repository methods or domain services. Not all platforms currently use complex-type operations, but the syntax is available.

3.7 Boolean field properties via field lists

For boolean properties that apply to multiple fields, you can use a compact list syntax at the end of the complex type. Instead of setting a flag on each field individually, you define the property once and list the fields for which it should be true:

cmn
type Person {

    firstName: Name
    lastName:  Name
    comment:   Text

    °°readOnly
        firstName
        lastName
}

This is equivalent to setting readOnly=true on firstName and lastName directly. The exact property name (readOnly in this example) must be defined as a boolean property in a profile contribution for fields.


4. Enums

Enums define a closed set of values:

cmn
enum OrderStatus {
    NEW
    CONFIRMED
    SHIPPED
}

Platforms can generate enum types in target languages, validation code or documentation from enums.

4.1 Enum value properties

Each enum literal can have one or more properties. This is useful when the external representation (for example a code in a database or JSON payload) differs from the enum name used in the model.

cmn
enum SetupType {

    NONE value="none"
    NP   value="NP"
    JP   value="JP"
}
  • NONE, NP, JP – enum literals used in the model and in generated code.
  • value="..." – property attached to the literal. Profiles and generators can use this to control the serialized form or map to external codes.

Additional properties can be defined in profiles and referenced on enum literals in the same way.


5. Services and operations

Services group operations that represent business capabilities.

cmn
service CustomerService {

    operation findCustomerById(id: Id): Customer

    operation updateCustomer(customer: Customer)
}
  • service – defines a named service.
  • operation – defines a method with parameters and an optional return type.
  • Stereotypes such as dao, controller, gateway can refine the role of a service or operation.

In the Base platform, services are mainly used for structuring and documentation. Other platforms can generate interfaces, adapters or handlers from services.


5. Resources and resource methods

Resources describe externally visible APIs, typically HTTP/REST endpoints. JoinedWorkz tries to make RESTful modelling easy while still allowing arbitrary HTTP+JSON style endpoints.

A resource always has a path starting with /. Anonymous root resources (with just /) are also possible.

cmn
resource /management {
    // nested resources and methods
}

5.1 Singleton vs. collection resources

JoinedWorkz distinguishes between

  • singleton resources – one logical instance, and
  • collection resources – a collection of items.

The default representation of a resource is defined with as and a type. A collection resource is indicated by a cardinality [] (or other bounds) on that type.

cmn
// singleton
resource /settings as SystemSettings { }

// collection
resource /customers as Customer[] by id {
    // methods and sub-resources
}

5.2 Identifiers for collection items (by)

Collection resources specify how individual items are identified using the by clause. One or more identifiers can be used:

cmn
resource /customers as Customer[] by id { }

Identifiers can be declared in two ways:

  1. By field name – referring to a field on the representation type:

    cmn
    type Customer {
        id**: Id
        name: String
    }
    
    resource /customers as Customer[] by id { }

    The existence and type of id are validated against Customer.

  2. By explicit name and type – using name : Type:

    cmn
    resource /customers as Customer[] by id : Id { }

Both forms are equivalent in terms of semantics. The first form is shorter and ensures the identifier matches an existing field on the representation.

5.3 Resource trees and sub-resources

Resources can be nested to form a hierarchical resource tree. There are two ways to define sub-resources:

  1. Inline inside the parent resource
  2. By referencing another resource

Resources that are only meant to be included as sub-resources (not as top-level API roots) should be marked abstract:

cmn
abstract resource /address by Address { }

Inside a parent resource you can:

  • reference a sub-resource at a nested path:

    cmn
    resource /management {
        /customers as Customer[] by id {
            /report as CustomersReport { }
        }
    }
  • reference a sub-resource at the item level of a collection by prefixing the sub-path with a dot:

    cmn
    resource /management {
        /customers as Customer[] by id {
            ./address        // item-level sub-resource
        }
    }
    
    abstract resource /address by Address { }

Here ./address means: for each /management/customers/{id} item there is a nested /management/customers/{id}/address resource.

The actual definition of /address can be in the same or another CMN file.

5.4 Resource properties

Resources can have additional properties that generators may interpret, for example:

cmn
resource /customers as Customer[] by id
    handlerClass="com.example.CustomerHandler" {
}

The available properties depend on the platform and the profiles in use (e.g. handler classes, tags, security metadata, …).

5.5 Resource methods

Inside a resource, 0..n resource methods can be defined. Each resource method starts with a reference to a method type (methodtype), then an optional local name, followed by parentheses for query parameters and finally optional properties.

cmn
resource /customers as Customer[] by id {

    query(category: String)
    create() consumes=NewCustomer
    read readCustomer() produces=CustomerDetail
    update() operationId="updateCustomer"
    delete()
}
  • query, create, read, update, delete – references to method types.
  • readCustomer – local method name (optional; if omitted, the method type name is used).
  • (...) – query parameters for this resource method.
  • Properties such as consumes, produces, operationId override or complement defaults from the method type.

Whether a method on a collection resource is executed on the collection or on a single item is defined by the method type property instance (see below).

5.6 Method types (methodtype)

Method types define reusable HTTP semantics for resource methods. A method type starts with the keyword methodtype, followed by its name and the HTTP verb, then a list of properties.

The Base facility now provides new kinds of method types::

  • Raw method types – pure HTTP verbs without defaults
  • Opinionated method types - REST-oriented defaults for typical CRUD and query operations

5.6.1 Raw method types

Raw method types map directly to HTTP verbs and do not define any defaults:

cnm methodtype get GET methodtype post POST methodtype put PUT methodtype patch PATCH methodtype delete DELETE

Use these when you need full control over:

  • consumes / produces
  • status codes
  • whether a method operates on the collection or on a single item
  • additional responses and metadata

All relevant properties must then be specified on each resource method (that uses these method types).

5.6.2 Opinionated method types

For most REST-style APIes you can use the opinionated method types defined by the Base facility:

cnm
methodtype create  POST consumes='*' produces=Id success=201
methodtype start   POST consumes='*' produces=Id success=201
methodtype execute POST consumes='*' produces='*' success=201

methodtype read    GET  instance=true produces='*' success=200 errorResponse=ErrorResponse
methodtype downloadText GET instance=true produces='text/plain' success=200

methodtype update  PUT  instance=true consumes='*' produces='*' success=200
methodtype delete  DELETE instance=true produces=Id success=200

methodtype query   GET produces='j**' success=200 paging=true sorting=true responseContext=ResponseContext errorResponse=ErrorResponse
methodtype list   GET produces='j**' success=200 paging=true sorting=true errorResponse=ErrorResponse

Important properties::

  • instance (boolean)

    • false (default): the method operates on the collectionn.
    • true: the method operates on a single item (collection item).
  • consumes / produces

    • Literal string or type reference.
    • Special values:
      • *) - use the default representation type of the resource.
      • **\[]' - collection of the default representation type.
      • ** - use the identifier type of the resource (e.g. the ID type of an entity).
    • You can also specify a dictionary, e.g. String[String] for generic key-value pairs.
  • success
    Default success HTTP status code for this method, e.g. 400, 201, 400.

  • paging… / sorting` Indicate that the result can be paged and sorted. Generators can use this to add pagination and sorting parameters or metadata.

  • responseContext
    References a type (for example ResponseContext) that carries metadata about the response (such as total number of items).

  • errorResponse
    References a type (for example ErrorResponse) used for error payloads.

Resource methods can override these properties locally to deviate from the defaults of the method type. In practice:

  • prefer the opinionated method types for standard REST scenarios,
  • fall back to the raw method types when the default semantics do not fit your use case.

5.7 Additional responses

Method types and concrete resource methods can define additional responses besides the default success code. The syntax is:

cmn
methodtype read GET instance=true produces='*' {
    404: NotFoundError
}

or inline after the method header (depending on your formatting style).

  • 404: – HTTP status code.
  • NotFoundError – type of the response body.
  • / as right-hand side means “no body” (empty response).

This information is typically used by the OpenAPI cartridge and other documentation/generation tools.

5.8 Consumes/produces dictionaries

Besides simple types and resource representations, consumes and produces can also describe dictionaries:

cmn
methodtype getAttributes GET produces=String[String] success=200

This example represents a map with String keys and String values in the response body.


These constructs make it easy to model RESTful APIs with clear separation between resources, their representations, identifiers and HTTP semantics, while keeping the model concise and reusable via method types.


7. Components

Components group API endpoints into deployable building blocks.

cmn
component QuickstartBackend
    basePackage='com.example.quickstart.webapp' {

    provide /hello
        subPackage='greeting.v1'
        controller="GreetingV1Controller" {
        // optional pseudo-code (see below)
    }
}
  • component – component name.
  • basePackage – base package for implementation artefacts.
  • provide /path – declares that this component implements the resource /path defined in the imported API model.
  • subPackage – appended to basePackage to form a full package.
  • controller – logical controller name; platforms can map this to class names or OpenAPI tags.

7.1 Pseudo-code in components

Inside a provide block you can describe behaviour of individual methods for documentation/diagrams. Example:

cmn
provide /hello subPackage='greeting.v1' controller="GreetingV1Controller" {

    read {
        if 'is morning' {
            [[ return 'Good Morning' ]]
        } else 'is evening' {
            [[ return 'Good Evening' ]]
        }
    }
}
  • Block name (read) corresponds to the resource method.
  • if 'is morning' { ... } else 'is evening' { ... } describes control flow.
  • [[ ... ]] marks actions; in this example simple return statements.

Diagram cartridges can turn this into sequence-like diagrams. The pseudo- code is not executable; it is for documentation and analysis only.


8. Applications

Applications assemble components into runnable systems.

cmn
application QuickstartApp {

    consists of {
        QuickstartBackend
    }

    use {
        // external components (optional)
        // ExternalBillingBackend
    }
}
  • consists of lists components that belong to the application.
  • use lists external components that this application depends on.

Platforms such as Base can generate:

  • component diagrams,
  • aggregated OpenAPI documents for all endpoints in the application,
  • HTML viewers for those OpenAPI documents.

9. Keywords (CMN)

Important CMN keywords at a glance:

  • package, import, platform, subpackage
  • type, enum
  • service, operation
  • resource, methodtype
  • component, provide
  • application, consists of, use
  • if, else