Skip to content

Model file structure (.cmn)

This page explains how a Canonical Model Notation (CMN) model file is structured and how elements in different files reference each other.

A CMN file has two main parts:

  1. a header – package, layer, imports, platform selection
  2. the content – types, services, resources, components, applications, …

Understanding this structure helps you organise larger models and reuse elements across packages and modules.


1. Header

The header appears at the top of a .cmn file and typically contains:

  • the package (and optional layer)
  • one or more imports (with optional aliases)
  • the platform specification for the model

A simplified example:

cmn
package com.example.main

import org.joinedworkz.facilities.common.base as base
import org.joinedworkz.facilities.common.base.api
import org.joinedworkz.facilities.profiles.springboot

platform SpringBoot exclude DtoCartridge

1.1 Package and layer

Project CMN files should normally declare a package. A declared package:

  • defines the namespace of the elements declared in the file,
  • is part of the fully qualified name of each element,
  • drives namespaces, names and output paths (e.g. Java packages).

Later, models can optionally specify a layer in addition to the package. Layers are used by platforms and generators to apply different rules to different parts of the model. In release 1.3.80, tag-aware Java class generators also pass the layer as a source tag so that a matching directory mapping can be selected within their outlet. This routing capability is generator-specific, not a promise for every outlet.

1.2 Imports and aliases

Imports make elements from other packages available in the current file without having to use fully qualified names everywhere.

A basic import looks like:

cmn
import org.joinedworkz.facilities.common.base.api

You can also define an alias for the imported package:

cmn
import org.joinedworkz.facilities.common.base as base

The alias can then be used as a prefix when referencing elements from that package, using the alias::Name syntax. For example:

cmn
type Info {
    createdAt: base::Timestamp
}

Here:

  • base is the alias defined in the import,
  • Timestamp is a type defined in the imported Base model,
  • base::Timestamp is an explicit reference to that type.

This has a few advantages:

  • your model stays readable even with longer package names,
  • you can clearly see from which package a referenced element comes,
  • you avoid ambiguities when different packages define elements with the same simple name.

If you do not use an alias, you can either rely on simple names (if there is no ambiguity) or use fully qualified names where supported by the DSL.

1.3 Platform specification

The platform specification selects the platform that should be used for this model file (or for the package/module it belongs to).

A simple platform declaration looks like:

cmn
platform SpringBoot

This tells the generator to use the SpringBoot platform defined in a .profile file. The platform controls which cartridges and generators are applied to the model and how they interpret its elements.

You can also exclude specific cartridges from the platform for this model, for example:

cmn
platform SpringBoot exclude DtoCartridge

In this case:

  • the SpringBoot platform is still used,
  • all its normal cartridges apply,
  • except the cartridge named DtoCartridge, which is disabled for this model.

This allows you to fine-tune generation behaviour without changing the platform profile itself. Typical use cases include:

  • disabling a cartridge for experimental or partial migrations,
  • avoiding generation of certain artifacts for specific modules,
  • temporarily turning off a cartridge while adjusting its configuration.

2. Content: model elements

After the header, a CMN file contains the model elements that make up your canonical model. The main categories are:

  • data types – simple, complex types and enums
  • services – operations representing business capabilities
  • resources – externally visible APIs (e.g. REST endpoints)
  • components – group resources/services into deployable units
  • applications – top-level compositions of components

These elements are described conceptually in:

Within the same file, you can define multiple elements of each kind. All of them share the package from the header.

2.1 Subpackages

To structure larger models, you can define subpackages inside a package using the subpackage keyword. For example:

cmn
package com.example.main

import org.joinedworkz.facilities.common.base

platform SpringBoot

subpackage sub1 {

    // model elements of com.example.main.sub1
}

subpackage sub2.sub3 {

    // model elements of com.example.main.sub2.sub3
}

In this example:

  • elements inside subpackage sub1 { ... } belong to the package com.example.main.sub1,
  • elements inside subpackage sub2.sub3 { ... } belong to com.example.main.sub2.sub3.

Subpackages help you:

  • group related model elements without splitting them into many files,
  • reflect a logical hierarchy inside a larger package,
  • keep the header (imports, platform) in one place while structuring the content.

Each subpackage shares the imports and platform specification of the parent package; you do not need to repeat them inside the subpackage block.


3. Referencing model elements

CMN models frequently reference elements defined:

  • in the same file,
  • in other files of the same package,
  • in other packages (via imports and aliases).

3.1 Same package

Within the same package, you normally reference elements by their simple name, for example:

cmn
type Greeting {
    message: String
}

resource /hello as Greeting {
    create()
    read()
}

Here, Greeting is a type defined in the same package, so no prefix or qualification is needed.

3.2 Imported packages and aliases

When referencing elements from imported packages, you typically:

  1. import the package,
  2. optionally assign an alias,
  3. use the alias with the alias::Name syntax.

Example:

cmn
import org.joinedworkz.facilities.common.base as base

type Info {
    createdAt: base::Timestamp
}

This makes it clear that Timestamp comes from the Base model. If you have multiple imports defining a type with the same name, using aliases avoids ambiguity.

3.3 Fully qualified references

You can also use a fully qualified name directly:

cmn
type Info {
    createdAt: org.joinedworkz.facilities.common.base.Timestamp
}

In practice, aliases are usually preferred for readability, especially when the same external package is used in many places.


4. Summary

A CMN model file is structured into:

  • a header, which defines:
    • the package (and optional layer),
    • imports and aliases,
    • the platform and optional cartridge exclusions;
  • a content section with the actual model elements:
    • types, services, resources, components, applications,
    • optionally organised into subpackages.

References between elements rely on:

  • simple names within the same package,
  • imports and aliases for other packages (alias::Name),
  • and, where appropriate, fully qualified names.

Keeping this structure in mind makes it easier to:

  • split models across multiple files and packages,
  • share common types via imported base models,
  • control platform behaviour per package or module,
  • and maintain large models over time.