Appearance
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 Basepackage– mandatory; defines the namespace of the model (com.example.appin the example).core– optional layer in front ofpackage. 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 withpackage com.example.app.import– brings other packages into scope.as– defines an alias for an imported package (sharedin the example). The alias can later be used as a prefix likeshared::TypeName.platform– selects the platform (e.g.Base,SpringBoot).platform X exclude Ydisables cartridgeYfor 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=trueThe
formatterelement 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=3string– the stereotype (from the Base profile).maxLengthin 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=trueNamekeeps all properties fromString, but fixesmaxLengthto50.TextspecializesStringand introduces a parametermaxLengthplus an additional propertymultiLine.
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:
On the simple type definition (as shown above):
cmntype Name specialization of String maxLength=50On a field in a complex type:
cmntype Person { name: Name maxLength=80 // override for this field only }Via parameters when using the simple type:
cmntype 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=JSONThis 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,projectioninfluence 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 leastminelements (min..n).[max]– up tomaxelements (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 stereotypekey(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: IntegerThese 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,gatewaycan 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:
By field name – referring to a field on the representation type:
cmntype Customer { id**: Id name: String } resource /customers as Customer[] by id { }The existence and type of
idare validated againstCustomer.By explicit name and type – using
name : Type:cmnresource /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:
- Inline inside the parent resource
- 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:
cmnresource /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:
cmnresource /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,operationIdoverride 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=ErrorResponseImportant 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 exampleResponseContext) that carries metadata about the response (such as total number of items).errorResponse
References a type (for exampleErrorResponse) 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=200This 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/pathdefined in the imported API model.subPackage– appended tobasePackageto 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 oflists components that belong to the application.uselists 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,subpackagetype,enumservice,operationresource,methodtypecomponent,provideapplication,consists of,useif,else
